pyplugy 0.1.0__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.
@@ -0,0 +1,31 @@
1
+ # Byte-compiled / cache
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ .pytest_cache/
6
+ .mypy_cache/
7
+ .ruff_cache/
8
+ .coverage
9
+ .coverage.*
10
+ htmlcov/
11
+ coverage.xml
12
+
13
+ # Build artifacts
14
+ build/
15
+ dist/
16
+ *.egg-info/
17
+ *.egg
18
+
19
+ # Virtual envs
20
+ .venv/
21
+ venv/
22
+ env/
23
+
24
+ # IDE
25
+ .idea/
26
+ .vscode/
27
+ *.swp
28
+
29
+ # OS
30
+ .DS_Store
31
+ Thumbs.db
pyplugy-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kilian Senger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
pyplugy-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,269 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyplugy
3
+ Version: 0.1.0
4
+ Summary: Plugin framework with hooks (pyHooky) and tasks (pyWorkflowy).
5
+ Project-URL: Homepage, https://github.com/kiliansen/pyPlugy
6
+ Project-URL: Issues, https://github.com/kiliansen/pyPlugy/issues
7
+ Author: KilianSen
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Kilian Senger
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: extension,framework,hooks,plugins,tasks
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Intended Audience :: Developers
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.11
36
+ Classifier: Programming Language :: Python :: 3.12
37
+ Classifier: Programming Language :: Python :: 3.13
38
+ Classifier: Programming Language :: Python :: 3.14
39
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
40
+ Classifier: Typing :: Typed
41
+ Requires-Python: >=3.11
42
+ Requires-Dist: packaging>=24.0
43
+ Requires-Dist: pyhooky>=0.1.0
44
+ Requires-Dist: pyworkflowy>=0.1.0
45
+ Provides-Extra: tasks
46
+ Requires-Dist: pyworkflowy>=0.1.0; extra == 'tasks'
47
+ Description-Content-Type: text/markdown
48
+
49
+ # pyPlugy
50
+
51
+ Plugin framework for Python that composes [pyHooky](https://github.com/kiliansen/pyHooky) for
52
+ hook-based extension points and (optionally) [pyWorkflowy](https://github.com/kiliansen/pyWorkflowy)
53
+ for plugin-owned background tasks.
54
+
55
+ A plugin in pyPlugy is just a tagged collection of hook registrations (plus,
56
+ optionally, tasks). The manager drives lifecycle, isolates each plugin's
57
+ registrations via pyHooky's `tag_scope`, and tears them all down with one
58
+ `clear_tag` call on unload.
59
+
60
+ ## Install
61
+
62
+ ```bash
63
+ uv add pyplugy
64
+ uv add 'pyplugy[tasks]' # add pyWorkflowy integration
65
+ ```
66
+
67
+ `pyplugy` depends on `pyhooky` and `packaging`. The `[tasks]` extra pulls in
68
+ `pyworkflowy` — without it, `ctx.task` raises a clear error pointing at the
69
+ extra.
70
+
71
+ ## Defining a plugin
72
+
73
+ Two equivalent authoring forms:
74
+
75
+ ### Decorator form
76
+
77
+ ```python
78
+ from pyplugy import plugin, PluginContext
79
+
80
+ @plugin("audit", version="1.0.0", requires=["auth>=1.0"], description="Audit log")
81
+ def setup(ctx: PluginContext) -> None:
82
+ @ctx.before("checkout")
83
+ def log_start(cart): ...
84
+
85
+ @ctx.after("checkout")
86
+ def log_done(result, cart): ...
87
+
88
+ @ctx.on("checkout:step")
89
+ def log_step(step, **kw): ...
90
+ ```
91
+
92
+ ### Class form
93
+
94
+ ```python
95
+ from pyplugy import Plugin, PluginContext
96
+
97
+ class AuditPlugin(Plugin):
98
+ name = "audit"
99
+ version = "1.0.0"
100
+ requires = ("auth>=1.0",)
101
+ description = "Audit log"
102
+
103
+ def on_load(self, ctx: PluginContext) -> None:
104
+ @ctx.before("checkout")
105
+ def log_start(cart): ...
106
+
107
+ def on_unload(self, ctx: PluginContext) -> None: ...
108
+ def on_enable(self, ctx: PluginContext) -> None: ...
109
+ def on_disable(self, ctx: PluginContext) -> None: ...
110
+ ```
111
+
112
+ Both produce a `Plugin` instance with the same `PluginManifest`.
113
+
114
+ ## PluginContext
115
+
116
+ The `PluginContext` handed to your setup / lifecycle methods exposes:
117
+
118
+ | Attribute | Use |
119
+ |----------------------|-----------------------------------------------------------|
120
+ | `ctx.hooks` | The `pyhooky.HookRegistry` hooks land in |
121
+ | `ctx.before/after/around/on/on_error/hook` | Thin auto-tagged pyHooky passthroughs |
122
+ | `ctx.task` | Register tasks via the manager's pyWorkflowy backend |
123
+ | `ctx.scheduler` | Shared scheduler for periodic tasks (`Scheduler` protocol) |
124
+ | `ctx.config` | Per-plugin config dict (manager-injected) |
125
+ | `ctx.logger` | `logging.Logger` named `pyplugy.<plugin-name>` |
126
+
127
+ Every hook registered inside `on_load` is auto-tagged with the plugin's name
128
+ because the manager wraps the call in `pyhooky.tag_scope(plugin.name)`.
129
+
130
+ ## Discovery
131
+
132
+ ```python
133
+ from pathlib import Path
134
+ from pyplugy import PluginManager
135
+
136
+ manager = PluginManager()
137
+
138
+ # Entry points
139
+ manager.load_entry_points("myapp.plugins")
140
+
141
+ # Directory
142
+ manager.load_directory(Path("./plugins"), recursive=True)
143
+
144
+ # JIT — any of these:
145
+ manager.load(MyPlugin)
146
+ manager.load(MyPlugin())
147
+ manager.load(some_module)
148
+ manager.load("my_app.plugins.audit") # dotted path
149
+ manager.load(Path("./extra/audit.py")) # .py file
150
+ ```
151
+
152
+ ## Lifecycle
153
+
154
+ States: `UNLOADED → LOADED → ENABLED ↔ DISABLED → UNLOADED`.
155
+
156
+ ```python
157
+ manager.load(MyPlugin) # → LOADED, then ENABLED
158
+ manager.disable("my-plugin") # ENABLED → DISABLED (tears down all hooks/tasks)
159
+ manager.enable("my-plugin") # DISABLED → ENABLED (re-runs on_load setup)
160
+ manager.unload("my-plugin") # → UNLOADED (clear_tag, on_unload)
161
+ manager.reload("my-plugin") # unload + load
162
+ ```
163
+
164
+ **Disable strategy** — pyPlugy *tears down* a disabled plugin's hooks and
165
+ tasks (via `clear_tag`) and *re-runs* its setup on enable. This keeps the
166
+ hook registry as the single source of truth and avoids stale half-registered
167
+ state when a plugin mutates external resources. Plugin authors should treat
168
+ `on_load` / `setup` as idempotent (or at least re-runnable).
169
+
170
+ ## Dependencies between plugins
171
+
172
+ `requires` accepts a list of `"<name><specifier>"` strings (PEP 440):
173
+
174
+ ```python
175
+ class Reporting(Plugin):
176
+ name = "reporting"
177
+ version = "0.2.0"
178
+ requires = ("audit>=1.0", "auth<3.0")
179
+ ```
180
+
181
+ On load, the manager performs a topological sort:
182
+
183
+ - Missing dependency → `PluginDependencyError`
184
+ - Specifier mismatch → `PluginDependencyError`
185
+ - Cycle → `PluginDependencyError`
186
+
187
+ ## Tag-based isolation
188
+
189
+ Every plugin's hooks are tagged with its `name` via `pyhooky.tag_scope`.
190
+ Unload is a single call:
191
+
192
+ ```python
193
+ manager.unload("audit")
194
+ # under the hood:
195
+ # registry.clear_tag("audit")
196
+ ```
197
+
198
+ You can list a plugin's targets / tasks with:
199
+
200
+ ```python
201
+ manager.plugin_targets("audit") # ["checkout", "checkout:step", ...]
202
+ manager.plugin_tasks("audit") # ["periodic_flush", ...]
203
+ ```
204
+
205
+ ## Manager-level hooks
206
+
207
+ The manager fires pyHooky listener hooks for every lifecycle transition:
208
+
209
+ | Target | Fires |
210
+ |--------------------------|------------------------------------------------------|
211
+ | `pyplugy:plugin:load` | After a plugin reaches LOADED |
212
+ | `pyplugy:plugin:enable` | After enable |
213
+ | `pyplugy:plugin:disable` | After disable |
214
+ | `pyplugy:plugin:unload` | Before the plugin's hooks/tasks are torn down |
215
+ | `pyplugy:plugin:error` | When any lifecycle method raises (plugin, exc) |
216
+
217
+ Listen with `pyhooky.on`:
218
+
219
+ ```python
220
+ from pyhooky import on
221
+ from pyplugy import HOOK_PLUGIN_LOAD
222
+
223
+ @on(HOOK_PLUGIN_LOAD)
224
+ def announce(plugin):
225
+ print("loaded:", plugin.manifest.name)
226
+ ```
227
+
228
+ ## Integration with pyWorkflowy (optional)
229
+
230
+ `ctx.task` and `ctx.scheduler` come from the manager-injected pyWorkflowy surface:
231
+
232
+ ```python
233
+ import pyworkflowy
234
+ from pyworkflowy.schedule import Scheduler
235
+ from pyplugy import PluginManager
236
+
237
+ manager = PluginManager(tasky=pyworkflowy, scheduler=Scheduler())
238
+
239
+ @plugin("cron-cleanup", version="0.1.0")
240
+ def setup(ctx):
241
+ @ctx.task
242
+ def cleanup(): ...
243
+ ctx.scheduler.every("5m").do(cleanup)
244
+ ```
245
+
246
+ Without a `tasky` argument, `ctx.task` raises a clear `RuntimeError`. pyPlugy
247
+ itself only depends on a small `typing.Protocol` defined in
248
+ `pyplugy._tasky_protocol` — so tests and apps don't need pyWorkflowy installed.
249
+
250
+ ## Errors
251
+
252
+ | Error | When |
253
+ |-----------------------------|-----------------------------------------------------|
254
+ | `PluginError` | Base |
255
+ | `PluginNotFoundError` | `get` / `enable` / `disable` / `unload` on unknown |
256
+ | `PluginAlreadyLoadedError` | Loading a plugin whose name is already loaded |
257
+ | `PluginDependencyError` | Missing dep, version mismatch, cycle |
258
+ | `PluginLoadError` | `on_load` / `on_enable` / decorator setup raised |
259
+ | `PluginUnloadError` | `on_unload` raised |
260
+ | `PluginManifestError` | Invalid manifest (missing name, ...) |
261
+
262
+ ## Development
263
+
264
+ ```bash
265
+ uv sync
266
+ uv run pytest
267
+ uv run ruff check .
268
+ uv run pyrefly check
269
+ ```
@@ -0,0 +1,221 @@
1
+ # pyPlugy
2
+
3
+ Plugin framework for Python that composes [pyHooky](https://github.com/kiliansen/pyHooky) for
4
+ hook-based extension points and (optionally) [pyWorkflowy](https://github.com/kiliansen/pyWorkflowy)
5
+ for plugin-owned background tasks.
6
+
7
+ A plugin in pyPlugy is just a tagged collection of hook registrations (plus,
8
+ optionally, tasks). The manager drives lifecycle, isolates each plugin's
9
+ registrations via pyHooky's `tag_scope`, and tears them all down with one
10
+ `clear_tag` call on unload.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ uv add pyplugy
16
+ uv add 'pyplugy[tasks]' # add pyWorkflowy integration
17
+ ```
18
+
19
+ `pyplugy` depends on `pyhooky` and `packaging`. The `[tasks]` extra pulls in
20
+ `pyworkflowy` — without it, `ctx.task` raises a clear error pointing at the
21
+ extra.
22
+
23
+ ## Defining a plugin
24
+
25
+ Two equivalent authoring forms:
26
+
27
+ ### Decorator form
28
+
29
+ ```python
30
+ from pyplugy import plugin, PluginContext
31
+
32
+ @plugin("audit", version="1.0.0", requires=["auth>=1.0"], description="Audit log")
33
+ def setup(ctx: PluginContext) -> None:
34
+ @ctx.before("checkout")
35
+ def log_start(cart): ...
36
+
37
+ @ctx.after("checkout")
38
+ def log_done(result, cart): ...
39
+
40
+ @ctx.on("checkout:step")
41
+ def log_step(step, **kw): ...
42
+ ```
43
+
44
+ ### Class form
45
+
46
+ ```python
47
+ from pyplugy import Plugin, PluginContext
48
+
49
+ class AuditPlugin(Plugin):
50
+ name = "audit"
51
+ version = "1.0.0"
52
+ requires = ("auth>=1.0",)
53
+ description = "Audit log"
54
+
55
+ def on_load(self, ctx: PluginContext) -> None:
56
+ @ctx.before("checkout")
57
+ def log_start(cart): ...
58
+
59
+ def on_unload(self, ctx: PluginContext) -> None: ...
60
+ def on_enable(self, ctx: PluginContext) -> None: ...
61
+ def on_disable(self, ctx: PluginContext) -> None: ...
62
+ ```
63
+
64
+ Both produce a `Plugin` instance with the same `PluginManifest`.
65
+
66
+ ## PluginContext
67
+
68
+ The `PluginContext` handed to your setup / lifecycle methods exposes:
69
+
70
+ | Attribute | Use |
71
+ |----------------------|-----------------------------------------------------------|
72
+ | `ctx.hooks` | The `pyhooky.HookRegistry` hooks land in |
73
+ | `ctx.before/after/around/on/on_error/hook` | Thin auto-tagged pyHooky passthroughs |
74
+ | `ctx.task` | Register tasks via the manager's pyWorkflowy backend |
75
+ | `ctx.scheduler` | Shared scheduler for periodic tasks (`Scheduler` protocol) |
76
+ | `ctx.config` | Per-plugin config dict (manager-injected) |
77
+ | `ctx.logger` | `logging.Logger` named `pyplugy.<plugin-name>` |
78
+
79
+ Every hook registered inside `on_load` is auto-tagged with the plugin's name
80
+ because the manager wraps the call in `pyhooky.tag_scope(plugin.name)`.
81
+
82
+ ## Discovery
83
+
84
+ ```python
85
+ from pathlib import Path
86
+ from pyplugy import PluginManager
87
+
88
+ manager = PluginManager()
89
+
90
+ # Entry points
91
+ manager.load_entry_points("myapp.plugins")
92
+
93
+ # Directory
94
+ manager.load_directory(Path("./plugins"), recursive=True)
95
+
96
+ # JIT — any of these:
97
+ manager.load(MyPlugin)
98
+ manager.load(MyPlugin())
99
+ manager.load(some_module)
100
+ manager.load("my_app.plugins.audit") # dotted path
101
+ manager.load(Path("./extra/audit.py")) # .py file
102
+ ```
103
+
104
+ ## Lifecycle
105
+
106
+ States: `UNLOADED → LOADED → ENABLED ↔ DISABLED → UNLOADED`.
107
+
108
+ ```python
109
+ manager.load(MyPlugin) # → LOADED, then ENABLED
110
+ manager.disable("my-plugin") # ENABLED → DISABLED (tears down all hooks/tasks)
111
+ manager.enable("my-plugin") # DISABLED → ENABLED (re-runs on_load setup)
112
+ manager.unload("my-plugin") # → UNLOADED (clear_tag, on_unload)
113
+ manager.reload("my-plugin") # unload + load
114
+ ```
115
+
116
+ **Disable strategy** — pyPlugy *tears down* a disabled plugin's hooks and
117
+ tasks (via `clear_tag`) and *re-runs* its setup on enable. This keeps the
118
+ hook registry as the single source of truth and avoids stale half-registered
119
+ state when a plugin mutates external resources. Plugin authors should treat
120
+ `on_load` / `setup` as idempotent (or at least re-runnable).
121
+
122
+ ## Dependencies between plugins
123
+
124
+ `requires` accepts a list of `"<name><specifier>"` strings (PEP 440):
125
+
126
+ ```python
127
+ class Reporting(Plugin):
128
+ name = "reporting"
129
+ version = "0.2.0"
130
+ requires = ("audit>=1.0", "auth<3.0")
131
+ ```
132
+
133
+ On load, the manager performs a topological sort:
134
+
135
+ - Missing dependency → `PluginDependencyError`
136
+ - Specifier mismatch → `PluginDependencyError`
137
+ - Cycle → `PluginDependencyError`
138
+
139
+ ## Tag-based isolation
140
+
141
+ Every plugin's hooks are tagged with its `name` via `pyhooky.tag_scope`.
142
+ Unload is a single call:
143
+
144
+ ```python
145
+ manager.unload("audit")
146
+ # under the hood:
147
+ # registry.clear_tag("audit")
148
+ ```
149
+
150
+ You can list a plugin's targets / tasks with:
151
+
152
+ ```python
153
+ manager.plugin_targets("audit") # ["checkout", "checkout:step", ...]
154
+ manager.plugin_tasks("audit") # ["periodic_flush", ...]
155
+ ```
156
+
157
+ ## Manager-level hooks
158
+
159
+ The manager fires pyHooky listener hooks for every lifecycle transition:
160
+
161
+ | Target | Fires |
162
+ |--------------------------|------------------------------------------------------|
163
+ | `pyplugy:plugin:load` | After a plugin reaches LOADED |
164
+ | `pyplugy:plugin:enable` | After enable |
165
+ | `pyplugy:plugin:disable` | After disable |
166
+ | `pyplugy:plugin:unload` | Before the plugin's hooks/tasks are torn down |
167
+ | `pyplugy:plugin:error` | When any lifecycle method raises (plugin, exc) |
168
+
169
+ Listen with `pyhooky.on`:
170
+
171
+ ```python
172
+ from pyhooky import on
173
+ from pyplugy import HOOK_PLUGIN_LOAD
174
+
175
+ @on(HOOK_PLUGIN_LOAD)
176
+ def announce(plugin):
177
+ print("loaded:", plugin.manifest.name)
178
+ ```
179
+
180
+ ## Integration with pyWorkflowy (optional)
181
+
182
+ `ctx.task` and `ctx.scheduler` come from the manager-injected pyWorkflowy surface:
183
+
184
+ ```python
185
+ import pyworkflowy
186
+ from pyworkflowy.schedule import Scheduler
187
+ from pyplugy import PluginManager
188
+
189
+ manager = PluginManager(tasky=pyworkflowy, scheduler=Scheduler())
190
+
191
+ @plugin("cron-cleanup", version="0.1.0")
192
+ def setup(ctx):
193
+ @ctx.task
194
+ def cleanup(): ...
195
+ ctx.scheduler.every("5m").do(cleanup)
196
+ ```
197
+
198
+ Without a `tasky` argument, `ctx.task` raises a clear `RuntimeError`. pyPlugy
199
+ itself only depends on a small `typing.Protocol` defined in
200
+ `pyplugy._tasky_protocol` — so tests and apps don't need pyWorkflowy installed.
201
+
202
+ ## Errors
203
+
204
+ | Error | When |
205
+ |-----------------------------|-----------------------------------------------------|
206
+ | `PluginError` | Base |
207
+ | `PluginNotFoundError` | `get` / `enable` / `disable` / `unload` on unknown |
208
+ | `PluginAlreadyLoadedError` | Loading a plugin whose name is already loaded |
209
+ | `PluginDependencyError` | Missing dep, version mismatch, cycle |
210
+ | `PluginLoadError` | `on_load` / `on_enable` / decorator setup raised |
211
+ | `PluginUnloadError` | `on_unload` raised |
212
+ | `PluginManifestError` | Invalid manifest (missing name, ...) |
213
+
214
+ ## Development
215
+
216
+ ```bash
217
+ uv sync
218
+ uv run pytest
219
+ uv run ruff check .
220
+ uv run pyrefly check
221
+ ```
@@ -0,0 +1,128 @@
1
+ [project]
2
+ name = "pyplugy"
3
+ version = "0.1.0"
4
+ description = "Plugin framework with hooks (pyHooky) and tasks (pyWorkflowy)."
5
+ readme = "README.md"
6
+ license = { file = "LICENSE" }
7
+ requires-python = ">=3.11"
8
+ authors = [{ name = "KilianSen" }]
9
+ keywords = ["plugins", "framework", "hooks", "tasks", "extension"]
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "Intended Audience :: Developers",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Programming Language :: Python :: 3.14",
19
+ "Topic :: Software Development :: Libraries :: Python Modules",
20
+ "Typing :: Typed",
21
+ ]
22
+ dependencies = [
23
+ "pyhooky>=0.1.0",
24
+ "packaging>=24.0",
25
+ "pyworkflowy>=0.1.0",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ tasks = ["pyworkflowy>=0.1.0"]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/kiliansen/pyPlugy"
33
+ Issues = "https://github.com/kiliansen/pyPlugy/issues"
34
+
35
+
36
+
37
+ [build-system]
38
+ requires = ["hatchling"]
39
+ build-backend = "hatchling.build"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/pyplugy"]
43
+
44
+ [tool.hatch.build.targets.sdist]
45
+ include = ["src/pyplugy", "README.md", "LICENSE", "pyproject.toml"]
46
+
47
+
48
+
49
+ [tool.pytest.ini_options]
50
+ minversion = "8.0"
51
+ testpaths = ["tests"]
52
+ addopts = ["-ra", "--strict-markers", "--strict-config"]
53
+ asyncio_mode = "auto"
54
+
55
+
56
+ [tool.coverage.run]
57
+ source = ["src/pyplugy"]
58
+ branch = true
59
+
60
+ [tool.coverage.report]
61
+ exclude_lines = [
62
+ "pragma: no cover",
63
+ "raise NotImplementedError",
64
+ "if TYPE_CHECKING:",
65
+ ]
66
+
67
+
68
+ [tool.ruff]
69
+ line-length = 100
70
+ target-version = "py311"
71
+ src = ["src", "tests"]
72
+
73
+ [tool.ruff.lint]
74
+ select = [
75
+ "E", "F", "W", # pycodestyle / pyflakes
76
+ "I", # isort
77
+ "B", # bugbear
78
+ "UP", # pyupgrade
79
+ "SIM", # simplify
80
+ "RUF", # ruff-specific
81
+ ]
82
+ ignore = [
83
+ # PEP 695 generic syntax doesn't play well with @overload chains
84
+ "UP046", # generic class via Generic[T]
85
+ "UP047", # generic function via TypeVar
86
+ ]
87
+
88
+ [tool.ruff.lint.per-file-ignores]
89
+ "tests/**" = ["B011"]
90
+
91
+
92
+ [tool.pyrefly]
93
+ project-includes = ["src", "tests"]
94
+ python-version = "3.11"
95
+
96
+ [tool.semantic_release]
97
+ version_toml = ["pyproject.toml:project.version"]
98
+ tag_format = "v{version}"
99
+ commit_message = "chore(release): {version} [skip ci]"
100
+ build_command = "pip install uv && uv build"
101
+ major_on_zero = false
102
+ allow_zero_version = true
103
+
104
+ [tool.semantic_release.branches.master]
105
+ match = "master"
106
+ prerelease = false
107
+
108
+ [tool.semantic_release.commit_parser_options]
109
+ allowed_tags = ["feat", "fix", "perf", "refactor", "docs", "style", "test", "build", "ci", "chore"]
110
+ minor_tags = ["feat"]
111
+ patch_tags = ["fix", "perf"]
112
+ default_bump_level = 0
113
+
114
+ [tool.semantic_release.publish]
115
+ upload_to_vcs_release = true
116
+
117
+ [tool.semantic_release.changelog]
118
+ exclude_commit_patterns = ["^chore\\(release\\):"]
119
+
120
+
121
+ [dependency-groups]
122
+ dev = [
123
+ "pytest>=8.0",
124
+ "pytest-asyncio>=0.23",
125
+ "pytest-cov>=5.0",
126
+ "ruff>=0.6",
127
+ "pyrefly>=0.16",
128
+ ]