frequenz-dispatch 1.0.2__tar.gz → 1.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. frequenz_dispatch-1.0.3/PKG-INFO +225 -0
  2. frequenz_dispatch-1.0.3/README.md +155 -0
  3. frequenz_dispatch-1.0.3/RELEASE_NOTES.md +22 -0
  4. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/pyproject.toml +19 -19
  5. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/src/frequenz/dispatch/_bg_service.py +21 -8
  6. frequenz_dispatch-1.0.3/src/frequenz_dispatch.egg-info/PKG-INFO +225 -0
  7. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/src/frequenz_dispatch.egg-info/requires.txt +18 -18
  8. frequenz_dispatch-1.0.2/PKG-INFO +0 -136
  9. frequenz_dispatch-1.0.2/README.md +0 -66
  10. frequenz_dispatch-1.0.2/RELEASE_NOTES.md +0 -5
  11. frequenz_dispatch-1.0.2/src/frequenz_dispatch.egg-info/PKG-INFO +0 -136
  12. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/LICENSE +0 -0
  13. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/MANIFEST.in +0 -0
  14. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/setup.cfg +0 -0
  15. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/src/frequenz/dispatch/__init__.py +0 -0
  16. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/src/frequenz/dispatch/_actor_dispatcher.py +0 -0
  17. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/src/frequenz/dispatch/_dispatch.py +0 -0
  18. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/src/frequenz/dispatch/_dispatcher.py +0 -0
  19. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/src/frequenz/dispatch/_event.py +0 -0
  20. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/src/frequenz/dispatch/_merge_strategies.py +0 -0
  21. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/src/frequenz/dispatch/conftest.py +0 -0
  22. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/src/frequenz/dispatch/py.typed +0 -0
  23. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/src/frequenz_dispatch.egg-info/SOURCES.txt +0 -0
  24. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/src/frequenz_dispatch.egg-info/dependency_links.txt +0 -0
  25. {frequenz_dispatch-1.0.2 → frequenz_dispatch-1.0.3}/src/frequenz_dispatch.egg-info/top_level.txt +0 -0
