From 7de148935574cce35da0fcbd7e5aa52a7baf73ab Mon Sep 17 00:00:00 2001 From: Fernando Macedo Date: Sat, 6 Jul 2024 13:07:37 -0300 Subject: [PATCH 1/5] docs: Moving from rtd_theme to furo (#460) * docs: Moving from rtd_theme to furo * docs: Improving docs and examples * docs: Upd docs deps to python3.8 --- docs/actions.md | 67 +++-- docs/async.md | 75 +++-- docs/conf.py | 29 +- docs/installation.md | 41 +-- docs/listeners.md | 8 +- docs/releases/2.0.0.md | 4 +- docs/releases/2.3.2.md | 3 +- docs/transitions.md | 25 +- poetry.lock | 278 +++++++++++------- pyproject.toml | 11 +- statemachine/statemachine.py | 2 +- statemachine/states.py | 13 +- .../async_guess_the_number_machine.py | 10 +- tests/examples/async_without_loop_machine.py | 4 +- tests/examples/reusing_transitions_machine.py | 97 ++++++ tests/examples/traffic_light_machine.py | 99 +++---- tests/scrape_images.py | 6 +- tests/test_examples.py | 16 +- tests/test_listener.py | 7 +- tests/test_statemachine.py | 66 +++-- tests/test_statemachine_inheritance.py | 13 +- tests/test_transitions.py | 7 +- 22 files changed, 570 insertions(+), 311 deletions(-) create mode 100644 tests/examples/reusing_transitions_machine.py diff --git a/docs/actions.md b/docs/actions.md index 00dc4a83..10526a17 100644 --- a/docs/actions.md +++ b/docs/actions.md @@ -304,33 +304,54 @@ See {ref}`conditions` and {ref}`validators`. ## Ordering -Actions and Guards will be executed in the following order: +There are major groups of callbacks, these groups run sequentially. -- `validators()` (attached to the transition) - -- `conditions()` (attached to the transition) - -- `unless()` (attached to the transition) - -- `before_transition()` - -- `before_()` - -- `on_exit_state()` - -- `on_exit_()` - -- `on_transition()` - -- `on_()` - -- `on_enter_state()` +```{warning} +Actions registered on the same group don't have order guaranties and are executed in parallel when using the {ref}`AsyncEngine`, and may be executed in parallel in future versions of {ref}`SyncEngine`. +``` -- `on_enter_()` -- `after_()` +```{list-table} +:header-rows: 1 + +* - Group + - Action + - Current state + - Description +* - Validators + - `validators()` + - `source` + - Validators raise exceptions. +* - Conditions + - `cond()`, `unless()` + - `source` + - Conditions are predicates that prevent transitions to occur. +* - Before + - `before_transition()`, `before_()` + - `source` + - Callbacks declared in the transition or event. +* - Exit + - `on_exit_state()`, `on_exit_()` + - `source` + - Callbacks declared in the source state. +* - On + - `on_transition()`, `on_()` + - `source` + - Callbacks declared in the transition or event. +* - **State updated** + - + - + - Current state is updated. +* - Enter + - `on_enter_state()`, `on_enter_()` + - `destination` + - Callbacks declared in the destination state. +* - After + - `after_()`, `after_transition()` + - `destination` + - Callbacks declared in the transition or event. -- `after_transition()` +``` ## Return values diff --git a/docs/async.md b/docs/async.md index 5909b34a..aeed7685 100644 --- a/docs/async.md +++ b/docs/async.md @@ -8,19 +8,15 @@ The {ref}`StateMachine` fully supports asynchronous code. You can write async {r This is achieved through a new concept called "engine," an internal strategy pattern abstraction that manages transitions and callbacks. -There are two engines: +There are two engines, {ref}`SyncEngine` and {ref}`AsyncEngine`. -SyncEngine -: Activated if there are no async callbacks. All code runs exactly as it did before version 2.3.0. -AsyncEngine -: Activated if there is at least one async callback. The code runs asynchronously and requires a running event loop, which it will create if none exists. +## Sync vs async engines -These engines are internal and are activated automatically by inspecting the registered callbacks in the following scenarios: +Engines are internal and are activated automatically by inspecting the registered callbacks in the following scenarios. ```{list-table} Sync vs async engines -:widths: 15 10 25 10 10 :header-rows: 1 * - Outer scope @@ -30,32 +26,40 @@ These engines are internal and are activated automatically by inspecting the reg - Reuses external loop * - Sync - No - - Sync + - SyncEngine - No - No * - Sync - Yes - - Async + - AsyncEngine - Yes - No * - Async - No - - Sync + - SyncEngine - No - No * - Async - Yes - - Async + - AsyncEngine - No - Yes ``` - ```{note} All handlers will run on the same thread they are called. Therefore, mixing synchronous and asynchronous code is not recommended unless you are confident in your implementation. ``` +### SyncEngine +Activated if there are no async callbacks. All code runs exactly as it did before version 2.3.0. +There's no event loop. + +### AsyncEngine +Activated if there is at least one async callback. The code runs asynchronously and requires a running event loop, which it will create if none exists. + + + ## Asynchronous Support We support native coroutine callbacks using asyncio, enabling seamless integration with asynchronous code. There is no change in the public API of the library to work with asynchronous codebases. @@ -72,6 +76,7 @@ async code with a state machine. ... initial = State('Initial', initial=True) ... final = State('Final', final=True) ... +... keep = initial.to.itself(internal=True) ... advance = initial.to(final) ... ... async def on_advance(self): @@ -91,7 +96,10 @@ Final ## Sync codebase with async callbacks -The same state machine can be executed in a synchronous codebase, even if it contains async callbacks. The callbacks will be awaited using `asyncio.get_event_loop()` if needed. +The same state machine with async callbacks can be executed in a synchronous codebase, +even if the calling context don't have an asyncio loop. + +If needed, the state machine will create a loop using `asyncio.new_event_loop()` and callbacks will be awaited using `loop.run_until_complete()`. ```py @@ -109,24 +117,53 @@ Final ## Initial State Activation for Async Code -If you perform checks against the `current_state`, like a loop `while sm.current_state.is_final:`, then on async code you must manually +If **on async code** you perform checks against the `current_state`, like a loop `while sm.current_state.is_final:`, then you must manually await for the [activate initial state](statemachine.StateMachine.activate_initial_state) to be able to check the current state. +```{hint} +This manual initial state activation on async is because Python don't allow awaiting at class initalization time and the initial state activation may contain async callbacks that must be awaited. +``` + If you don't do any check for current state externally, just ignore this as the initial state is activated automatically before the first event trigger is handled. +You get an error checking the current state before the initial state activation: + +```py +>>> async def initialize_sm(): +... sm = AsyncStateMachine() +... print(sm.current_state) + +>>> asyncio.run(initialize_sm()) +Traceback (most recent call last): +... +InvalidStateValue: There's no current state set. In async code, did you activate the initial state? (e.g., `await sm.activate_initial_state()`) + +``` + +You can activate the initial state explicitly: + ```py >>> async def initialize_sm(): ... sm = AsyncStateMachine() ... await sm.activate_initial_state() -... return sm +... print(sm.current_state) ->>> sm = asyncio.run(initialize_sm()) ->>> print(sm.current_state) +>>> asyncio.run(initialize_sm()) Initial ``` -```{hint} -This manual initial state activation on async is because Python don't allow awaiting at class initalization time and the initial state activation may contain async callbacks that must be awaited. +Or just by sending an event. The first event activates the initial state automatically +before the event is handled: + +```py +>>> async def initialize_sm(): +... sm = AsyncStateMachine() +... await sm.keep() # first event activates the initial state before the event is handled +... print(sm.current_state) + +>>> asyncio.run(initialize_sm()) +Initial + ``` diff --git a/docs/conf.py b/docs/conf.py index cdd828e1..1e8f2afa 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,7 +14,6 @@ import os import sys -import sphinx_rtd_theme from sphinx_gallery import gen_gallery # If extensions (or modules to document with autodoc) are in another @@ -51,6 +50,7 @@ "sphinx.ext.viewcode", "sphinx.ext.autosectionlabel", "sphinx_gallery.gen_gallery", + "sphinx_copybutton", ] # Add any paths that contain templates here, relative to this directory. @@ -108,7 +108,6 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -122,16 +121,14 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "sphinx_rtd_theme" +html_theme = "furo" +# https://pradyunsg.me/furo/ # Theme options are theme-specific and customize the look and feel of a # theme further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} -# Add any paths that contain custom themes here, relative to this directory. -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None @@ -163,6 +160,23 @@ "https://buttons.github.io/buttons.js", ] +html_title = f"python-statemachine {release}" +html_logo = "images/python-statemachine.png" + +html_copy_source = False +html_show_sourcelink = False + +html_theme_options = { + "navigation_with_keys": True, + "top_of_page_buttons": ["view", "edit"], + "source_repository": "https://github.com/fgmacedo/python-statemachine/", + # "source_branch": "develop", + "source_directory": "docs/", +} + +pygments_style = "monokai" +pygments_dark_style = "monokai" + # If not '', a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' @@ -274,6 +288,9 @@ } +copybutton_exclude = ".linenos, .gp, .go" + + def dummy_write_computation_times(gallery_conf, target_dir, costs): "patch gen_gallery to disable write_computation_times" pass diff --git a/docs/installation.md b/docs/installation.md index 97e7a093..bdf59f93 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,27 +1,28 @@ # Installation -## Stable release +## Latest release -To install Python State Machine, if you're using [poetry](https://python-poetry.org/): +To install Python State Machine using [poetry](https://python-poetry.org/): - poetry add python-statemachine +```shell +poetry add python-statemachine +``` +Alternatively, if you prefer using [pip](https://pip.pypa.io): -Or to install using [pip](https://pip.pypa.io): +```shell +python3 -m pip install python-statemachine +``` - pip install python-statemachine +For those looking to generate diagrams from your state machines, [pydot](https://github.com/pydot/pydot) and [Graphviz](https://graphviz.org/) are required. +Conveniently, you can install python-statemachine along with the `pydot` dependency using the extras option. +For more information, please refer to our documentation. +```shell +python3 -m pip install "python-statemachine[diagrams]" +``` -To generate diagrams from your machines, you'll also need `pydot` and `Graphviz`. You can -install this library already with `pydot` dependency using the `extras` install option. See -our docs for more details. - - pip install python-statemachine[diagrams] - - -If you don't have [pip](https://pip.pypa.io) installed, this [Python installation guide](http://docs.python-guide.org/en/latest/starting/installation/) can guide -you through the process. ## From sources @@ -30,12 +31,18 @@ The sources for Python State Machine can be downloaded from the [Github repo](ht You can either clone the public repository: - git clone git://github.com/fgmacedo/python-statemachine +```shell +git clone git://github.com/fgmacedo/python-statemachine +``` Or download the `tarball`: - curl -OL https://github.com/fgmacedo/python-statemachine/tarball/master +```shell +curl -OL https://github.com/fgmacedo/python-statemachine/tarball/main +``` Once you have a copy of the source, you can install it with: - python setup.py install +```shell +python3 -m pip install -e . +``` diff --git a/docs/listeners.md b/docs/listeners.md index b33b6f78..7fcbdf37 100644 --- a/docs/listeners.md +++ b/docs/listeners.md @@ -29,9 +29,9 @@ Giving the {ref}`sphx_glr_auto_examples_traffic_light_machine.py` as example: Paulista Avenue enter: green from __initial__ >>> sm.cycle() +Running cycle from green to yellow Paulista Avenue enter: yellow from cycle Paulista Avenue after: green--(cycle)-->yellow -'Running cycle from green to yellow' ``` @@ -71,20 +71,18 @@ Now each "LED panel" reacts to changes in state from the state machine: ```py >>> sm.cycle() +Running cycle from yellow to red yellow turning off Paulista Avenue enter: red from cycle red turning on -Don't move. Paulista Avenue after: yellow--(cycle)-->red -'Running cycle from yellow to red' >>> sm.cycle() +Running cycle from red to green red turning off -Go ahead! Paulista Avenue enter: green from cycle green turning on Paulista Avenue after: red--(cycle)-->green -'Running cycle from red to green' ``` diff --git a/docs/releases/2.0.0.md b/docs/releases/2.0.0.md index 941e843c..594f6407 100644 --- a/docs/releases/2.0.0.md +++ b/docs/releases/2.0.0.md @@ -298,7 +298,7 @@ Should become: >>> sm = TrafficLightMachine() >>> sm.send("cycle") -'Running cycle from green to yellow' +Running cycle from green to yellow ``` @@ -319,7 +319,7 @@ Should become: >>> from tests.examples.traffic_light_machine import TrafficLightMachine >>> sm = TrafficLightMachine() ->>> assert [t.name for t in sm.allowed_events] == ["cycle", "slowdown"] +>>> assert [t.name for t in sm.allowed_events] == ["cycle"] ``` diff --git a/docs/releases/2.3.2.md b/docs/releases/2.3.2.md index 61eeaabe..0ca74f26 100644 --- a/docs/releases/2.3.2.md +++ b/docs/releases/2.3.2.md @@ -54,9 +54,10 @@ Example: Paulista Avenue enter: green from __initial__ >>> sm.cycle() +Running cycle from green to yellow Paulista Avenue enter: yellow from cycle Paulista Avenue after: green--(cycle)-->yellow -'Running cycle from green to yellow' + ``` diff --git a/docs/transitions.md b/docs/transitions.md index e257929f..f0f84918 100644 --- a/docs/transitions.md +++ b/docs/transitions.md @@ -32,10 +32,12 @@ This state machine could be expressed in `python-statemachine` as: ```{literalinclude} ../tests/examples/traffic_light_machine.py :language: python :linenos: -:emphasize-lines: 18 +:emphasize-lines: 12 +:start-at: from statemachine +:end-before: "# %%" ``` -In line 18, you can say that this code defines three transitions: +In line 12, you can say that this code defines three transitions: * `green.to(yellow)` * `yellow.to(red)` @@ -43,21 +45,26 @@ In line 18, you can say that this code defines three transitions: And these transitions are assigned to the {ref}`event` `cycle` defined at the class level. +```{note} + +In fact, before the full class body is evaluated, the assigments of transitions are instances of [](statemachine.transition_list.TransitionList). When the state machine is evaluated by our custom [metaclass](https://docs.python.org/3/reference/datamodel.html#metaclasses), these names will be transformed into a method that triggers an {ref}`Event`. + +``` + ## Transitions In an executing state machine, a {ref}`transition` is a transfer from one state to another. In a {ref}`statemachine`, a {ref}`transition` tells us what happens when an {ref}`event` occurs. -```{tip} A transition can define {ref}`actions` that will be executed whenever that transition is executed. -Transitions can be filtered with {ref}`guards` allowing you to add conditions when a +Transitions can have {ref}`conditions` allowing you to specify when a transition may be executed. An action associated with an event (before, on, after), will be assigned to all transitions bounded that uses the event as trigger. -``` + ```{hint} Usually you don't need to import and use a {ref}`transition` class directly in your code, @@ -188,7 +195,7 @@ You can invoke the event in an imperative syntax: >>> machine = TrafficLightMachine() >>> machine.cycle() -'Running cycle from green to yellow' +Running cycle from green to yellow >>> machine.current_state.id 'yellow' @@ -199,8 +206,7 @@ Or in an event-oriented style, events are `send`: ```py >>> machine.send("cycle") -Don't move. -'Running cycle from yellow to red' +Running cycle from yellow to red >>> machine.current_state.id 'red' @@ -227,8 +233,7 @@ You can raise an exception at this point to stop a transition from completing. 'red' >>> machine.cycle() -Go ahead! -'Running cycle from red to green' +Running cycle from red to green >>> machine.current_state.id 'green' diff --git a/poetry.lock b/poetry.lock index 0e7fec44..d7e7ab10 100644 --- a/poetry.lock +++ b/poetry.lock @@ -30,13 +30,13 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] name = "babel" -version = "2.14.0" +version = "2.15.0" description = "Internationalization utilities" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, - {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, ] [package.dependencies] @@ -45,15 +45,36 @@ pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "certifi" -version = "2024.6.2" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, - {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] @@ -288,11 +309,8 @@ name = "docutils" version = "0.19" description = "Docutils -- Python Documentation Utilities" optional = false -python-versions = ">=3.7" -files = [ - {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, - {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, -] +python-versions = "*" +files = [] [[package]] name = "exceptiongroup" @@ -323,6 +341,23 @@ files = [ docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] +[[package]] +name = "furo" +version = "2024.5.6" +description = "A clean customisable Sphinx documentation theme." +optional = false +python-versions = ">=3.8" +files = [ + {file = "furo-2024.5.6-py3-none-any.whl", hash = "sha256:490a00d08c0a37ecc90de03ae9227e8eb5d6f7f750edf9807f398a2bdf2358de"}, + {file = "furo-2024.5.6.tar.gz", hash = "sha256:81f205a6605ebccbb883350432b4831c0196dd3d1bc92f61e1f459045b3d2b0b"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +pygments = ">=2.7" +sphinx = ">=6.0,<8.0" +sphinx-basic-ng = ">=1.0.0.beta2" + [[package]] name = "gprof2dot" version = "2022.7.29" @@ -434,18 +469,17 @@ tornado = "*" [[package]] name = "markdown-it-py" -version = "2.2.0" +version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, - {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, ] [package.dependencies] mdurl = ">=0.1,<1.0" -typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark"] @@ -454,7 +488,7 @@ compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0 linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] -rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] @@ -528,21 +562,21 @@ files = [ [[package]] name = "mdit-py-plugins" -version = "0.3.5" +version = "0.4.1" description = "Collection of plugins for markdown-it-py" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mdit-py-plugins-0.3.5.tar.gz", hash = "sha256:eee0adc7195e5827e17e02d2a258a2ba159944a0748f59c5099a4a27f78fcf6a"}, - {file = "mdit_py_plugins-0.3.5-py3-none-any.whl", hash = "sha256:ca9a0714ea59a24b2b044a1831f48d817dd0c817e84339f20e7889f392d77c4e"}, + {file = "mdit_py_plugins-0.4.1-py3-none-any.whl", hash = "sha256:1020dfe4e6bfc2c79fb49ae4e3f5b297f5ccd20f010187acc52af2921e27dc6a"}, + {file = "mdit_py_plugins-0.4.1.tar.gz", hash = "sha256:834b8ac23d1cd60cec703646ffd22ae97b7955a6d596eb1d304be1e251ae499c"}, ] [package.dependencies] -markdown-it-py = ">=1.0.0,<3.0.0" +markdown-it-py = ">=1.0.0,<4.0.0" [package.extras] code-style = ["pre-commit"] -rtd = ["attrs", "myst-parser (>=0.16.1,<0.17.0)", "sphinx-book-theme (>=0.1.0,<0.2.0)"] +rtd = ["myst-parser", "sphinx-book-theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] @@ -616,30 +650,29 @@ files = [ [[package]] name = "myst-parser" -version = "1.0.0" +version = "3.0.1" description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "myst-parser-1.0.0.tar.gz", hash = "sha256:502845659313099542bd38a2ae62f01360e7dd4b1310f025dd014dfc0439cdae"}, - {file = "myst_parser-1.0.0-py3-none-any.whl", hash = "sha256:69fb40a586c6fa68995e6521ac0a525793935db7e724ca9bac1d33be51be9a4c"}, + {file = "myst_parser-3.0.1-py3-none-any.whl", hash = "sha256:6457aaa33a5d474aca678b8ead9b3dc298e89c68e67012e73146ea6fd54babf1"}, + {file = "myst_parser-3.0.1.tar.gz", hash = "sha256:88f0cb406cb363b077d176b51c476f62d60604d68a8dcdf4832e080441301a87"}, ] [package.dependencies] -docutils = ">=0.15,<0.20" +docutils = ">=0.18,<0.22" jinja2 = "*" -markdown-it-py = ">=1.0.0,<3.0.0" -mdit-py-plugins = ">=0.3.4,<0.4.0" +markdown-it-py = ">=3.0,<4.0" +mdit-py-plugins = ">=0.4,<1.0" pyyaml = "*" -sphinx = ">=5,<7" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} +sphinx = ">=6,<8" [package.extras] code-style = ["pre-commit (>=3.0,<4.0)"] -linkify = ["linkify-it-py (>=1.0,<2.0)"] -rtd = ["ipython", "pydata-sphinx-theme (==v0.13.0rc4)", "sphinx-autodoc2 (>=0.4.2,<0.5.0)", "sphinx-book-theme (==1.0.0rc2)", "sphinx-copybutton", "sphinx-design2", "sphinx-pyscript", "sphinx-tippy (>=0.3.1)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.7.5,<0.8.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] -testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=7,<8)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx-pytest"] -testing-docutils = ["pygments", "pytest (>=7,<8)", "pytest-param-files (>=0.3.4,<0.4.0)"] +linkify = ["linkify-it-py (>=2.0,<3.0)"] +rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] +testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] +testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] [[package]] name = "nodeenv" @@ -829,17 +862,16 @@ tests = ["black", "chardet", "tox"] [[package]] name = "pygments" -version = "2.17.2" +version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] -plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] [[package]] @@ -1081,13 +1113,13 @@ files = [ [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -1148,28 +1180,39 @@ files = [ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] +[[package]] +name = "soupsieve" +version = "2.5" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, +] + [[package]] name = "sphinx" -version = "5.3.0" +version = "7.1.2" description = "Python documentation generator" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, - {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, + {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, + {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.14,<0.20" +docutils = ">=0.18.1,<0.21" imagesize = ">=1.3" importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" packaging = ">=21.0" -Pygments = ">=2.12" -requests = ">=2.5.0" +Pygments = ">=2.13" +requests = ">=2.25.0" snowballstemmer = ">=2.0" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" @@ -1180,8 +1223,8 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"] -test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] +test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] [[package]] name = "sphinx-autobuild" @@ -1203,47 +1246,70 @@ sphinx = "*" test = ["pytest", "pytest-cov"] [[package]] -name = "sphinx-gallery" -version = "0.14.0" -description = "A `Sphinx `_ extension that builds an HTML gallery of examples from any set of Python scripts." +name = "sphinx-basic-ng" +version = "1.0.0b2" +description = "A modern skeleton for Sphinx themes." optional = false python-versions = ">=3.7" files = [ - {file = "sphinx-gallery-0.14.0.tar.gz", hash = "sha256:2a4a0aaf032955508e1d0f3495199a3c7819ce420e71096bff0bca551a4043c2"}, - {file = "sphinx_gallery-0.14.0-py3-none-any.whl", hash = "sha256:55b3ad1f378abd126232c166192270ac0a3ef615dec10b66c961ed2967be1df6"}, + {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"}, + {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"}, ] [package.dependencies] -sphinx = ">=4" +sphinx = ">=4.0" + +[package.extras] +docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] [[package]] -name = "sphinx-rtd-theme" -version = "2.0.0" -description = "Read the Docs theme for Sphinx" +name = "sphinx-copybutton" +version = "0.5.2" +description = "Add a copy button to each of your code cells." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, + {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, +] + +[package.dependencies] +sphinx = ">=1.8" + +[package.extras] +code-style = ["pre-commit (==2.12.1)"] +rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] + +[[package]] +name = "sphinx-gallery" +version = "0.16.0" +description = "A Sphinx extension that builds an HTML gallery of examples from any set of Python scripts." +optional = false +python-versions = ">=3.8" files = [ - {file = "sphinx_rtd_theme-2.0.0-py2.py3-none-any.whl", hash = "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586"}, - {file = "sphinx_rtd_theme-2.0.0.tar.gz", hash = "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b"}, + {file = "sphinx_gallery-0.16.0-py3-none-any.whl", hash = "sha256:f5456514f4efb230a6f1db6241667774ca3ee8f15e9a7456678f1d1815118e60"}, + {file = "sphinx_gallery-0.16.0.tar.gz", hash = "sha256:3912765bc5e7b5451dc471ad50ead808a9752280b23fd2ec4277719a5ef68e42"}, ] [package.dependencies] -docutils = "<0.21" -sphinx = ">=5,<8" -sphinxcontrib-jquery = ">=4,<5" +pillow = "*" +sphinx = ">=4" [package.extras] -dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] +jupyterlite = ["jupyterlite-sphinx"] +recommender = ["numpy"] +show-api-usage = ["graphviz"] +show-memory = ["memory-profiler"] [[package]] name = "sphinxcontrib-applehelp" -version = "1.0.2" -description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +version = "1.0.4" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" files = [ - {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, - {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, + {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, + {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, ] [package.extras] @@ -1267,33 +1333,19 @@ test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.0.0" +version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, - {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, + {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, + {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] test = ["html5lib", "pytest"] -[[package]] -name = "sphinxcontrib-jquery" -version = "4.1" -description = "Extension to include jQuery on newer Sphinx releases" -optional = false -python-versions = ">=2.7" -files = [ - {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, - {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, -] - -[package.dependencies] -Sphinx = ">=1.8" - [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" @@ -1380,22 +1432,22 @@ files = [ [[package]] name = "tornado" -version = "6.2" +version = "6.4.1" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false -python-versions = ">= 3.7" +python-versions = ">=3.8" files = [ - {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, - {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, - {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, - {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, - {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, ] [[package]] @@ -1472,18 +1524,18 @@ files = [ [[package]] name = "urllib3" -version = "2.0.7" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -1529,4 +1581,4 @@ diagrams = [] [metadata] lock-version = "2.0" python-versions = ">=3.7" -content-hash = "d3f11ae6fdae2a55261030f90f5aa09f05405a6719730973c90061be5b7bde3c" +content-hash = "712589ce962aab4a02f587b0f46e18cb4630547545782a38669687dd4917919e" diff --git a/pyproject.toml b/pyproject.toml index 04ae9581..4cb9d4f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,12 +56,13 @@ django = { version = "^5.0.3", python = ">3.10" } pytest-django = { version = "^4.8.0", python = ">3.8" } [tool.poetry.group.docs.dependencies] -Sphinx = "*" -sphinx-rtd-theme = "2.0.0" -myst-parser = "*" -sphinx-gallery = "*" +Sphinx = { version = "*", python = ">3.8" } +myst-parser = { version = "*", python = ">3.8" } +sphinx-gallery = { version = "*", python = ">3.8" } pillow = "*" -sphinx-autobuild = "*" +sphinx-autobuild = { version = "*", python = ">3.8" } +furo = { version = "^2024.5.6", python = ">3.8" } +sphinx-copybutton = { version = "^0.5.2", python = ">3.8" } [build-system] requires = ["poetry-core"] diff --git a/statemachine/statemachine.py b/statemachine/statemachine.py index 5f168837..a9f7061c 100644 --- a/statemachine/statemachine.py +++ b/statemachine/statemachine.py @@ -210,7 +210,7 @@ def _register_callbacks(self, listeners: List[object]): def add_observer(self, *observers): """Add a listener.""" warnings.warn( - """The `add_observer` was rebranded to `add_listener`.""", + """Method `add_observer` has been renamed to `add_listener`.""", DeprecationWarning, stacklevel=2, ) diff --git a/statemachine/states.py b/statemachine/states.py index ffc5fb82..1f5c2257 100644 --- a/statemachine/states.py +++ b/statemachine/states.py @@ -102,12 +102,13 @@ def from_enum(cls, enum_type: EnumType, initial, final=None, use_enum_instance: ... def on_enter_completed(self): ... print("Completed!") - .. note:: - Given that you assign the response of ``States.from_enum`` to a class level - variable on your :ref:`StateMachine` you're good to go, you can use any name to assign - this response, on this example we used ``_`` to indicate that the name does not matter. - The variable of type :ref:`States (class)` will be inspected by the metaclass and the - inner :ref:`State` instances assigned to the state machine. + .. tip:: + When you assign the result of ``States.from_enum`` to a class-level variable in your + :ref:`StateMachine`, you're all set. You can use any name for this variable. In this + example, we used ``_`` to show that the name doesn't matter. The metaclass will inspect + the variable of type :ref:`States (class)` and automatically assign the inner + :ref:`State` instances to the state machine. + Everything else is similar, the ``Enum`` is only used to declare the :ref:`State` instances. diff --git a/tests/examples/async_guess_the_number_machine.py b/tests/examples/async_guess_the_number_machine.py index fc6ec985..d6162f3d 100644 --- a/tests/examples/async_guess_the_number_machine.py +++ b/tests/examples/async_guess_the_number_machine.py @@ -110,6 +110,13 @@ async def on_enter_lose(self): self.writer(f"Oh, no! You've spent all your {self.guesses} attempts!") +# %% +# Async stdin/stdout +# ------------------ + +# This function will be used to connect the stdin and stdout to the asyncio event loop. + + async def connect_stdin_stdout(): loop = asyncio.get_event_loop() reader = asyncio.StreamReader() @@ -126,8 +133,7 @@ async def connect_stdin_stdout(): # Executing # --------- # -# This script only run by passing the `-i` flag, is is to avoid blocking while running automated -# tests. +# This script only run by passing the `-i` flag, avoiding blocking while running automated tests. # # To play the game, run this script and type a number between 1 and 5. # diff --git a/tests/examples/async_without_loop_machine.py b/tests/examples/async_without_loop_machine.py index b1d6c0f4..d6e8e4a3 100644 --- a/tests/examples/async_without_loop_machine.py +++ b/tests/examples/async_without_loop_machine.py @@ -31,7 +31,7 @@ async def on_finish(self): # --------- -def main(): +def sync_main(): sm = AsyncStateMachine() result = sm.start() print(f"Start result is {result}") @@ -42,4 +42,4 @@ def main(): if __name__ == "__main__": - main() + sync_main() diff --git a/tests/examples/reusing_transitions_machine.py b/tests/examples/reusing_transitions_machine.py new file mode 100644 index 00000000..8d935844 --- /dev/null +++ b/tests/examples/reusing_transitions_machine.py @@ -0,0 +1,97 @@ +""" + +------------------- +Reusing transitions +------------------- + +This example helps to turn visual the different compositions of how to declare +and bind :ref:`transitions` to :ref:`event`. + +.. note:: + + Even sharing the same transition instance, only the transition actions associated with the + event will be called. + + +TrafficLightMachine + The same transitions are bound to more than one event. + +TrafficLightIsolatedTransitions + We define new transitions, thus, isolating the connection + between states. + +""" + +from statemachine import State +from statemachine import StateMachine + + +class TrafficLightMachine(StateMachine): + "A traffic light machine" + + green = State(initial=True) + yellow = State() + red = State() + + slowdown = green.to(yellow) + stop = yellow.to(red) + go = red.to(green) + + cycle = slowdown | stop | go + + def before_slowdown(self): + print("Slowdown") + + def before_cycle(self, event: str, source: State, target: State, message: str = ""): + message = ". " + message if message else "" + return f"Running {event} from {source.id} to {target.id}{message}" + + def on_enter_red(self): + print("Don't move.") + + def on_exit_red(self): + print("Go ahead!") + + +# %% +# Run a transition + +sm = TrafficLightMachine() +sm.send("cycle") + + +# %% + + +class TrafficLightIsolatedTransitions(StateMachine): + "A traffic light machine" + + green = State(initial=True) + yellow = State() + red = State() + + slowdown = green.to(yellow) + stop = yellow.to(red) + go = red.to(green) + + cycle = green.to(yellow) | yellow.to(red) | red.to(green) + + def before_slowdown(self): + print("Slowdown") + + def before_cycle(self, event: str, source: State, target: State, message: str = ""): + message = ". " + message if message else "" + return f"Running {event} from {source.id} to {target.id}{message}" + + def on_enter_red(self): + print("Don't move.") + + def on_exit_red(self): + print("Go ahead!") + + +# %% +# Run a transition + +sm2 = TrafficLightIsolatedTransitions() +sm2.send("cycle") diff --git a/tests/examples/traffic_light_machine.py b/tests/examples/traffic_light_machine.py index 8d935844..b89167a1 100644 --- a/tests/examples/traffic_light_machine.py +++ b/tests/examples/traffic_light_machine.py @@ -1,27 +1,19 @@ """ -------------------- -Reusing transitions -------------------- +--------------------- +Traffic light machine +--------------------- -This example helps to turn visual the different compositions of how to declare -and bind :ref:`transitions` to :ref:`event`. +This example demonstrates how to create a traffic light machine using the `statemachine` library. -.. note:: - - Even sharing the same transition instance, only the transition actions associated with the - event will be called. - - -TrafficLightMachine - The same transitions are bound to more than one event. - -TrafficLightIsolatedTransitions - We define new transitions, thus, isolating the connection - between states. +The state machine will run in a dedicated thread and will cycle through the states. """ +import time +from threading import Event as ThreadingEvent +from threading import Thread + from statemachine import State from statemachine import StateMachine @@ -33,65 +25,40 @@ class TrafficLightMachine(StateMachine): yellow = State() red = State() - slowdown = green.to(yellow) - stop = yellow.to(red) - go = red.to(green) - - cycle = slowdown | stop | go - - def before_slowdown(self): - print("Slowdown") - - def before_cycle(self, event: str, source: State, target: State, message: str = ""): - message = ". " + message if message else "" - return f"Running {event} from {source.id} to {target.id}{message}" - - def on_enter_red(self): - print("Don't move.") - - def on_exit_red(self): - print("Go ahead!") - - -# %% -# Run a transition + cycle = green.to(yellow) | yellow.to(red) | red.to(green) -sm = TrafficLightMachine() -sm.send("cycle") + def before_cycle(self, event: str, source: State, target: State): + print(f"Running {event} from {source.id} to {target.id}") # %% +# Run in a dedicated thread -class TrafficLightIsolatedTransitions(StateMachine): - "A traffic light machine" +class Supervisor: + def __init__(self, sm: StateMachine, sm_event: str): + self.sm = sm + self.sm_event = sm_event + self.stop_event = ThreadingEvent() - green = State(initial=True) - yellow = State() - red = State() + def run(self): + while not self.stop_event.is_set(): + self.sm.send(self.sm_event) + self.stop_event.wait(0.1) - slowdown = green.to(yellow) - stop = yellow.to(red) - go = red.to(green) + def stop(self): + self.stop_event.set() - cycle = green.to(yellow) | yellow.to(red) | red.to(green) - - def before_slowdown(self): - print("Slowdown") - - def before_cycle(self, event: str, source: State, target: State, message: str = ""): - message = ". " + message if message else "" - return f"Running {event} from {source.id} to {target.id}{message}" - def on_enter_red(self): - print("Don't move.") +def main(): + supervisor = Supervisor(TrafficLightMachine(), "cycle") + t = Thread(target=supervisor.run) + t.start() - def on_exit_red(self): - print("Go ahead!") + time.sleep(0.5) + supervisor.stop() + t.join() -# %% -# Run a transition - -sm2 = TrafficLightIsolatedTransitions() -sm2.send("cycle") +if __name__ == "__main__": + main() diff --git a/tests/scrape_images.py b/tests/scrape_images.py index f77ac92b..1f57b6aa 100644 --- a/tests/scrape_images.py +++ b/tests/scrape_images.py @@ -1,7 +1,5 @@ import re -from sphinx_gallery.scrapers import figure_rst - from statemachine.contrib.diagram import DotGraphMachine from statemachine.factory import StateMachineMetaclass @@ -37,7 +35,9 @@ def generate_image(self, sm_class, original_path): return image_path def __call__(self, block, block_vars, gallery_conf): - # Find all PNG files in the directory of this example. + "Find all PNG files in the directory of this example." + from sphinx_gallery.scrapers import figure_rst + module = self._get_module(block_vars["src_file"]) if module is None: return "" diff --git a/tests/test_examples.py b/tests/test_examples.py index d7d3c1cf..2c97e996 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -1,3 +1,4 @@ +from inspect import iscoroutinefunction from pathlib import Path import pytest @@ -20,13 +21,18 @@ def pytest_generate_tests(metafunc): def example_file_wrapper(file_name): def execute_file_wrapper(): module = import_module_by_path(file_name.with_suffix("")) - main = getattr(module, "main", None) - if main: - main() + return getattr(module, "main", None) return execute_file_wrapper -def test_example(example_file_wrapper): +async def test_example(example_file_wrapper): """Import the example file so the module is executed""" - example_file_wrapper() + main = example_file_wrapper() + if main is None: + return + + if iscoroutinefunction(main): + await main() + else: + main() diff --git a/tests/test_listener.py b/tests/test_listener.py index 6496b3c7..d613d92f 100644 --- a/tests/test_listener.py +++ b/tests/test_listener.py @@ -1,3 +1,5 @@ +import pytest + EXPECTED_LOG_ADD = """Frodo on: draft--(add_job)-->draft Frodo enter: draft from add_job Frodo on: draft--(produce)-->producing @@ -66,7 +68,10 @@ def on_enter_state(self, target, event): sm = campaign_machine() - sm.add_observer(LogObserver("Frodo")) + with pytest.warns( + DeprecationWarning, match="Method `add_observer` has been renamed to `add_listener`." + ): + sm.add_observer(LogObserver("Frodo")) sm.add_job() sm.produce() diff --git a/tests/test_statemachine.py b/tests/test_statemachine.py index 7973cf7f..2ebf194e 100644 --- a/tests/test_statemachine.py +++ b/tests/test_statemachine.py @@ -434,27 +434,57 @@ def test_should_not_override_states_properties(campaign_machine): assert "State overriding is not allowed. Trying to add 'something else' to draft" in str(e) -def test_should_warn_if_model_already_has_attribute_and_binding_is_enabled( - campaign_machine_with_final_state, capsys -): - class Model: - state = "draft" +class TestWarnings: + def test_should_warn_if_model_already_has_attribute_and_binding_is_enabled( + self, campaign_machine_with_final_state, capsys + ): + class Model: + state = "draft" + + def produce(self): + return f"producing from {self.__class__.__name__!r}" + + model = Model() + + sm = campaign_machine_with_final_state(model) + with pytest.warns( + UserWarning, match="Attribute 'produce' already exists on Date: Sun, 7 Jul 2024 07:30:12 -0300 Subject: [PATCH 2/5] chore: Update pillow docs dependency (#462) --- poetry.lock | 158 +++++++++++++++++++++++++++---------------------- pyproject.toml | 2 +- 2 files changed, 89 insertions(+), 71 deletions(-) diff --git a/poetry.lock b/poetry.lock index d7e7ab10..caa6b5cf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -698,82 +698,100 @@ files = [ [[package]] name = "pillow" -version = "9.5.0" +version = "10.4.0" description = "Python Imaging Library (Fork)" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, - {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"}, - {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"}, - {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"}, - {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"}, - {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"}, - {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"}, - {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"}, - {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"}, - {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"}, - {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"}, - {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"}, - {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"}, - {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"}, - {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"}, - {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"}, - {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"}, - {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"}, - {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"}, - {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"}, - {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"}, - {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"}, - {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"}, - {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"}, - {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"}, - {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"}, - {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"}, - {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"}, - {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"}, - {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, - {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, + {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, + {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, + {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, + {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, + {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, + {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, + {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, + {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, + {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, + {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, + {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, + {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, + {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, + {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, + {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, + {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, + {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, + {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] [[package]] name = "platformdirs" @@ -1581,4 +1599,4 @@ diagrams = [] [metadata] lock-version = "2.0" python-versions = ">=3.7" -content-hash = "712589ce962aab4a02f587b0f46e18cb4630547545782a38669687dd4917919e" +content-hash = "78ef39b5fcbb5300dd17b8c7a68e0a3b7723f18d7c4e34e43b11d98516365ded" diff --git a/pyproject.toml b/pyproject.toml index 4cb9d4f9..44c16db8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ pytest-django = { version = "^4.8.0", python = ">3.8" } Sphinx = { version = "*", python = ">3.8" } myst-parser = { version = "*", python = ">3.8" } sphinx-gallery = { version = "*", python = ">3.8" } -pillow = "*" +pillow = { version ="*", python = ">3.8" } sphinx-autobuild = { version = "*", python = ">3.8" } furo = { version = "^2024.5.6", python = ">3.8" } sphinx-copybutton = { version = "^0.5.2", python = ">3.8" } From d308b47a3251e3e259b6c4b60544c7b7bf7ec02e Mon Sep 17 00:00:00 2001 From: Fernando Macedo Date: Thu, 11 Jul 2024 16:42:59 -0300 Subject: [PATCH 3/5] fix: Regression that causes an exception when registering listeners with unbounded callables. (#466) --- docs/releases/2.3.4.md | 8 ++++++++ docs/releases/index.md | 1 + statemachine/callbacks.py | 13 +++++++++---- statemachine/dispatcher.py | 18 +++++++++++++----- statemachine/statemachine.py | 19 +++++++++++++------ tests/test_listener.py | 27 +++++++++++++++++++++++++++ 6 files changed, 71 insertions(+), 15 deletions(-) create mode 100644 docs/releases/2.3.4.md diff --git a/docs/releases/2.3.4.md b/docs/releases/2.3.4.md new file mode 100644 index 00000000..45f23568 --- /dev/null +++ b/docs/releases/2.3.4.md @@ -0,0 +1,8 @@ +# StateMachine 2.3.4 + +*July 11, 2024* + + +## Bugfixes in 2.3.4 + +- Fixes [#465](https://github.com/fgmacedo/python-statemachine/issues/465) regression that caused exception when registering a listener with unbounded callbacks. diff --git a/docs/releases/index.md b/docs/releases/index.md index f0203a01..848d39ae 100644 --- a/docs/releases/index.md +++ b/docs/releases/index.md @@ -15,6 +15,7 @@ Below are release notes through StateMachine and its patch releases. ```{toctree} :maxdepth: 2 +2.3.4 2.3.3 2.3.2 2.3.1 diff --git a/statemachine/callbacks.py b/statemachine/callbacks.py index dfe943e6..43a022d9 100644 --- a/statemachine/callbacks.py +++ b/statemachine/callbacks.py @@ -3,6 +3,7 @@ from collections import defaultdict from collections import deque from enum import IntEnum +from enum import IntFlag from enum import auto from inspect import isawaitable from inspect import iscoroutinefunction @@ -26,10 +27,14 @@ class CallbackPriority(IntEnum): AFTER = 40 -class SpecReference(IntEnum): - NAME = 1 - CALLABLE = 2 - PROPERTY = 3 +class SpecReference(IntFlag): + NAME = auto() + CALLABLE = auto() + PROPERTY = auto() + + +SPECS_ALL = SpecReference.NAME | SpecReference.CALLABLE | SpecReference.PROPERTY +SPECS_SAFE = SpecReference.NAME class CallbackGroup(IntEnum): diff --git a/statemachine/dispatcher.py b/statemachine/dispatcher.py index f41288fe..7ca1b55c 100644 --- a/statemachine/dispatcher.py +++ b/statemachine/dispatcher.py @@ -8,8 +8,8 @@ from typing import Set from typing import Tuple -from statemachine.callbacks import SpecReference - +from .callbacks import SPECS_ALL +from .callbacks import SpecReference from .signature import SignatureAdapter if TYPE_CHECKING: @@ -54,10 +54,18 @@ def from_listeners(cls, listeners: Iterable["Listener"]) -> "Listeners": all_attrs = set().union(*(listener.all_attrs for listener in listeners)) return cls(listeners, all_attrs) - def resolve(self, specs: "CallbackSpecList", registry): + def resolve( + self, + specs: "CallbackSpecList", + registry, + allowed_references: SpecReference = SPECS_ALL, + ): found_convention_specs = specs.conventional_specs & self.all_attrs filtered_specs = [ - spec for spec in specs if not spec.is_convention or spec.func in found_convention_specs + spec + for spec in specs + if spec.reference in allowed_references + and (not spec.is_convention or spec.func in found_convention_specs) ] if not filtered_specs: return @@ -101,7 +109,7 @@ def _search_callable(self, spec) -> "Callable": if func is not None and func.__func__ is spec.func: return callable_method(spec.attr_name, func, config.resolver_id) - return callable_method(spec.func, spec.func, None) + return callable_method(spec.attr_name, spec.func, None) def _search_name(self, name) -> Generator["Callable", None, None]: for config in self.items: diff --git a/statemachine/statemachine.py b/statemachine/statemachine.py index a9f7061c..a2e750aa 100644 --- a/statemachine/statemachine.py +++ b/statemachine/statemachine.py @@ -9,11 +9,11 @@ from typing import Dict from typing import List -from statemachine.graph import iterate_states_and_transitions -from statemachine.utils import run_async_from_sync - +from .callbacks import SPECS_ALL +from .callbacks import SPECS_SAFE from .callbacks import CallbacksExecutor from .callbacks import CallbacksRegistry +from .callbacks import SpecReference from .dispatcher import Listener from .dispatcher import Listeners from .engines.async_ import AsyncEngine @@ -24,8 +24,10 @@ from .exceptions import InvalidStateValue from .exceptions import TransitionNotAllowed from .factory import StateMachineMetaclass +from .graph import iterate_states_and_transitions from .i18n import _ from .model import Model +from .utils import run_async_from_sync if TYPE_CHECKING: from .state import State @@ -177,8 +179,12 @@ def bind_events_to(self, *targets): continue setattr(target, event.name, trigger) - def _add_listener(self, listeners: "Listeners"): - register = partial(listeners.resolve, registry=self._callbacks_registry) + def _add_listener(self, listeners: "Listeners", allowed_references: SpecReference = SPECS_ALL): + register = partial( + listeners.resolve, + registry=self._callbacks_registry, + allowed_references=allowed_references, + ) for visited in iterate_states_and_transitions(self.states): register(visited._specs) @@ -228,7 +234,8 @@ def add_listener(self, *listeners): """ self._listeners.update({o: None for o in listeners}) return self._add_listener( - Listeners.from_listeners(Listener.from_obj(o) for o in listeners) + Listeners.from_listeners(Listener.from_obj(o) for o in listeners), + allowed_references=SPECS_SAFE, ) def _repr_html_(self): diff --git a/tests/test_listener.py b/tests/test_listener.py index d613d92f..1d27336e 100644 --- a/tests/test_listener.py +++ b/tests/test_listener.py @@ -1,5 +1,8 @@ import pytest +from statemachine.state import State +from statemachine.statemachine import StateMachine + EXPECTED_LOG_ADD = """Frodo on: draft--(add_job)-->draft Frodo enter: draft from add_job Frodo on: draft--(produce)-->producing @@ -78,3 +81,27 @@ def on_enter_state(self, target, event): captured = capsys.readouterr() assert captured.out == EXPECTED_LOG_ADD + + +def test_regression_456(): + class TestListener: + def __init__(self): + pass + + class MyMachine(StateMachine): + first = State("FIRST", initial=True) + + second = State("SECOND") + + first_selected = second.to(first) + + second_selected = first.to(second) + + @first.exit + def exit_first(self) -> None: + print("exit SLEEPING") + + m = MyMachine() + m.add_listener(TestListener()) + + m.send("second_selected") From f6e37cdf75636fa3650e90d6a0a3f21ff2bc16bb Mon Sep 17 00:00:00 2001 From: Fernando Macedo Date: Thu, 11 Jul 2024 16:49:17 -0300 Subject: [PATCH 4/5] chore: Updating dev dependencies --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index caa6b5cf..a41003dd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -286,13 +286,13 @@ files = [ [[package]] name = "django" -version = "5.0.6" +version = "5.0.7" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.10" files = [ - {file = "Django-5.0.6-py3-none-any.whl", hash = "sha256:8363ac062bb4ef7c3f12d078f6fa5d154031d129a15170a1066412af49d30905"}, - {file = "Django-5.0.6.tar.gz", hash = "sha256:ff1b61005004e476e0aeea47c7f79b85864c70124030e95146315396f1e7951f"}, + {file = "Django-5.0.7-py3-none-any.whl", hash = "sha256:f216510ace3de5de01329463a315a629f33480e893a9024fc93d8c32c22913da"}, + {file = "Django-5.0.7.tar.gz", hash = "sha256:bd4505cae0b9bd642313e8fb71810893df5dc2ffcacaa67a33af2d5cd61888f2"}, ] [package.dependencies] From 83760c72df7459ff48d88d7e87191c162855c86c Mon Sep 17 00:00:00 2001 From: Fernando Macedo Date: Thu, 11 Jul 2024 16:51:00 -0300 Subject: [PATCH 5/5] chore: Release 2.3.4 --- pyproject.toml | 2 +- statemachine/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 44c16db8..bb2e6265 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "python-statemachine" -version = "2.3.3" +version = "2.3.4" description = "Python Finite State Machines made easy." authors = ["Fernando Macedo "] maintainers = [ diff --git a/statemachine/__init__.py b/statemachine/__init__.py index afbe1e9e..c45708b8 100644 --- a/statemachine/__init__.py +++ b/statemachine/__init__.py @@ -3,6 +3,6 @@ __author__ = """Fernando Macedo""" __email__ = "fgmacedo@gmail.com" -__version__ = "2.3.3" +__version__ = "2.3.4" __all__ = ["StateMachine", "State"]