kanban-core 2.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.
- kanban_core-2.1.0/.gitignore +164 -0
- kanban_core-2.1.0/PKG-INFO +177 -0
- kanban_core-2.1.0/README.md +150 -0
- kanban_core-2.1.0/pyproject.toml +171 -0
- kanban_core-2.1.0/src/kanban/__init__.py +26 -0
- kanban_core-2.1.0/src/kanban/events.py +128 -0
- kanban_core-2.1.0/src/kanban/exceptions.py +52 -0
- kanban_core-2.1.0/src/kanban/facade.py +262 -0
- kanban_core-2.1.0/src/kanban/models.py +102 -0
- kanban_core-2.1.0/src/kanban/permissions.py +64 -0
- kanban_core-2.1.0/src/kanban/policies.py +66 -0
- kanban_core-2.1.0/src/kanban/repositories.py +116 -0
- kanban_core-2.1.0/src/kanban/services/__init__.py +19 -0
- kanban_core-2.1.0/src/kanban/services/automation.py +20 -0
- kanban_core-2.1.0/src/kanban/services/board_admin.py +261 -0
- kanban_core-2.1.0/src/kanban/services/card_workflow.py +245 -0
- kanban_core-2.1.0/src/kanban/services/core.py +294 -0
- kanban_core-2.1.0/src/kanban/services/search.py +47 -0
- kanban_core-2.1.0/src/kanban/spi/__init__.py +49 -0
- kanban_core-2.1.0/src/kanban/spi/automation.py +49 -0
- kanban_core-2.1.0/src/kanban/unit_of_work.py +28 -0
- kanban_core-2.1.0/src/kanban_adapters/__init__.py +7 -0
- kanban_core-2.1.0/src/kanban_adapters/events.py +54 -0
- kanban_core-2.1.0/src/kanban_adapters/repositories.py +50 -0
- kanban_core-2.1.0/src/kanban_adapters/synchronous.py +52 -0
- kanban_core-2.1.0/tests/__init__.py +1 -0
- kanban_core-2.1.0/tests/conftest.py +16 -0
- kanban_core-2.1.0/tests/services/__init__.py +1 -0
- kanban_core-2.1.0/tests/test_automation_spi.py +239 -0
- kanban_core-2.1.0/tests/test_basic_functionality.py +63 -0
- kanban_core-2.1.0/tests/test_events.py +475 -0
- kanban_core-2.1.0/tests/test_models.py +50 -0
- kanban_core-2.1.0/tests/test_permissions.py +63 -0
- kanban_core-2.1.0/tests/test_repositories.py +161 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
.coverage*
|
|
54
|
+
|
|
55
|
+
# Translations
|
|
56
|
+
*.mo
|
|
57
|
+
*.pot
|
|
58
|
+
|
|
59
|
+
# Django stuff:
|
|
60
|
+
*.log
|
|
61
|
+
local_settings.py
|
|
62
|
+
db.sqlite3
|
|
63
|
+
db.sqlite3-journal
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
.pybuilder/
|
|
77
|
+
target/
|
|
78
|
+
|
|
79
|
+
# Jupyter Notebook
|
|
80
|
+
.ipynb_checkpoints
|
|
81
|
+
|
|
82
|
+
# IPython
|
|
83
|
+
profile_default/
|
|
84
|
+
ipython_config.py
|
|
85
|
+
|
|
86
|
+
# pyenv
|
|
87
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
88
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
89
|
+
# .python-version
|
|
90
|
+
|
|
91
|
+
# pipenv
|
|
92
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
93
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
94
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
95
|
+
# install all needed dependencies.
|
|
96
|
+
#Pipfile.lock
|
|
97
|
+
|
|
98
|
+
# poetry
|
|
99
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
100
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
101
|
+
# commonly ignored for libraries.
|
|
102
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
103
|
+
#poetry.lock
|
|
104
|
+
|
|
105
|
+
# pdm
|
|
106
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
107
|
+
#pdm.lock
|
|
108
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
109
|
+
# in version control.
|
|
110
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
111
|
+
.pdm.toml
|
|
112
|
+
|
|
113
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
114
|
+
__pypackages__/
|
|
115
|
+
|
|
116
|
+
# Celery stuff
|
|
117
|
+
celerybeat-schedule
|
|
118
|
+
celerybeat.pid
|
|
119
|
+
|
|
120
|
+
# SageMath parsed files
|
|
121
|
+
*.sage.py
|
|
122
|
+
|
|
123
|
+
# Environments
|
|
124
|
+
.env
|
|
125
|
+
.venv
|
|
126
|
+
env/
|
|
127
|
+
venv/
|
|
128
|
+
ENV/
|
|
129
|
+
env.bak/
|
|
130
|
+
venv.bak/
|
|
131
|
+
|
|
132
|
+
# Spyder project settings
|
|
133
|
+
.spyderproject
|
|
134
|
+
.spyproject
|
|
135
|
+
|
|
136
|
+
# Rope project settings
|
|
137
|
+
.ropeproject
|
|
138
|
+
|
|
139
|
+
# mkdocs documentation
|
|
140
|
+
/site
|
|
141
|
+
|
|
142
|
+
# mypy
|
|
143
|
+
.mypy_cache/
|
|
144
|
+
.dmypy.json
|
|
145
|
+
dmypy.json
|
|
146
|
+
|
|
147
|
+
# Pyre type checker
|
|
148
|
+
.pyre/
|
|
149
|
+
|
|
150
|
+
# pytype static type analyzer
|
|
151
|
+
.pytype/
|
|
152
|
+
|
|
153
|
+
# Cython debug symbols
|
|
154
|
+
cython_debug/
|
|
155
|
+
|
|
156
|
+
# PyCharm
|
|
157
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
158
|
+
# be added to the global gitignore or merged into this project gitignore. For a PyCharm
|
|
159
|
+
# project, it is advised to generate a fresh project using the JetBrains template.
|
|
160
|
+
# Do not edit any of the settings files in the project root directory.
|
|
161
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
162
|
+
# be added to the global gitignore or merged into this project gitignore. For a PyCharm
|
|
163
|
+
# project, it is advised to generate a fresh project using the JetBrains template.
|
|
164
|
+
.idea/
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: kanban-core
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: A comprehensive, extensible kanban board implementation
|
|
5
|
+
Project-URL: Homepage, https://github.com/adieyal/kanban-core
|
|
6
|
+
Project-URL: Repository, https://github.com/adieyal/kanban-core
|
|
7
|
+
Project-URL: Issues, https://github.com/adieyal/kanban-core/issues
|
|
8
|
+
Author: Adi Eyal
|
|
9
|
+
License: MIT
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: pre-commit>=3.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# kanban-core
|
|
29
|
+
|
|
30
|
+
[](https://github.com/adieyal/kanban-core/actions/workflows/test.yml)
|
|
31
|
+
|
|
32
|
+
A framework-independent Kanban business module for Python 3.10+.
|
|
33
|
+
|
|
34
|
+
**This is not a Kanban board app.** It's the engine underneath one — boards,
|
|
35
|
+
columns, cards, moves, workflow rules, permissions, automation and events —
|
|
36
|
+
with no UI and no database of its own. You supply persistence, authorization
|
|
37
|
+
and an interface; kanban-core guarantees the board behaves correctly
|
|
38
|
+
underneath them.
|
|
39
|
+
|
|
40
|
+
Board mechanics — moving cards, enforcing WIP limits, tracking state — get
|
|
41
|
+
rewritten in nearly every Kanban tool because it's hard to find an
|
|
42
|
+
implementation with no UI or database bolted on. kanban-core is that missing
|
|
43
|
+
piece.
|
|
44
|
+
|
|
45
|
+
Your application talks to one public business API, `KanbanFacade`. The module
|
|
46
|
+
owns its business state and invariants; persistence, authorization, search,
|
|
47
|
+
automation and event delivery are supplied from outside at composition time.
|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
## What's included
|
|
52
|
+
|
|
53
|
+
- Boards, columns and cards, with move validation, WIP limits, card ordering
|
|
54
|
+
and dependency-cycle checks
|
|
55
|
+
- Configurable workflow policies for deployment-specific rules
|
|
56
|
+
- Permission checks via an injected policy — not hardcoded into the module
|
|
57
|
+
- Search over cards via a pluggable searcher
|
|
58
|
+
- Automation that reacts to events and proposes actions
|
|
59
|
+
- An event system for audit trails, outboxes and integrations
|
|
60
|
+
- Deterministic testing support via injected clock and ID factory
|
|
61
|
+
- Zero dependencies outside the Python standard library
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install kanban-core
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Quick example
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from kanban import KanbanFacade
|
|
73
|
+
from kanban_adapters import (
|
|
74
|
+
EventBus,
|
|
75
|
+
InMemoryKanbanRepository,
|
|
76
|
+
SynchronousKanbanUnitOfWork,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
repository = InMemoryKanbanRepository()
|
|
80
|
+
events = EventBus()
|
|
81
|
+
|
|
82
|
+
# Commits the board state and the events produced by the same operation.
|
|
83
|
+
uow = SynchronousKanbanUnitOfWork(repository, events)
|
|
84
|
+
|
|
85
|
+
kanban = KanbanFacade(repository, uow)
|
|
86
|
+
|
|
87
|
+
board = kanban.create_board("alice", "Launch")
|
|
88
|
+
todo = board.column_named("To Do")
|
|
89
|
+
in_progress = board.column_named("In Progress")
|
|
90
|
+
assert todo is not None and in_progress is not None
|
|
91
|
+
|
|
92
|
+
card = kanban.add_card("alice", board.id, todo.id, "Publish release notes")
|
|
93
|
+
kanban.move_card("alice", board.id, card.id, in_progress.id)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`BoardView`, `ColumnView` and `CardView` are immutable observations. Change
|
|
97
|
+
business state only through `KanbanFacade`.
|
|
98
|
+
|
|
99
|
+
Swap `InMemoryKanbanRepository` for a Postgres- or Django-backed
|
|
100
|
+
implementation and nothing above this line changes — that boundary is the
|
|
101
|
+
whole point of the module. See [Extending Kanban Core](docs/extensibility.md).
|
|
102
|
+
|
|
103
|
+
### Why both a repository and a unit of work?
|
|
104
|
+
|
|
105
|
+
They have different responsibilities:
|
|
106
|
+
|
|
107
|
+
- `KanbanRepository` **reads and stores board snapshots**.
|
|
108
|
+
- `KanbanUnitOfWork` **commits one business operation**, including both the new
|
|
109
|
+
board snapshot and the events produced by that operation.
|
|
110
|
+
|
|
111
|
+
The distinction matters most in production. A database-backed unit of work can save
|
|
112
|
+
the board and append its events to an outbox in one transaction, so you do not end
|
|
113
|
+
up with committed state but a lost event.
|
|
114
|
+
|
|
115
|
+
A useful shorthand is:
|
|
116
|
+
|
|
117
|
+
> **Repository reads state. Unit of work commits an operation.**
|
|
118
|
+
|
|
119
|
+
`SynchronousKanbanUnitOfWork` is the reference implementation for tests and simple
|
|
120
|
+
single-process applications. It saves through the repository and then publishes
|
|
121
|
+
through the supplied event publisher.
|
|
122
|
+
|
|
123
|
+
## Demo UI
|
|
124
|
+
|
|
125
|
+
The repository includes a small FastAPI example in [`kanban-demo-ui`](kanban-demo-ui/).
|
|
126
|
+
It is an application of the module, not part of the business package. The example
|
|
127
|
+
composes `KanbanFacade` with the in-memory repository, synchronous unit of work and
|
|
128
|
+
event bus, then keeps HTTP routes and browser behavior outside Kanban Core.
|
|
129
|
+
|
|
130
|
+
Install and run it from the repository root:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
python -m pip install -e .
|
|
134
|
+
python -m pip install -r kanban-demo-ui/requirements.txt
|
|
135
|
+
python kanban-demo-ui/run_demo.py
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The browser uses native HTML drag-and-drop. It updates the card list optimistically,
|
|
139
|
+
then sends the requested move through the facade; the server remains authoritative
|
|
140
|
+
for WIP limits, workflow rules, ordering and event generation. The demo deliberately
|
|
141
|
+
uses in-memory state and has no authentication, CSRF protection or durable outbox.
|
|
142
|
+
|
|
143
|
+
## Documentation
|
|
144
|
+
|
|
145
|
+
- **[Quickstart](docs/quickstart.md)** — build and use a board in a few minutes.
|
|
146
|
+
- **[Core concepts](docs/concepts.md)** — the facade boundary, immutable views,
|
|
147
|
+
ports and composition.
|
|
148
|
+
- **[Extending Kanban Core](docs/extensibility.md)** — persistence, permissions,
|
|
149
|
+
workflow rules, search, automation and events.
|
|
150
|
+
- **[Integration patterns](docs/integration-patterns.md)** — compose the module in
|
|
151
|
+
web requests, workers and commands.
|
|
152
|
+
- **[API reference](docs/api-reference.md)** — supported facade, views, exceptions
|
|
153
|
+
and SPI contracts.
|
|
154
|
+
- **[Architecture](docs/architecture.md)** — why the module is designed this way.
|
|
155
|
+
|
|
156
|
+
## Public contract
|
|
157
|
+
|
|
158
|
+
Only names in `kanban.__all__` are supported application API:
|
|
159
|
+
|
|
160
|
+
- `KanbanFacade` is the only business-operation entry point.
|
|
161
|
+
- `BoardView`, `ColumnView` and `CardView` are immutable return values.
|
|
162
|
+
- public exceptions are exported from `kanban`.
|
|
163
|
+
- infrastructure and extension contracts live under `kanban.spi`.
|
|
164
|
+
|
|
165
|
+
Internal models and application services are implementation details.
|
|
166
|
+
|
|
167
|
+
## Development
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
python -m pip install -e ".[dev]"
|
|
171
|
+
make check
|
|
172
|
+
make test
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# kanban-core
|
|
2
|
+
|
|
3
|
+
[](https://github.com/adieyal/kanban-core/actions/workflows/test.yml)
|
|
4
|
+
|
|
5
|
+
A framework-independent Kanban business module for Python 3.10+.
|
|
6
|
+
|
|
7
|
+
**This is not a Kanban board app.** It's the engine underneath one — boards,
|
|
8
|
+
columns, cards, moves, workflow rules, permissions, automation and events —
|
|
9
|
+
with no UI and no database of its own. You supply persistence, authorization
|
|
10
|
+
and an interface; kanban-core guarantees the board behaves correctly
|
|
11
|
+
underneath them.
|
|
12
|
+
|
|
13
|
+
Board mechanics — moving cards, enforcing WIP limits, tracking state — get
|
|
14
|
+
rewritten in nearly every Kanban tool because it's hard to find an
|
|
15
|
+
implementation with no UI or database bolted on. kanban-core is that missing
|
|
16
|
+
piece.
|
|
17
|
+
|
|
18
|
+
Your application talks to one public business API, `KanbanFacade`. The module
|
|
19
|
+
owns its business state and invariants; persistence, authorization, search,
|
|
20
|
+
automation and event delivery are supplied from outside at composition time.
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
## What's included
|
|
25
|
+
|
|
26
|
+
- Boards, columns and cards, with move validation, WIP limits, card ordering
|
|
27
|
+
and dependency-cycle checks
|
|
28
|
+
- Configurable workflow policies for deployment-specific rules
|
|
29
|
+
- Permission checks via an injected policy — not hardcoded into the module
|
|
30
|
+
- Search over cards via a pluggable searcher
|
|
31
|
+
- Automation that reacts to events and proposes actions
|
|
32
|
+
- An event system for audit trails, outboxes and integrations
|
|
33
|
+
- Deterministic testing support via injected clock and ID factory
|
|
34
|
+
- Zero dependencies outside the Python standard library
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install kanban-core
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick example
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from kanban import KanbanFacade
|
|
46
|
+
from kanban_adapters import (
|
|
47
|
+
EventBus,
|
|
48
|
+
InMemoryKanbanRepository,
|
|
49
|
+
SynchronousKanbanUnitOfWork,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
repository = InMemoryKanbanRepository()
|
|
53
|
+
events = EventBus()
|
|
54
|
+
|
|
55
|
+
# Commits the board state and the events produced by the same operation.
|
|
56
|
+
uow = SynchronousKanbanUnitOfWork(repository, events)
|
|
57
|
+
|
|
58
|
+
kanban = KanbanFacade(repository, uow)
|
|
59
|
+
|
|
60
|
+
board = kanban.create_board("alice", "Launch")
|
|
61
|
+
todo = board.column_named("To Do")
|
|
62
|
+
in_progress = board.column_named("In Progress")
|
|
63
|
+
assert todo is not None and in_progress is not None
|
|
64
|
+
|
|
65
|
+
card = kanban.add_card("alice", board.id, todo.id, "Publish release notes")
|
|
66
|
+
kanban.move_card("alice", board.id, card.id, in_progress.id)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`BoardView`, `ColumnView` and `CardView` are immutable observations. Change
|
|
70
|
+
business state only through `KanbanFacade`.
|
|
71
|
+
|
|
72
|
+
Swap `InMemoryKanbanRepository` for a Postgres- or Django-backed
|
|
73
|
+
implementation and nothing above this line changes — that boundary is the
|
|
74
|
+
whole point of the module. See [Extending Kanban Core](docs/extensibility.md).
|
|
75
|
+
|
|
76
|
+
### Why both a repository and a unit of work?
|
|
77
|
+
|
|
78
|
+
They have different responsibilities:
|
|
79
|
+
|
|
80
|
+
- `KanbanRepository` **reads and stores board snapshots**.
|
|
81
|
+
- `KanbanUnitOfWork` **commits one business operation**, including both the new
|
|
82
|
+
board snapshot and the events produced by that operation.
|
|
83
|
+
|
|
84
|
+
The distinction matters most in production. A database-backed unit of work can save
|
|
85
|
+
the board and append its events to an outbox in one transaction, so you do not end
|
|
86
|
+
up with committed state but a lost event.
|
|
87
|
+
|
|
88
|
+
A useful shorthand is:
|
|
89
|
+
|
|
90
|
+
> **Repository reads state. Unit of work commits an operation.**
|
|
91
|
+
|
|
92
|
+
`SynchronousKanbanUnitOfWork` is the reference implementation for tests and simple
|
|
93
|
+
single-process applications. It saves through the repository and then publishes
|
|
94
|
+
through the supplied event publisher.
|
|
95
|
+
|
|
96
|
+
## Demo UI
|
|
97
|
+
|
|
98
|
+
The repository includes a small FastAPI example in [`kanban-demo-ui`](kanban-demo-ui/).
|
|
99
|
+
It is an application of the module, not part of the business package. The example
|
|
100
|
+
composes `KanbanFacade` with the in-memory repository, synchronous unit of work and
|
|
101
|
+
event bus, then keeps HTTP routes and browser behavior outside Kanban Core.
|
|
102
|
+
|
|
103
|
+
Install and run it from the repository root:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
python -m pip install -e .
|
|
107
|
+
python -m pip install -r kanban-demo-ui/requirements.txt
|
|
108
|
+
python kanban-demo-ui/run_demo.py
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The browser uses native HTML drag-and-drop. It updates the card list optimistically,
|
|
112
|
+
then sends the requested move through the facade; the server remains authoritative
|
|
113
|
+
for WIP limits, workflow rules, ordering and event generation. The demo deliberately
|
|
114
|
+
uses in-memory state and has no authentication, CSRF protection or durable outbox.
|
|
115
|
+
|
|
116
|
+
## Documentation
|
|
117
|
+
|
|
118
|
+
- **[Quickstart](docs/quickstart.md)** — build and use a board in a few minutes.
|
|
119
|
+
- **[Core concepts](docs/concepts.md)** — the facade boundary, immutable views,
|
|
120
|
+
ports and composition.
|
|
121
|
+
- **[Extending Kanban Core](docs/extensibility.md)** — persistence, permissions,
|
|
122
|
+
workflow rules, search, automation and events.
|
|
123
|
+
- **[Integration patterns](docs/integration-patterns.md)** — compose the module in
|
|
124
|
+
web requests, workers and commands.
|
|
125
|
+
- **[API reference](docs/api-reference.md)** — supported facade, views, exceptions
|
|
126
|
+
and SPI contracts.
|
|
127
|
+
- **[Architecture](docs/architecture.md)** — why the module is designed this way.
|
|
128
|
+
|
|
129
|
+
## Public contract
|
|
130
|
+
|
|
131
|
+
Only names in `kanban.__all__` are supported application API:
|
|
132
|
+
|
|
133
|
+
- `KanbanFacade` is the only business-operation entry point.
|
|
134
|
+
- `BoardView`, `ColumnView` and `CardView` are immutable return values.
|
|
135
|
+
- public exceptions are exported from `kanban`.
|
|
136
|
+
- infrastructure and extension contracts live under `kanban.spi`.
|
|
137
|
+
|
|
138
|
+
Internal models and application services are implementation details.
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
python -m pip install -e ".[dev]"
|
|
144
|
+
make check
|
|
145
|
+
make test
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
MIT
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kanban-core"
|
|
7
|
+
version = "2.1.0"
|
|
8
|
+
description = "A comprehensive, extensible kanban board implementation"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
authors = [{ name = "Adi Eyal" }]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
|
+
]
|
|
23
|
+
requires-python = ">=3.10"
|
|
24
|
+
dependencies = []
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
dev = [
|
|
28
|
+
"pytest>=7.0",
|
|
29
|
+
"pytest-cov>=4.0",
|
|
30
|
+
"ruff>=0.1.0",
|
|
31
|
+
"pre-commit>=3.0",
|
|
32
|
+
"mypy>=1.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/adieyal/kanban-core"
|
|
37
|
+
Repository = "https://github.com/adieyal/kanban-core"
|
|
38
|
+
Issues = "https://github.com/adieyal/kanban-core/issues"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/kanban", "src/kanban_adapters"]
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.sdist]
|
|
44
|
+
include = [
|
|
45
|
+
"/src",
|
|
46
|
+
"/tests",
|
|
47
|
+
"/README.md",
|
|
48
|
+
"/LICENSE",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
# Ruff configuration
|
|
52
|
+
[tool.ruff]
|
|
53
|
+
target-version = "py310"
|
|
54
|
+
line-length = 88
|
|
55
|
+
src = ["src", "tests"]
|
|
56
|
+
|
|
57
|
+
[tool.ruff.lint]
|
|
58
|
+
select = [
|
|
59
|
+
"E", # pycodestyle errors
|
|
60
|
+
"W", # pycodestyle warnings
|
|
61
|
+
"F", # pyflakes
|
|
62
|
+
"I", # isort
|
|
63
|
+
"B", # flake8-bugbear
|
|
64
|
+
"C4", # flake8-comprehensions
|
|
65
|
+
"UP", # pyupgrade
|
|
66
|
+
"ARG", # flake8-unused-arguments
|
|
67
|
+
"SIM", # flake8-simplify
|
|
68
|
+
]
|
|
69
|
+
ignore = [
|
|
70
|
+
"E501", # line too long (handled by formatter)
|
|
71
|
+
"B008", # do not perform function calls in argument defaults
|
|
72
|
+
"B904", # raise ... from None
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
[tool.ruff.lint.per-file-ignores]
|
|
76
|
+
"tests/**/*" = ["ARG", "S101"] # Allow unused args and assert statements in tests
|
|
77
|
+
|
|
78
|
+
[tool.ruff.lint.isort]
|
|
79
|
+
known-first-party = ["kanban", "kanban_adapters"]
|
|
80
|
+
|
|
81
|
+
[tool.ruff.format]
|
|
82
|
+
quote-style = "double"
|
|
83
|
+
indent-style = "space"
|
|
84
|
+
skip-magic-trailing-comma = false
|
|
85
|
+
line-ending = "auto"
|
|
86
|
+
|
|
87
|
+
# Pytest configuration
|
|
88
|
+
[tool.pytest.ini_options]
|
|
89
|
+
testpaths = ["tests"]
|
|
90
|
+
python_files = ["test_*.py", "*_test.py"]
|
|
91
|
+
python_classes = ["Test*"]
|
|
92
|
+
python_functions = ["test_*"]
|
|
93
|
+
addopts = [
|
|
94
|
+
"--strict-markers",
|
|
95
|
+
"--strict-config",
|
|
96
|
+
"--verbose",
|
|
97
|
+
]
|
|
98
|
+
filterwarnings = [
|
|
99
|
+
"error",
|
|
100
|
+
"ignore::UserWarning",
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
[tool.importlinter]
|
|
104
|
+
root_packages = ["kanban", "kanban_adapters"]
|
|
105
|
+
|
|
106
|
+
[[tool.importlinter.contracts]]
|
|
107
|
+
name = "Adapters use only the kanban SPI"
|
|
108
|
+
type = "forbidden"
|
|
109
|
+
source_modules = ["kanban_adapters"]
|
|
110
|
+
forbidden_modules = [
|
|
111
|
+
"kanban",
|
|
112
|
+
]
|
|
113
|
+
ignore_imports = [
|
|
114
|
+
"kanban_adapters.events -> kanban.spi",
|
|
115
|
+
"kanban_adapters.repositories -> kanban.spi",
|
|
116
|
+
"kanban_adapters.synchronous -> kanban.spi",
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
# Coverage configuration
|
|
120
|
+
[tool.coverage.run]
|
|
121
|
+
source = ["src"]
|
|
122
|
+
branch = true
|
|
123
|
+
omit = [
|
|
124
|
+
"*/tests/*",
|
|
125
|
+
"*/test_*",
|
|
126
|
+
"*/__init__.py",
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
[tool.coverage.report]
|
|
130
|
+
show_missing = true
|
|
131
|
+
skip_covered = false
|
|
132
|
+
skip_empty = true
|
|
133
|
+
precision = 2
|
|
134
|
+
exclude_lines = [
|
|
135
|
+
"pragma: no cover",
|
|
136
|
+
"def __repr__",
|
|
137
|
+
"if self.debug:",
|
|
138
|
+
"if settings.DEBUG",
|
|
139
|
+
"raise AssertionError",
|
|
140
|
+
"raise NotImplementedError",
|
|
141
|
+
"if 0:",
|
|
142
|
+
"if __name__ == .__main__.:",
|
|
143
|
+
"class .*\\bProtocol\\):",
|
|
144
|
+
"@(abc\\.)?abstractmethod",
|
|
145
|
+
"\\.\\.\\.", # Ellipsis
|
|
146
|
+
"pass",
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
[tool.coverage.html]
|
|
150
|
+
directory = "htmlcov"
|
|
151
|
+
|
|
152
|
+
[tool.coverage.xml]
|
|
153
|
+
output = "coverage.xml"
|
|
154
|
+
|
|
155
|
+
# MyPy configuration
|
|
156
|
+
[tool.mypy]
|
|
157
|
+
python_version = "3.10"
|
|
158
|
+
check_untyped_defs = true
|
|
159
|
+
disallow_any_generics = true
|
|
160
|
+
disallow_incomplete_defs = true
|
|
161
|
+
disallow_untyped_defs = true
|
|
162
|
+
no_implicit_optional = true
|
|
163
|
+
warn_redundant_casts = true
|
|
164
|
+
warn_unused_ignores = true
|
|
165
|
+
warn_return_any = true
|
|
166
|
+
strict_equality = true
|
|
167
|
+
|
|
168
|
+
[dependency-groups]
|
|
169
|
+
dev = [
|
|
170
|
+
"import-linter>=2.13",
|
|
171
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Public API for the kanban business module."""
|
|
2
|
+
|
|
3
|
+
from .exceptions import (
|
|
4
|
+
ConcurrencyError,
|
|
5
|
+
InvalidPersistenceStateError,
|
|
6
|
+
KanbanError,
|
|
7
|
+
NotFoundError,
|
|
8
|
+
PermissionError,
|
|
9
|
+
ValidationError,
|
|
10
|
+
)
|
|
11
|
+
from .facade import BoardView, CardView, ColumnView, KanbanFacade
|
|
12
|
+
|
|
13
|
+
__version__ = "2.1.0"
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"BoardView",
|
|
17
|
+
"CardView",
|
|
18
|
+
"ColumnView",
|
|
19
|
+
"ConcurrencyError",
|
|
20
|
+
"InvalidPersistenceStateError",
|
|
21
|
+
"KanbanError",
|
|
22
|
+
"KanbanFacade",
|
|
23
|
+
"NotFoundError",
|
|
24
|
+
"PermissionError",
|
|
25
|
+
"ValidationError",
|
|
26
|
+
]
|