@@ -0,0 +1,225 @@
1
+ Metadata-Version: 2.4
2
+ Name: frequenz-dispatch
3
+ Version: 1.0.3
4
+ Summary: A highlevel interface for the dispatch API
5
+ Author-email: Frequenz Energy-as-a-Service GmbH <floss@frequenz.com>
6
+ License: MIT
7
+ Project-URL: Documentation, https://frequenz-floss.github.io/frequenz-dispatch-python/
8
+ Project-URL: Changelog, https://github.com/frequenz-floss/frequenz-dispatch-python/releases
9
+ Project-URL: Issues, https://github.com/frequenz-floss/frequenz-dispatch-python/issues
10
+ Project-URL: Repository, https://github.com/frequenz-floss/frequenz-dispatch-python
11
+ Project-URL: Support, https://github.com/frequenz-floss/frequenz-dispatch-python/discussions/categories/support
12
+ Keywords: frequenz,python,actor,frequenz-dispatch,dispatch,highlevel,api
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: <4,>=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: typing-extensions<5.0.0,>=4.13.0
24
+ Requires-Dist: frequenz-sdk<1.0.0-rc2300,>=1.0.0-rc2100
25
+ Requires-Dist: frequenz-channels<2.0.0,>=1.6.1
26
+ Requires-Dist: frequenz-client-dispatch<2.0.0,>=0.11.3
27
+ Requires-Dist: frequenz-client-base<0.12.0,>=0.11.0
28
+ Provides-Extra: dev-flake8
29
+ Requires-Dist: flake8==7.3.0; extra == "dev-flake8"
30
+ Requires-Dist: flake8-docstrings==1.7.0; extra == "dev-flake8"
31
+ Requires-Dist: flake8-pyproject==1.2.4; extra == "dev-flake8"
32
+ Requires-Dist: pydoclint==0.8.3; extra == "dev-flake8"
33
+ Requires-Dist: pydocstyle==6.3.0; extra == "dev-flake8"
34
+ Provides-Extra: dev-formatting
35
+ Requires-Dist: black==25.12.0; extra == "dev-formatting"
36
+ Requires-Dist: isort==7.0.0; extra == "dev-formatting"
37
+ Provides-Extra: dev-mkdocs
38
+ Requires-Dist: black==25.12.0; extra == "dev-mkdocs"
39
+ Requires-Dist: Markdown==3.10.2; extra == "dev-mkdocs"
40
+ Requires-Dist: mike==2.1.3; extra == "dev-mkdocs"
41
+ Requires-Dist: mkdocs-gen-files==0.6.0; extra == "dev-mkdocs"
42
+ Requires-Dist: mkdocs-literate-nav==0.6.2; extra == "dev-mkdocs"
43
+ Requires-Dist: mkdocs-macros-plugin==1.5.0; extra == "dev-mkdocs"
44
+ Requires-Dist: mkdocs-material==9.7.3; extra == "dev-mkdocs"
45
+ Requires-Dist: mkdocstrings[python]==1.0.3; extra == "dev-mkdocs"
46
+ Requires-Dist: mkdocstrings-python==1.18.2; extra == "dev-mkdocs"
47
+ Requires-Dist: frequenz-repo-config[lib]==0.13.8; extra == "dev-mkdocs"
48
+ Provides-Extra: dev-mypy
49
+ Requires-Dist: mypy==1.19.1; extra == "dev-mypy"
50
+ Requires-Dist: grpc-stubs==1.53.0.6; extra == "dev-mypy"
51
+ Requires-Dist: types-Markdown==3.10.2.20260211; extra == "dev-mypy"
52
+ Requires-Dist: frequenz-dispatch[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-mypy"
53
+ Provides-Extra: dev-noxfile
54
+ Requires-Dist: uv==0.10.7; extra == "dev-noxfile"
55
+ Requires-Dist: nox==2025.11.12; extra == "dev-noxfile"
56
+ Requires-Dist: frequenz-repo-config[lib]==0.13.8; extra == "dev-noxfile"
57
+ Provides-Extra: dev-pylint
58
+ Requires-Dist: pylint==4.0.5; extra == "dev-pylint"
59
+ Requires-Dist: frequenz-dispatch[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-pylint"
60
+ Provides-Extra: dev-pytest
61
+ Requires-Dist: pytest==9.0.2; extra == "dev-pytest"
62
+ Requires-Dist: frequenz-repo-config[extra-lint-examples]==0.13.8; extra == "dev-pytest"
63
+ Requires-Dist: pytest-mock==3.15.1; extra == "dev-pytest"
64
+ Requires-Dist: pytest-asyncio==1.3.0; extra == "dev-pytest"
65
+ Requires-Dist: async-solipsism==0.8; extra == "dev-pytest"
66
+ Requires-Dist: time-machine==3.2.0; extra == "dev-pytest"
67
+ Provides-Extra: dev
68
+ Requires-Dist: frequenz-dispatch[dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]; extra == "dev"
69
+ Dynamic: license-file
70
+
71
+ # Dispatch Highlevel Interface
72
+
73
+ [![Build Status](https://github.com/frequenz-floss/frequenz-dispatch-python/actions/workflows/ci.yaml/badge.svg)](https://github.com/frequenz-floss/frequenz-dispatch-python/actions/workflows/ci.yaml)
74
+ [![PyPI Package](https://img.shields.io/pypi/v/frequenz-dispatch)](https://pypi.org/project/frequenz-dispatch/)
75
+ [![Docs](https://img.shields.io/badge/docs-latest-informational)](https://frequenz-floss.github.io/frequenz-dispatch-python/)
76
+
77
+ ## Introduction
78
+
79
+ The `frequenz-dispatch` library provides a high-level Python interface for
80
+ interacting with the Frequenz Dispatch API. This library enables you to
81
+ manage and monitor dispatch operations in microgrids, including lifecycle
82
+ events and running status changes of dispatch operations.
83
+
84
+ The main entry point is the [`Dispatcher`][dispatcher-class] class, which
85
+ provides methods for receiving dispatch lifecycle events and running status
86
+ updates, allowing you to build reactive applications that respond to dispatch
87
+ state changes.
88
+
89
+ ## Supported Platforms
90
+
91
+ The following platforms are officially supported (tested):
92
+
93
+ - **Python:** 3.11, 3.13
94
+ - **Operating System:** Ubuntu Linux 24.04
95
+ - **Architectures:** amd64, arm64
96
+
97
+ ## Installation
98
+
99
+ ### Using pip
100
+
101
+ You can install the package from PyPI:
102
+
103
+ ```bash
104
+ python3 -m pip install frequenz-dispatch
105
+ ```
106
+
107
+ ### Using pyproject.toml
108
+
109
+ Add the dependency to your `pyproject.toml` file:
110
+
111
+ ```toml
112
+ [project]
113
+ dependencies = [
114
+ "frequenz-dispatch >= 1.0.1, < 2",
115
+ ]
116
+ ```
117
+
118
+ > [!NOTE]
119
+ > We recommend pinning the dependency to the latest version for programs,
120
+ > like `"frequenz-dispatch == 1.0.1"`, and specifying a version range
121
+ > spanning one major version for libraries, like
122
+ > `"frequenz-dispatch >= 1.0.1, < 2"`. We follow [semver](https://semver.org/).
123
+
124
+ ## Quick Start
125
+
126
+ Here's a minimal example to get you started with lifecycle events:
127
+
128
+ ```python
129
+ import asyncio
130
+ import os
131
+
132
+ from frequenz.dispatch import Created, Deleted, Dispatcher, Updated
133
+
134
+ async def main() -> None:
135
+ url = os.getenv("DISPATCH_API_URL", "grpc://localhost:50051")
136
+ auth_key = os.getenv("DISPATCH_API_AUTH_KEY", "my-api-key")
137
+ sign_secret = os.getenv("DISPATCH_API_SIGN_SECRET")
138
+ microgrid_id = 1
139
+
140
+ async with Dispatcher(
141
+ microgrid_id=microgrid_id,
142
+ server_url=url,
143
+ auth_key=auth_key,
144
+ sign_secret=sign_secret,
145
+ ) as dispatcher:
146
+ events_receiver = dispatcher.new_lifecycle_events_receiver("MY_TYPE")
147
+
148
+ async for event in events_receiver:
149
+ match event:
150
+ case Created(dispatch):
151
+ print(f"Created: {dispatch}")
152
+ case Updated(dispatch):
153
+ print(f"Updated: {dispatch}")
154
+ case Deleted(dispatch):
155
+ print(f"Deleted: {dispatch}")
156
+
157
+ asyncio.run(main())
158
+ ```
159
+
160
+ The [`Dispatcher` class][dispatcher-class] provides two receiver methods:
161
+
162
+ * [`new_lifecycle_events_receiver()`][lifecycle-events]: Returns a receiver
163
+ that sends a message whenever a Dispatch is created, updated or deleted.
164
+ * [`new_running_state_event_receiver()`][running-status-change]: Returns a
165
+ receiver that sends a dispatch message whenever a dispatch is ready to be
166
+ executed according to the schedule or the running status of the dispatch
167
+ changed in a way that could potentially require the actor to start, stop or
168
+ reconfigure itself.
169
+
170
+ ### Example managing actors with dispatch events
171
+
172
+ ```python
173
+ import os
174
+ from datetime import timedelta
175
+ from unittest.mock import MagicMock
176
+
177
+ from frequenz.channels import Receiver
178
+ from frequenz.sdk.actor import Actor
179
+
180
+ from frequenz.dispatch import Dispatcher, DispatchInfo, MergeByType
181
+
182
+ async def create_actor(
183
+ dispatch: DispatchInfo, receiver: Receiver[DispatchInfo]
184
+ ) -> Actor:
185
+ return MagicMock(dispatch=dispatch, receiver=receiver)
186
+
187
+ async def run() -> None:
188
+ url = os.getenv(
189
+ "DISPATCH_API_URL", "grpc://dispatch.api.example.com:50051"
190
+ )
191
+ auth_key = os.getenv("DISPATCH_API_AUTH_KEY", "my-api-key")
192
+ sign_secret = os.getenv("DISPATCH_API_SIGN_SECRET")
193
+
194
+ microgrid_id = 1
195
+
196
+ async with Dispatcher(
197
+ microgrid_id=microgrid_id,
198
+ server_url=url,
199
+ auth_key=auth_key,
200
+ sign_secret=sign_secret,
201
+ ) as dispatcher:
202
+ await dispatcher.start_managing(
203
+ dispatch_type="EXAMPLE_TYPE",
204
+ actor_factory=create_actor,
205
+ merge_strategy=MergeByType(),
206
+ retry_interval=timedelta(seconds=10)
207
+ )
208
+
209
+ await dispatcher
210
+ ```
211
+
212
+ ## Documentation
213
+
214
+ For complete API documentation, examples, and advanced usage patterns, see
215
+ [the documentation][docs].
216
+
217
+ [dispatcher-class]: https://frequenz-floss.github.io/frequenz-dispatch-python/latest/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher
218
+ [lifecycle-events]: https://frequenz-floss.github.io/frequenz-dispatch-python/latest/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher.new_lifecycle_events_receiver
219
+ [running-status-change]: https://frequenz-floss.github.io/frequenz-dispatch-python/latest/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher.new_running_state_event_receiver
220
+ [docs]: https://frequenz-floss.github.io/frequenz-dispatch-python/latest/
221
+
222
+ ## Contributing
223
+
224
+ If you want to know how to build this project and contribute to it, please
225
+ check out the [Contributing Guide](CONTRIBUTING.md).
@@ -0,0 +1,155 @@
1
+ # Dispatch Highlevel Interface
2
+
3
+ [![Build Status](https://github.com/frequenz-floss/frequenz-dispatch-python/actions/workflows/ci.yaml/badge.svg)](https://github.com/frequenz-floss/frequenz-dispatch-python/actions/workflows/ci.yaml)
4
+ [![PyPI Package](https://img.shields.io/pypi/v/frequenz-dispatch)](https://pypi.org/project/frequenz-dispatch/)
5
+ [![Docs](https://img.shields.io/badge/docs-latest-informational)](https://frequenz-floss.github.io/frequenz-dispatch-python/)
6
+
7
+ ## Introduction
8
+
9
+ The `frequenz-dispatch` library provides a high-level Python interface for
10
+ interacting with the Frequenz Dispatch API. This library enables you to
11
+ manage and monitor dispatch operations in microgrids, including lifecycle
12
+ events and running status changes of dispatch operations.
13
+
14
+ The main entry point is the [`Dispatcher`][dispatcher-class] class, which
15
+ provides methods for receiving dispatch lifecycle events and running status
16
+ updates, allowing you to build reactive applications that respond to dispatch
17
+ state changes.
18
+
19
+ ## Supported Platforms
20
+
21
+ The following platforms are officially supported (tested):
22
+
23
+ - **Python:** 3.11, 3.13
24
+ - **Operating System:** Ubuntu Linux 24.04
25
+ - **Architectures:** amd64, arm64
26
+
27
+ ## Installation
28
+
29
+ ### Using pip
30
+
31
+ You can install the package from PyPI:
32
+
33
+ ```bash
34
+ python3 -m pip install frequenz-dispatch
35
+ ```
36
+
37
+ ### Using pyproject.toml
38
+
39
+ Add the dependency to your `pyproject.toml` file:
40
+
41
+ ```toml
42
+ [project]
43
+ dependencies = [
44
+ "frequenz-dispatch >= 1.0.1, < 2",
45
+ ]
46
+ ```
47
+
48
+ > [!NOTE]
49
+ > We recommend pinning the dependency to the latest version for programs,
50
+ > like `"frequenz-dispatch == 1.0.1"`, and specifying a version range
51
+ > spanning one major version for libraries, like
52
+ > `"frequenz-dispatch >= 1.0.1, < 2"`. We follow [semver](https://semver.org/).
53
+
54
+ ## Quick Start
55
+
56
+ Here's a minimal example to get you started with lifecycle events:
57
+
58
+ ```python
59
+ import asyncio
60
+ import os
61
+
62
+ from frequenz.dispatch import Created, Deleted, Dispatcher, Updated
63
+
64
+ async def main() -> None:
65
+ url = os.getenv("DISPATCH_API_URL", "grpc://localhost:50051")
66
+ auth_key = os.getenv("DISPATCH_API_AUTH_KEY", "my-api-key")
67
+ sign_secret = os.getenv("DISPATCH_API_SIGN_SECRET")
68
+ microgrid_id = 1
69
+
70
+ async with Dispatcher(
71
+ microgrid_id=microgrid_id,
72
+ server_url=url,
73
+ auth_key=auth_key,
74
+ sign_secret=sign_secret,
75
+ ) as dispatcher:
76
+ events_receiver = dispatcher.new_lifecycle_events_receiver("MY_TYPE")
77
+
78
+ async for event in events_receiver:
79
+ match event:
80
+ case Created(dispatch):
81
+ print(f"Created: {dispatch}")
82
+ case Updated(dispatch):
83
+ print(f"Updated: {dispatch}")
84
+ case Deleted(dispatch):
85
+ print(f"Deleted: {dispatch}")
86
+
87
+ asyncio.run(main())
88
+ ```
89
+
90
+ The [`Dispatcher` class][dispatcher-class] provides two receiver methods:
91
+
92
+ * [`new_lifecycle_events_receiver()`][lifecycle-events]: Returns a receiver
93
+ that sends a message whenever a Dispatch is created, updated or deleted.
94
+ * [`new_running_state_event_receiver()`][running-status-change]: Returns a
95
+ receiver that sends a dispatch message whenever a dispatch is ready to be
96
+ executed according to the schedule or the running status of the dispatch
97
+ changed in a way that could potentially require the actor to start, stop or
98
+ reconfigure itself.
99
+
100
+ ### Example managing actors with dispatch events
101
+
102
+ ```python
103
+ import os
104
+ from datetime import timedelta
105
+ from unittest.mock import MagicMock
106
+
107
+ from frequenz.channels import Receiver
108
+ from frequenz.sdk.actor import Actor
109
+
110
+ from frequenz.dispatch import Dispatcher, DispatchInfo, MergeByType
111
+
112
+ async def create_actor(
113
+ dispatch: DispatchInfo, receiver: Receiver[DispatchInfo]
114
+ ) -> Actor:
115
+ return MagicMock(dispatch=dispatch, receiver=receiver)
116
+
117
+ async def run() -> None:
118
+ url = os.getenv(
119
+ "DISPATCH_API_URL", "grpc://dispatch.api.example.com:50051"
120
+ )
121
+ auth_key = os.getenv("DISPATCH_API_AUTH_KEY", "my-api-key")
122
+ sign_secret = os.getenv("DISPATCH_API_SIGN_SECRET")
123
+
124
+ microgrid_id = 1
125
+
126
+ async with Dispatcher(
127
+ microgrid_id=microgrid_id,
128
+ server_url=url,
129
+ auth_key=auth_key,
130
+ sign_secret=sign_secret,
131
+ ) as dispatcher:
132
+ await dispatcher.start_managing(
133
+ dispatch_type="EXAMPLE_TYPE",
134
+ actor_factory=create_actor,
135
+ merge_strategy=MergeByType(),
136
+ retry_interval=timedelta(seconds=10)
137
+ )
138
+
139
+ await dispatcher
140
+ ```
141
+
142
+ ## Documentation
143
+
144
+ For complete API documentation, examples, and advanced usage patterns, see
145
+ [the documentation][docs].
146
+
147
+ [dispatcher-class]: https://frequenz-floss.github.io/frequenz-dispatch-python/latest/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher
148
+ [lifecycle-events]: https://frequenz-floss.github.io/frequenz-dispatch-python/latest/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher.new_lifecycle_events_receiver
149
+ [running-status-change]: https://frequenz-floss.github.io/frequenz-dispatch-python/latest/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher.new_running_state_event_receiver
150
+ [docs]: https://frequenz-floss.github.io/frequenz-dispatch-python/latest/
151
+
152
+ ## Contributing
153
+
154
+ If you want to know how to build this project and contribute to it, please
155
+ check out the [Contributing Guide](CONTRIBUTING.md).
@@ -0,0 +1,22 @@
1
+ # Dispatch Highlevel Interface Release Notes
2
+
3
+ ## Summary
4
+
5
+ <!-- Here goes a general summary of what this release is about -->
6
+
7
+ ## Upgrading
8
+
9
+ <!-- Here goes notes on how to upgrade from previous versions, including deprecations and what they should be replaced with -->
10
+
11
+ ## New Features
12
+
13
+ <!-- Here goes the main new features and examples or instructions on how to use them -->
14
+
15
+ ## Bug Fixes
16
+
17
+ <!-- Here goes notable bug fixes that are worth a special mention or explanation -->
18
+
19
+ * Fixed bugs that could cause a dispatch actor not to stop when an update
20
+ arrives exactly at the moment a dispatch window closes. Also fixed a
21
+ related issue that could cause incorrect event ordering when multiple
22
+ dispatches are updated concurrently.
@@ -4,8 +4,8 @@
4
4
  [build-system]
5
5
  requires = [
6
6
  "setuptools == 80.9.0",
7
- "setuptools_scm[toml] == 9.2.0",
8
- "frequenz-repo-config[lib] == 0.13.5",
7
+ "setuptools_scm[toml] == 9.2.2",
8
+ "frequenz-repo-config[lib] == 0.13.8",
9
9
  ]
10
10
  build-backend = "setuptools.build_meta"
11
11
 
@@ -53,48 +53,48 @@ email = "floss@frequenz.com"
53
53
  dev-flake8 = [
54
54
  "flake8 == 7.3.0",
55
55
  "flake8-docstrings == 1.7.0",
56
- "flake8-pyproject == 1.2.3", # For reading the flake8 config from pyproject.toml
56
+ "flake8-pyproject == 1.2.4", # For reading the flake8 config from pyproject.toml
57
57
  "pydoclint == 0.8.3",
58
58
  "pydocstyle == 6.3.0",
59
59
  ]
60
- dev-formatting = ["black == 25.11.0", "isort == 6.0.1"]
60
+ dev-formatting = ["black == 25.12.0", "isort == 7.0.0"]
61
61
  dev-mkdocs = [
62
- "black == 25.11.0",
63
- "Markdown==3.10",
62
+ "black == 25.12.0",
63
+ "Markdown==3.10.2",
64
64
  "mike == 2.1.3",
65
- "mkdocs-gen-files == 0.5.0",
65
+ "mkdocs-gen-files == 0.6.0",
66
66
  "mkdocs-literate-nav == 0.6.2",
67
67
  "mkdocs-macros-plugin == 1.5.0",
68
- "mkdocs-material == 9.7.0",
69
- "mkdocstrings[python] == 0.30.1",
68
+ "mkdocs-material == 9.7.3",
69
+ "mkdocstrings[python] == 1.0.3",
70
70
  "mkdocstrings-python == 1.18.2",
71
- "frequenz-repo-config[lib] == 0.13.5",
71
+ "frequenz-repo-config[lib] == 0.13.8",
72
72
  ]
73
73
  dev-mypy = [
74
- "mypy == 1.19.0",
74
+ "mypy == 1.19.1",
75
75
  # This dependency introduces breaking changes in patch releases
76
76
  "grpc-stubs == 1.53.0.6",
77
- "types-Markdown == 3.10.0.20251106",
77
+ "types-Markdown == 3.10.2.20260211",
78
78
  # For checking the noxfile, docs/ script, and tests
79
79
  "frequenz-dispatch[dev-mkdocs,dev-noxfile,dev-pytest]",
80
80
  ]
81
81
  dev-noxfile = [
82
- "uv == 0.9.13",
82
+ "uv == 0.10.7",
83
83
  "nox == 2025.11.12",
84
- "frequenz-repo-config[lib] == 0.13.5",
84
+ "frequenz-repo-config[lib] == 0.13.8",
85
85
  ]
86
86
  dev-pylint = [
87
- "pylint == 3.3.8",
87
+ "pylint == 4.0.5",
88
88
  # For checking the noxfile, docs/ script, and tests
89
89
  "frequenz-dispatch[dev-mkdocs,dev-noxfile,dev-pytest]",
90
90
  ]
91
91
  dev-pytest = [
92
- "pytest == 8.4.2",
93
- "frequenz-repo-config[extra-lint-examples] == 0.13.5",
92
+ "pytest == 9.0.2",
93
+ "frequenz-repo-config[extra-lint-examples] == 0.13.8",
94
94
  "pytest-mock == 3.15.1",
95
- "pytest-asyncio == 1.2.0",
95
+ "pytest-asyncio == 1.3.0",
96
96
  "async-solipsism == 0.8",
97
- "time-machine == 2.19.0",
97
+ "time-machine == 3.2.0",
98
98
  ]
99
99
  dev = [
100
100
  "frequenz-dispatch[dev-mkdocs,dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]",
@@ -13,7 +13,7 @@ from collections.abc import Mapping
13
13
  from contextlib import closing
14
14
  from dataclasses import dataclass, field
15
15
  from datetime import datetime, timedelta, timezone
16
- from heapq import heappop, heappush
16
+ from heapq import heapify, heappop, heappush
17
17
 
18
18
  import grpc.aio
19
19
  from frequenz.channels import Broadcast, Receiver, select, selected_from
@@ -228,7 +228,7 @@ class DispatchScheduler(BackgroundService):
228
228
  """Start the background service."""
229
229
  self._tasks.add(asyncio.create_task(self._run()))
230
230
 
231
- async def _run(self) -> None:
231
+ async def _run(self) -> None: # pylint: disable=too-many-branches
232
232
  """Run the background service."""
233
233
  _logger.info(
234
234
  "Starting dispatching background service for microgrid %s",
@@ -486,11 +486,19 @@ class DispatchScheduler(BackgroundService):
486
486
  # Dispatch was updated
487
487
  elif dispatch and old_dispatch:
488
488
  # Remove potentially existing scheduled event
489
- self._remove_scheduled(old_dispatch)
489
+ removed = self._remove_scheduled(old_dispatch)
490
490
 
491
491
  # Check if the change requires an immediate notification
492
492
  if self._update_changed_running_state(dispatch, old_dispatch):
493
493
  await self._send_running_state_change(dispatch)
494
+ elif removed is not None and removed.priority == 1 and not dispatch.started:
495
+ # priority == 1 means a stop event (see QueueItem.__init__).
496
+ # If we removed a pending stop event and the dispatch is no
497
+ # longer started, the update arrived exactly at the stop
498
+ # boundary. The timer would have delivered the stop event, but
499
+ # _remove_scheduled consumed it first. Send the notification
500
+ # here so the actor is not left running past the window end.
501
+ await self._send_running_state_change(dispatch)
494
502
 
495
503
  if dispatch.started:
496
504
  self._schedule_stop(dispatch)
@@ -507,21 +515,26 @@ class DispatchScheduler(BackgroundService):
507
515
  timer.reset(interval=due_at - datetime.now(timezone.utc))
508
516
  _logger.debug("Next event scheduled at %s", self._scheduled_events[0].time)
509
517
 
510
- def _remove_scheduled(self, dispatch: Dispatch) -> bool:
518
+ def _remove_scheduled(self, dispatch: Dispatch) -> "QueueItem | None":
511
519
  """Remove a dispatch from the scheduled events.
512
520
 
513
521
  Args:
514
522
  dispatch: The dispatch to remove.
515
523
 
516
524
  Returns:
517
- True if the dispatch was found and removed, False otherwise.
525
+ The removed queue item, or None if not found.
518
526
  """
519
527
  for idx, item in enumerate(self._scheduled_events):
520
528
  if dispatch.id == item.dispatch.id:
521
529
  self._scheduled_events.pop(idx)
522
- return True
523
-
524
- return False
530
+ # heappop() only removes the root (index 0) and does not accept
531
+ # an index argument, so we use list.pop(idx) instead. After
532
+ # removing an arbitrary element the heap property is broken and
533
+ # must be restored explicitly.
534
+ heapify(self._scheduled_events)
535
+ return item
536
+
537
+ return None
525
538
 
526
539
  def _schedule_start(self, dispatch: Dispatch) -> None:
527
540
  """Schedule a dispatch to start.
@@ -0,0 +1,225 @@
1
+ Metadata-Version: 2.4
2
+ Name: frequenz-dispatch
3
+ Version: 1.0.3
4
+ Summary: A highlevel interface for the dispatch API
5
+ Author-email: Frequenz Energy-as-a-Service GmbH <floss@frequenz.com>
6
+ License: MIT
7
+ Project-URL: Documentation, https://frequenz-floss.github.io/frequenz-dispatch-python/
8
+ Project-URL: Changelog, https://github.com/frequenz-floss/frequenz-dispatch-python/releases
9
+ Project-URL: Issues, https://github.com/frequenz-floss/frequenz-dispatch-python/issues
10
+ Project-URL: Repository, https://github.com/frequenz-floss/frequenz-dispatch-python
11
+ Project-URL: Support, https://github.com/frequenz-floss/frequenz-dispatch-python/discussions/categories/support
12
+ Keywords: frequenz,python,actor,frequenz-dispatch,dispatch,highlevel,api
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: <4,>=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: typing-extensions<5.0.0,>=4.13.0
24
+ Requires-Dist: frequenz-sdk<1.0.0-rc2300,>=1.0.0-rc2100
25
+ Requires-Dist: frequenz-channels<2.0.0,>=1.6.1
26
+ Requires-Dist: frequenz-client-dispatch<2.0.0,>=0.11.3
27
+ Requires-Dist: frequenz-client-base<0.12.0,>=0.11.0
28
+ Provides-Extra: dev-flake8
29
+ Requires-Dist: flake8==7.3.0; extra == "dev-flake8"
30
+ Requires-Dist: flake8-docstrings==1.7.0; extra == "dev-flake8"
31
+ Requires-Dist: flake8-pyproject==1.2.4; extra == "dev-flake8"
32
+ Requires-Dist: pydoclint==0.8.3; extra == "dev-flake8"
33
+ Requires-Dist: pydocstyle==6.3.0; extra == "dev-flake8"
34
+ Provides-Extra: dev-formatting
35
+ Requires-Dist: black==25.12.0; extra == "dev-formatting"
36
+ Requires-Dist: isort==7.0.0; extra == "dev-formatting"
37
+ Provides-Extra: dev-mkdocs
38
+ Requires-Dist: black==25.12.0; extra == "dev-mkdocs"
39
+ Requires-Dist: Markdown==3.10.2; extra == "dev-mkdocs"
40
+ Requires-Dist: mike==2.1.3; extra == "dev-mkdocs"
41
+ Requires-Dist: mkdocs-gen-files==0.6.0; extra == "dev-mkdocs"
42
+ Requires-Dist: mkdocs-literate-nav==0.6.2; extra == "dev-mkdocs"
43
+ Requires-Dist: mkdocs-macros-plugin==1.5.0; extra == "dev-mkdocs"
44
+ Requires-Dist: mkdocs-material==9.7.3; extra == "dev-mkdocs"
45
+ Requires-Dist: mkdocstrings[python]==1.0.3; extra == "dev-mkdocs"
46
+ Requires-Dist: mkdocstrings-python==1.18.2; extra == "dev-mkdocs"
47
+ Requires-Dist: frequenz-repo-config[lib]==0.13.8; extra == "dev-mkdocs"
48
+ Provides-Extra: dev-mypy
49
+ Requires-Dist: mypy==1.19.1; extra == "dev-mypy"
50
+ Requires-Dist: grpc-stubs==1.53.0.6; extra == "dev-mypy"
51
+ Requires-Dist: types-Markdown==3.10.2.20260211; extra == "dev-mypy"
52
+ Requires-Dist: frequenz-dispatch[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-mypy"
53
+ Provides-Extra: dev-noxfile
54
+ Requires-Dist: uv==0.10.7; extra == "dev-noxfile"
55
+ Requires-Dist: nox==2025.11.12; extra == "dev-noxfile"
56
+ Requires-Dist: frequenz-repo-config[lib]==0.13.8; extra == "dev-noxfile"
57
+ Provides-Extra: dev-pylint
58
+ Requires-Dist: pylint==4.0.5; extra == "dev-pylint"
59
+ Requires-Dist: frequenz-dispatch[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-pylint"
60
+ Provides-Extra: dev-pytest
61
+ Requires-Dist: pytest==9.0.2; extra == "dev-pytest"
62
+ Requires-Dist: frequenz-repo-config[extra-lint-examples]==0.13.8; extra == "dev-pytest"
63
+ Requires-Dist: pytest-mock==3.15.1; extra == "dev-pytest"
64
+ Requires-Dist: pytest-asyncio==1.3.0; extra == "dev-pytest"
65
+ Requires-Dist: async-solipsism==0.8; extra == "dev-pytest"
66
+ Requires-Dist: time-machine==3.2.0; extra == "dev-pytest"
67
+ Provides-Extra: dev
68
+ Requires-Dist: frequenz-dispatch[dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]; extra == "dev"
69
+ Dynamic: license-file
70
+
71
+ # Dispatch Highlevel Interface
72
+
73
+ [![Build Status](https://github.com/frequenz-floss/frequenz-dispatch-python/actions/workflows/ci.yaml/badge.svg)](https://github.com/frequenz-floss/frequenz-dispatch-python/actions/workflows/ci.yaml)
74
+ [![PyPI Package](https://img.shields.io/pypi/v/frequenz-dispatch)](https://pypi.org/project/frequenz-dispatch/)
75
+ [![Docs](https://img.shields.io/badge/docs-latest-informational)](https://frequenz-floss.github.io/frequenz-dispatch-python/)
76
+
77
+ ## Introduction
78
+
79
+ The `frequenz-dispatch` library provides a high-level Python interface for
80
+ interacting with the Frequenz Dispatch API. This library enables you to
81
+ manage and monitor dispatch operations in microgrids, including lifecycle
82
+ events and running status changes of dispatch operations.
83
+
84
+ The main entry point is the [`Dispatcher`][dispatcher-class] class, which
85
+ provides methods for receiving dispatch lifecycle events and running status
86
+ updates, allowing you to build reactive applications that respond to dispatch
87
+ state changes.
88
+
89
+ ## Supported Platforms
90
+
91
+ The following platforms are officially supported (tested):
92
+
93
+ - **Python:** 3.11, 3.13
94
+ - **Operating System:** Ubuntu Linux 24.04
95
+ - **Architectures:** amd64, arm64
96
+
97
+ ## Installation
98
+
99
+ ### Using pip
100
+
101
+ You can install the package from PyPI:
102
+
103
+ ```bash
104
+ python3 -m pip install frequenz-dispatch
105
+ ```
106
+
107
+ ### Using pyproject.toml
108
+
109
+ Add the dependency to your `pyproject.toml` file:
110
+
111
+ ```toml
112
+ [project]
113
+ dependencies = [
114
+ "frequenz-dispatch >= 1.0.1, < 2",
115
+ ]
116
+ ```
117
+
118
+ > [!NOTE]
119
+ > We recommend pinning the dependency to the latest version for programs,
120
+ > like `"frequenz-dispatch == 1.0.1"`, and specifying a version range
121
+ > spanning one major version for libraries, like
122
+ > `"frequenz-dispatch >= 1.0.1, < 2"`. We follow [semver](https://semver.org/).
123
+
124
+ ## Quick Start
125
+
126
+ Here's a minimal example to get you started with lifecycle events:
127
+
128
+ ```python
129
+ import asyncio
130
+ import os
131
+
132
+ from frequenz.dispatch import Created, Deleted, Dispatcher, Updated
133
+
134
+ async def main() -> None:
135
+ url = os.getenv("DISPATCH_API_URL", "grpc://localhost:50051")
136
+ auth_key = os.getenv("DISPATCH_API_AUTH_KEY", "my-api-key")
137
+ sign_secret = os.getenv("DISPATCH_API_SIGN_SECRET")
138
+ microgrid_id = 1
139
+
140
+ async with Dispatcher(
141
+ microgrid_id=microgrid_id,
142
+ server_url=url,
143
+ auth_key=auth_key,
144
+ sign_secret=sign_secret,
145
+ ) as dispatcher:
146
+ events_receiver = dispatcher.new_lifecycle_events_receiver("MY_TYPE")
147
+
148
+ async for event in events_receiver:
149
+ match event:
150
+ case Created(dispatch):
151
+ print(f"Created: {dispatch}")
152
+ case Updated(dispatch):
153
+ print(f"Updated: {dispatch}")
154
+ case Deleted(dispatch):
155
+ print(f"Deleted: {dispatch}")
156
+
157
+ asyncio.run(main())
158
+ ```
159
+
160
+ The [`Dispatcher` class][dispatcher-class] provides two receiver methods:
161
+
162
+ * [`new_lifecycle_events_receiver()`][lifecycle-events]: Returns a receiver
163
+ that sends a message whenever a Dispatch is created, updated or deleted.
164
+ * [`new_running_state_event_receiver()`][running-status-change]: Returns a
165
+ receiver that sends a dispatch message whenever a dispatch is ready to be
166
+ executed according to the schedule or the running status of the dispatch
167
+ changed in a way that could potentially require the actor to start, stop or
168
+ reconfigure itself.
169
+
170
+ ### Example managing actors with dispatch events
171
+
172
+ ```python
173
+ import os
174
+ from datetime import timedelta
175
+ from unittest.mock import MagicMock
176
+
177
+ from frequenz.channels import Receiver
178
+ from frequenz.sdk.actor import Actor
179
+
180
+ from frequenz.dispatch import Dispatcher, DispatchInfo, MergeByType
181
+
182
+ async def create_actor(
183
+ dispatch: DispatchInfo, receiver: Receiver[DispatchInfo]
184
+ ) -> Actor:
185
+ return MagicMock(dispatch=dispatch, receiver=receiver)
186
+
187
+ async def run() -> None:
188
+ url = os.getenv(
189
+ "DISPATCH_API_URL", "grpc://dispatch.api.example.com:50051"
190
+ )
191
+ auth_key = os.getenv("DISPATCH_API_AUTH_KEY", "my-api-key")
192
+ sign_secret = os.getenv("DISPATCH_API_SIGN_SECRET")
193
+
194
+ microgrid_id = 1
195
+
196
+ async with Dispatcher(
197
+ microgrid_id=microgrid_id,
198
+ server_url=url,
199
+ auth_key=auth_key,
200
+ sign_secret=sign_secret,
201
+ ) as dispatcher:
202
+ await dispatcher.start_managing(
203
+ dispatch_type="EXAMPLE_TYPE",
204
+ actor_factory=create_actor,
205
+ merge_strategy=MergeByType(),
206
+ retry_interval=timedelta(seconds=10)
207
+ )
208
+
209
+ await dispatcher
210
+ ```
211
+
212
+ ## Documentation
213
+
214
+ For complete API documentation, examples, and advanced usage patterns, see
215
+ [the documentation][docs].
216
+
217
+ [dispatcher-class]: https://frequenz-floss.github.io/frequenz-dispatch-python/latest/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher
218
+ [lifecycle-events]: https://frequenz-floss.github.io/frequenz-dispatch-python/latest/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher.new_lifecycle_events_receiver
219
+ [running-status-change]: https://frequenz-floss.github.io/frequenz-dispatch-python/latest/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher.new_running_state_event_receiver
220
+ [docs]: https://frequenz-floss.github.io/frequenz-dispatch-python/latest/
221
+
222
+ ## Contributing
223
+
224
+ If you want to know how to build this project and contribute to it, please
225
+ check out the [Contributing Guide](CONTRIBUTING.md).
@@ -10,45 +10,45 @@ frequenz-dispatch[dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-
10
10
  [dev-flake8]
11
11
  flake8==7.3.0
12
12
  flake8-docstrings==1.7.0
13
- flake8-pyproject==1.2.3
13
+ flake8-pyproject==1.2.4
14
14
  pydoclint==0.8.3
15
15
  pydocstyle==6.3.0
16
16
 
17
17
  [dev-formatting]
18
- black==25.11.0
19
- isort==6.0.1
18
+ black==25.12.0
19
+ isort==7.0.0
20
20
 
21
21
  [dev-mkdocs]
22
- black==25.11.0
23
- Markdown==3.10
22
+ black==25.12.0
23
+ Markdown==3.10.2
24
24
  mike==2.1.3
25
- mkdocs-gen-files==0.5.0
25
+ mkdocs-gen-files==0.6.0
26
26
  mkdocs-literate-nav==0.6.2
27
27
  mkdocs-macros-plugin==1.5.0
28
- mkdocs-material==9.7.0
29
- mkdocstrings[python]==0.30.1
28
+ mkdocs-material==9.7.3
29
+ mkdocstrings[python]==1.0.3
30
30
  mkdocstrings-python==1.18.2
31
- frequenz-repo-config[lib]==0.13.5
31
+ frequenz-repo-config[lib]==0.13.8
32
32
 
33
33
  [dev-mypy]
34
- mypy==1.19.0
34
+ mypy==1.19.1
35
35
  grpc-stubs==1.53.0.6
36
- types-Markdown==3.10.0.20251106
36
+ types-Markdown==3.10.2.20260211
37
37
  frequenz-dispatch[dev-mkdocs,dev-noxfile,dev-pytest]
38
38
 
39
39
  [dev-noxfile]
40
- uv==0.9.13
40
+ uv==0.10.7
41
41
  nox==2025.11.12
42
- frequenz-repo-config[lib]==0.13.5
42
+ frequenz-repo-config[lib]==0.13.8
43
43
 
44
44
  [dev-pylint]
45
- pylint==3.3.8
45
+ pylint==4.0.5
46
46
  frequenz-dispatch[dev-mkdocs,dev-noxfile,dev-pytest]
47
47
 
48
48
  [dev-pytest]
49
- pytest==8.4.2
50
- frequenz-repo-config[extra-lint-examples]==0.13.5
49
+ pytest==9.0.2
50
+ frequenz-repo-config[extra-lint-examples]==0.13.8
51
51
  pytest-mock==3.15.1
52
- pytest-asyncio==1.2.0
52
+ pytest-asyncio==1.3.0
53
53
  async-solipsism==0.8
54
- time-machine==2.19.0
54
+ time-machine==3.2.0
@@ -1,136 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: frequenz-dispatch
3
- Version: 1.0.2
4
- Summary: A highlevel interface for the dispatch API
5
- Author-email: Frequenz Energy-as-a-Service GmbH <floss@frequenz.com>
6
- License: MIT
7
- Project-URL: Documentation, https://frequenz-floss.github.io/frequenz-dispatch-python/
8
- Project-URL: Changelog, https://github.com/frequenz-floss/frequenz-dispatch-python/releases
9
- Project-URL: Issues, https://github.com/frequenz-floss/frequenz-dispatch-python/issues
10
- Project-URL: Repository, https://github.com/frequenz-floss/frequenz-dispatch-python
11
- Project-URL: Support, https://github.com/frequenz-floss/frequenz-dispatch-python/discussions/categories/support
12
- Keywords: frequenz,python,actor,frequenz-dispatch,dispatch,highlevel,api
13
- Classifier: Development Status :: 3 - Alpha
14
- Classifier: Intended Audience :: Developers
15
- Classifier: License :: OSI Approved :: MIT License
16
- Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3 :: Only
18
- Classifier: Topic :: Software Development :: Libraries
19
- Classifier: Typing :: Typed
20
- Requires-Python: <4,>=3.11
21
- Description-Content-Type: text/markdown
22
- License-File: LICENSE
23
- Requires-Dist: typing-extensions<5.0.0,>=4.13.0
24
- Requires-Dist: frequenz-sdk<1.0.0-rc2300,>=1.0.0-rc2100
25
- Requires-Dist: frequenz-channels<2.0.0,>=1.6.1
26
- Requires-Dist: frequenz-client-dispatch<2.0.0,>=0.11.3
27
- Requires-Dist: frequenz-client-base<0.12.0,>=0.11.0
28
- Provides-Extra: dev-flake8
29
- Requires-Dist: flake8==7.3.0; extra == "dev-flake8"
30
- Requires-Dist: flake8-docstrings==1.7.0; extra == "dev-flake8"
31
- Requires-Dist: flake8-pyproject==1.2.3; extra == "dev-flake8"
32
- Requires-Dist: pydoclint==0.8.3; extra == "dev-flake8"
33
- Requires-Dist: pydocstyle==6.3.0; extra == "dev-flake8"
34
- Provides-Extra: dev-formatting
35
- Requires-Dist: black==25.11.0; extra == "dev-formatting"
36
- Requires-Dist: isort==6.0.1; extra == "dev-formatting"
37
- Provides-Extra: dev-mkdocs
38
- Requires-Dist: black==25.11.0; extra == "dev-mkdocs"
39
- Requires-Dist: Markdown==3.10; extra == "dev-mkdocs"
40
- Requires-Dist: mike==2.1.3; extra == "dev-mkdocs"
41
- Requires-Dist: mkdocs-gen-files==0.5.0; extra == "dev-mkdocs"
42
- Requires-Dist: mkdocs-literate-nav==0.6.2; extra == "dev-mkdocs"
43
- Requires-Dist: mkdocs-macros-plugin==1.5.0; extra == "dev-mkdocs"
44
- Requires-Dist: mkdocs-material==9.7.0; extra == "dev-mkdocs"
45
- Requires-Dist: mkdocstrings[python]==0.30.1; extra == "dev-mkdocs"
46
- Requires-Dist: mkdocstrings-python==1.18.2; extra == "dev-mkdocs"
47
- Requires-Dist: frequenz-repo-config[lib]==0.13.5; extra == "dev-mkdocs"
48
- Provides-Extra: dev-mypy
49
- Requires-Dist: mypy==1.19.0; extra == "dev-mypy"
50
- Requires-Dist: grpc-stubs==1.53.0.6; extra == "dev-mypy"
51
- Requires-Dist: types-Markdown==3.10.0.20251106; extra == "dev-mypy"
52
- Requires-Dist: frequenz-dispatch[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-mypy"
53
- Provides-Extra: dev-noxfile
54
- Requires-Dist: uv==0.9.13; extra == "dev-noxfile"
55
- Requires-Dist: nox==2025.11.12; extra == "dev-noxfile"
56
- Requires-Dist: frequenz-repo-config[lib]==0.13.5; extra == "dev-noxfile"
57
- Provides-Extra: dev-pylint
58
- Requires-Dist: pylint==3.3.8; extra == "dev-pylint"
59
- Requires-Dist: frequenz-dispatch[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-pylint"
60
- Provides-Extra: dev-pytest
61
- Requires-Dist: pytest==8.4.2; extra == "dev-pytest"
62
- Requires-Dist: frequenz-repo-config[extra-lint-examples]==0.13.5; extra == "dev-pytest"
63
- Requires-Dist: pytest-mock==3.15.1; extra == "dev-pytest"
64
- Requires-Dist: pytest-asyncio==1.2.0; extra == "dev-pytest"
65
- Requires-Dist: async-solipsism==0.8; extra == "dev-pytest"
66
- Requires-Dist: time-machine==2.19.0; extra == "dev-pytest"
67
- Provides-Extra: dev
68
- Requires-Dist: frequenz-dispatch[dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]; extra == "dev"
69
- Dynamic: license-file
70
-
71
- # Dispatch Highlevel Interface
72
-
73
- [![Build Status](https://github.com/frequenz-floss/frequenz-dispatch-python/actions/workflows/ci.yaml/badge.svg)](https://github.com/frequenz-floss/frequenz-dispatch-python/actions/workflows/ci.yaml)
74
- [![PyPI Package](https://img.shields.io/pypi/v/frequenz-dispatch)](https://pypi.org/project/frequenz-dispatch/)
75
- [![Docs](https://img.shields.io/badge/docs-latest-informational)](https://frequenz-floss.github.io/frequenz-dispatch-python/)
76
-
77
- ## Introduction
78
-
79
- A highlevel interface for the dispatch API.
80
-
81
- See [the documentation](https://frequenz-floss.github.io/frequenz-dispatch-python/v0.1/reference/frequenz/dispatch) for more information.
82
-
83
- ## Usage
84
-
85
- The [`Dispatcher` class](https://frequenz-floss.github.io/frequenz-dispatch-python/v0.1/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher), the main entry point for the API, provides two channels:
86
-
87
- * [Lifecycle events](https://frequenz-floss.github.io/frequenz-dispatch-python/v0.1/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher.lifecycle_events): A channel that sends a message whenever a [Dispatch][frequenz.dispatch.Dispatch] is created, updated or deleted.
88
- * [Running status change](https://frequenz-floss.github.io/frequenz-dispatch-python/v0.1/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher.running_status_change): Sends a dispatch message whenever a dispatch is ready to be executed according to the schedule or the running status of the dispatch changed in a way that could potentially require the actor to start, stop or reconfigure itself.
89
-
90
- ### Example using the running status change channel
91
-
92
- ```python
93
- import os
94
- from unittest.mock import MagicMock
95
- from datetime import timedelta
96
-
97
- from frequenz.dispatch import Dispatcher, DispatchInfo, MergeByType
98
-
99
- async def create_actor(dispatch: DispatchInfo, receiver: Receiver[DispatchInfo]) -> Actor:
100
- return MagicMock(dispatch=dispatch, receiver=receiver)
101
-
102
- async def run():
103
- url = os.getenv("DISPATCH_API_URL", "grpc://dispatch.url.goes.here.example.com")
104
- auth_key = os.getenv("DISPATCH_API_AUTH_KEY", "some-key")
105
- sign_secret = os.getenv("DISPATCH_API_SIGN_SECRET")
106
-
107
- microgrid_id = 1
108
-
109
- async with Dispatcher(
110
- microgrid_id=microgrid_id,
111
- server_url=url,
112
- auth_key=auth_key,
113
- sign_secret=sign_secret,
114
- ) as dispatcher:
115
- await dispatcher.start_managing(
116
- dispatch_type="EXAMPLE_TYPE",
117
- actor_factory=create_actor,
118
- merge_strategy=MergeByType(),
119
- retry_interval=timedelta(seconds=10)
120
- )
121
-
122
- await dispatcher
123
- ```
124
-
125
- ## Supported Platforms
126
-
127
- The following platforms are officially supported (tested):
128
-
129
- - **Python:** 3.11
130
- - **Operating System:** Ubuntu Linux 20.04
131
- - **Architectures:** amd64, arm64
132
-
133
- ## Contributing
134
-
135
- If you want to know how to build this project and contribute to it, please
136
- check out the [Contributing Guide](CONTRIBUTING.md).
@@ -1,66 +0,0 @@
1
- # Dispatch Highlevel Interface
2
-
3
- [![Build Status](https://github.com/frequenz-floss/frequenz-dispatch-python/actions/workflows/ci.yaml/badge.svg)](https://github.com/frequenz-floss/frequenz-dispatch-python/actions/workflows/ci.yaml)
4
- [![PyPI Package](https://img.shields.io/pypi/v/frequenz-dispatch)](https://pypi.org/project/frequenz-dispatch/)
5
- [![Docs](https://img.shields.io/badge/docs-latest-informational)](https://frequenz-floss.github.io/frequenz-dispatch-python/)
6
-
7
- ## Introduction
8
-
9
- A highlevel interface for the dispatch API.
10
-
11
- See [the documentation](https://frequenz-floss.github.io/frequenz-dispatch-python/v0.1/reference/frequenz/dispatch) for more information.
12
-
13
- ## Usage
14
-
15
- The [`Dispatcher` class](https://frequenz-floss.github.io/frequenz-dispatch-python/v0.1/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher), the main entry point for the API, provides two channels:
16
-
17
- * [Lifecycle events](https://frequenz-floss.github.io/frequenz-dispatch-python/v0.1/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher.lifecycle_events): A channel that sends a message whenever a [Dispatch][frequenz.dispatch.Dispatch] is created, updated or deleted.
18
- * [Running status change](https://frequenz-floss.github.io/frequenz-dispatch-python/v0.1/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher.running_status_change): Sends a dispatch message whenever a dispatch is ready to be executed according to the schedule or the running status of the dispatch changed in a way that could potentially require the actor to start, stop or reconfigure itself.
19
-
20
- ### Example using the running status change channel
21
-
22
- ```python
23
- import os
24
- from unittest.mock import MagicMock
25
- from datetime import timedelta
26
-
27
- from frequenz.dispatch import Dispatcher, DispatchInfo, MergeByType
28
-
29
- async def create_actor(dispatch: DispatchInfo, receiver: Receiver[DispatchInfo]) -> Actor:
30
- return MagicMock(dispatch=dispatch, receiver=receiver)
31
-
32
- async def run():
33
- url = os.getenv("DISPATCH_API_URL", "grpc://dispatch.url.goes.here.example.com")
34
- auth_key = os.getenv("DISPATCH_API_AUTH_KEY", "some-key")
35
- sign_secret = os.getenv("DISPATCH_API_SIGN_SECRET")
36
-
37
- microgrid_id = 1
38
-
39
- async with Dispatcher(
40
- microgrid_id=microgrid_id,
41
- server_url=url,
42
- auth_key=auth_key,
43
- sign_secret=sign_secret,
44
- ) as dispatcher:
45
- await dispatcher.start_managing(
46
- dispatch_type="EXAMPLE_TYPE",
47
- actor_factory=create_actor,
48
- merge_strategy=MergeByType(),
49
- retry_interval=timedelta(seconds=10)
50
- )
51
-
52
- await dispatcher
53
- ```
54
-
55
- ## Supported Platforms
56
-
57
- The following platforms are officially supported (tested):
58
-
59
- - **Python:** 3.11
60
- - **Operating System:** Ubuntu Linux 20.04
61
- - **Architectures:** amd64, arm64
62
-
63
- ## Contributing
64
-
65
- If you want to know how to build this project and contribute to it, please
66
- check out the [Contributing Guide](CONTRIBUTING.md).
@@ -1,5 +0,0 @@
1
- # Dispatch Highlevel Interface Release Notes
2
-
3
- ## Summary
4
-
5
- This release just updates some dependencies to allow compatibility with newer versions.
@@ -1,136 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: frequenz-dispatch
3
- Version: 1.0.2
4
- Summary: A highlevel interface for the dispatch API
5
- Author-email: Frequenz Energy-as-a-Service GmbH <floss@frequenz.com>
6
- License: MIT
7
- Project-URL: Documentation, https://frequenz-floss.github.io/frequenz-dispatch-python/
8
- Project-URL: Changelog, https://github.com/frequenz-floss/frequenz-dispatch-python/releases
9
- Project-URL: Issues, https://github.com/frequenz-floss/frequenz-dispatch-python/issues
10
- Project-URL: Repository, https://github.com/frequenz-floss/frequenz-dispatch-python
11
- Project-URL: Support, https://github.com/frequenz-floss/frequenz-dispatch-python/discussions/categories/support
12
- Keywords: frequenz,python,actor,frequenz-dispatch,dispatch,highlevel,api
13
- Classifier: Development Status :: 3 - Alpha
14
- Classifier: Intended Audience :: Developers
15
- Classifier: License :: OSI Approved :: MIT License
16
- Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3 :: Only
18
- Classifier: Topic :: Software Development :: Libraries
19
- Classifier: Typing :: Typed
20
- Requires-Python: <4,>=3.11
21
- Description-Content-Type: text/markdown
22
- License-File: LICENSE
23
- Requires-Dist: typing-extensions<5.0.0,>=4.13.0
24
- Requires-Dist: frequenz-sdk<1.0.0-rc2300,>=1.0.0-rc2100
25
- Requires-Dist: frequenz-channels<2.0.0,>=1.6.1
26
- Requires-Dist: frequenz-client-dispatch<2.0.0,>=0.11.3
27
- Requires-Dist: frequenz-client-base<0.12.0,>=0.11.0
28
- Provides-Extra: dev-flake8
29
- Requires-Dist: flake8==7.3.0; extra == "dev-flake8"
30
- Requires-Dist: flake8-docstrings==1.7.0; extra == "dev-flake8"
31
- Requires-Dist: flake8-pyproject==1.2.3; extra == "dev-flake8"
32
- Requires-Dist: pydoclint==0.8.3; extra == "dev-flake8"
33
- Requires-Dist: pydocstyle==6.3.0; extra == "dev-flake8"
34
- Provides-Extra: dev-formatting
35
- Requires-Dist: black==25.11.0; extra == "dev-formatting"
36
- Requires-Dist: isort==6.0.1; extra == "dev-formatting"
37
- Provides-Extra: dev-mkdocs
38
- Requires-Dist: black==25.11.0; extra == "dev-mkdocs"
39
- Requires-Dist: Markdown==3.10; extra == "dev-mkdocs"
40
- Requires-Dist: mike==2.1.3; extra == "dev-mkdocs"
41
- Requires-Dist: mkdocs-gen-files==0.5.0; extra == "dev-mkdocs"
42
- Requires-Dist: mkdocs-literate-nav==0.6.2; extra == "dev-mkdocs"
43
- Requires-Dist: mkdocs-macros-plugin==1.5.0; extra == "dev-mkdocs"
44
- Requires-Dist: mkdocs-material==9.7.0; extra == "dev-mkdocs"
45
- Requires-Dist: mkdocstrings[python]==0.30.1; extra == "dev-mkdocs"
46
- Requires-Dist: mkdocstrings-python==1.18.2; extra == "dev-mkdocs"
47
- Requires-Dist: frequenz-repo-config[lib]==0.13.5; extra == "dev-mkdocs"
48
- Provides-Extra: dev-mypy
49
- Requires-Dist: mypy==1.19.0; extra == "dev-mypy"
50
- Requires-Dist: grpc-stubs==1.53.0.6; extra == "dev-mypy"
51
- Requires-Dist: types-Markdown==3.10.0.20251106; extra == "dev-mypy"
52
- Requires-Dist: frequenz-dispatch[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-mypy"
53
- Provides-Extra: dev-noxfile
54
- Requires-Dist: uv==0.9.13; extra == "dev-noxfile"
55
- Requires-Dist: nox==2025.11.12; extra == "dev-noxfile"
56
- Requires-Dist: frequenz-repo-config[lib]==0.13.5; extra == "dev-noxfile"
57
- Provides-Extra: dev-pylint
58
- Requires-Dist: pylint==3.3.8; extra == "dev-pylint"
59
- Requires-Dist: frequenz-dispatch[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-pylint"
60
- Provides-Extra: dev-pytest
61
- Requires-Dist: pytest==8.4.2; extra == "dev-pytest"
62
- Requires-Dist: frequenz-repo-config[extra-lint-examples]==0.13.5; extra == "dev-pytest"
63
- Requires-Dist: pytest-mock==3.15.1; extra == "dev-pytest"
64
- Requires-Dist: pytest-asyncio==1.2.0; extra == "dev-pytest"
65
- Requires-Dist: async-solipsism==0.8; extra == "dev-pytest"
66
- Requires-Dist: time-machine==2.19.0; extra == "dev-pytest"
67
- Provides-Extra: dev
68
- Requires-Dist: frequenz-dispatch[dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]; extra == "dev"
69
- Dynamic: license-file
70
-
71
- # Dispatch Highlevel Interface
72
-
73
- [![Build Status](https://github.com/frequenz-floss/frequenz-dispatch-python/actions/workflows/ci.yaml/badge.svg)](https://github.com/frequenz-floss/frequenz-dispatch-python/actions/workflows/ci.yaml)
74
- [![PyPI Package](https://img.shields.io/pypi/v/frequenz-dispatch)](https://pypi.org/project/frequenz-dispatch/)
75
- [![Docs](https://img.shields.io/badge/docs-latest-informational)](https://frequenz-floss.github.io/frequenz-dispatch-python/)
76
-
77
- ## Introduction
78
-
79
- A highlevel interface for the dispatch API.
80
-
81
- See [the documentation](https://frequenz-floss.github.io/frequenz-dispatch-python/v0.1/reference/frequenz/dispatch) for more information.
82
-
83
- ## Usage
84
-
85
- The [`Dispatcher` class](https://frequenz-floss.github.io/frequenz-dispatch-python/v0.1/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher), the main entry point for the API, provides two channels:
86
-
87
- * [Lifecycle events](https://frequenz-floss.github.io/frequenz-dispatch-python/v0.1/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher.lifecycle_events): A channel that sends a message whenever a [Dispatch][frequenz.dispatch.Dispatch] is created, updated or deleted.
88
- * [Running status change](https://frequenz-floss.github.io/frequenz-dispatch-python/v0.1/reference/frequenz/dispatch/#frequenz.dispatch.Dispatcher.running_status_change): Sends a dispatch message whenever a dispatch is ready to be executed according to the schedule or the running status of the dispatch changed in a way that could potentially require the actor to start, stop or reconfigure itself.
89
-
90
- ### Example using the running status change channel
91
-
92
- ```python
93
- import os
94
- from unittest.mock import MagicMock
95
- from datetime import timedelta
96
-
97
- from frequenz.dispatch import Dispatcher, DispatchInfo, MergeByType
98
-
99
- async def create_actor(dispatch: DispatchInfo, receiver: Receiver[DispatchInfo]) -> Actor:
100
- return MagicMock(dispatch=dispatch, receiver=receiver)
101
-
102
- async def run():
103
- url = os.getenv("DISPATCH_API_URL", "grpc://dispatch.url.goes.here.example.com")
104
- auth_key = os.getenv("DISPATCH_API_AUTH_KEY", "some-key")
105
- sign_secret = os.getenv("DISPATCH_API_SIGN_SECRET")
106
-
107
- microgrid_id = 1
108
-
109
- async with Dispatcher(
110
- microgrid_id=microgrid_id,
111
- server_url=url,
112
- auth_key=auth_key,
113
- sign_secret=sign_secret,
114
- ) as dispatcher:
115
- await dispatcher.start_managing(
116
- dispatch_type="EXAMPLE_TYPE",
117
- actor_factory=create_actor,
118
- merge_strategy=MergeByType(),
119
- retry_interval=timedelta(seconds=10)
120
- )
121
-
122
- await dispatcher
123
- ```
124
-
125
- ## Supported Platforms
126
-
127
- The following platforms are officially supported (tested):
128
-
129
- - **Python:** 3.11
130
- - **Operating System:** Ubuntu Linux 20.04
131
- - **Architectures:** amd64, arm64
132
-
133
- ## Contributing
134
-
135
- If you want to know how to build this project and contribute to it, please
136
- check out the [Contributing Guide](CONTRIBUTING.md).