forging-blocks 0.3.6__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 (48) hide show
  1. forging_blocks-0.3.6/LICENSE +21 -0
  2. forging_blocks-0.3.6/PKG-INFO +138 -0
  3. forging_blocks-0.3.6/README.md +121 -0
  4. forging_blocks-0.3.6/pyproject.toml +245 -0
  5. forging_blocks-0.3.6/src/forging_blocks/__init__.py +1 -0
  6. forging_blocks-0.3.6/src/forging_blocks/application/README.md +136 -0
  7. forging_blocks-0.3.6/src/forging_blocks/application/__init__.py +1 -0
  8. forging_blocks-0.3.6/src/forging_blocks/application/ports/__init__.py +32 -0
  9. forging_blocks-0.3.6/src/forging_blocks/application/ports/inbound/__init__.py +4 -0
  10. forging_blocks-0.3.6/src/forging_blocks/application/ports/inbound/message_handler.py +41 -0
  11. forging_blocks-0.3.6/src/forging_blocks/application/ports/inbound/use_case.py +37 -0
  12. forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/__init__.py +4 -0
  13. forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/command_sender.py +22 -0
  14. forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/event_publisher.py +30 -0
  15. forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/message_bus.py +20 -0
  16. forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/notifier.py +23 -0
  17. forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/query_fetcher.py +31 -0
  18. forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/repository.py +179 -0
  19. forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/unit_of_work.py +69 -0
  20. forging_blocks-0.3.6/src/forging_blocks/domain/README.md +127 -0
  21. forging_blocks-0.3.6/src/forging_blocks/domain/__init__.py +1 -0
  22. forging_blocks-0.3.6/src/forging_blocks/domain/aggregate_root.py +84 -0
  23. forging_blocks-0.3.6/src/forging_blocks/domain/entity.py +87 -0
  24. forging_blocks-0.3.6/src/forging_blocks/domain/errors/__init__.py +1 -0
  25. forging_blocks-0.3.6/src/forging_blocks/domain/errors/draft_entity_is_not_hashable_error.py +19 -0
  26. forging_blocks-0.3.6/src/forging_blocks/domain/errors/entity_id_none_error.py +17 -0
  27. forging_blocks-0.3.6/src/forging_blocks/domain/messages/__init__.py +8 -0
  28. forging_blocks-0.3.6/src/forging_blocks/domain/messages/command.py +65 -0
  29. forging_blocks-0.3.6/src/forging_blocks/domain/messages/event.py +73 -0
  30. forging_blocks-0.3.6/src/forging_blocks/domain/messages/message.py +255 -0
  31. forging_blocks-0.3.6/src/forging_blocks/domain/messages/query.py +47 -0
  32. forging_blocks-0.3.6/src/forging_blocks/domain/value_object.py +94 -0
  33. forging_blocks-0.3.6/src/forging_blocks/foundation/__init__.py +1 -0
  34. forging_blocks-0.3.6/src/forging_blocks/foundation/debuggable.py +11 -0
  35. forging_blocks-0.3.6/src/forging_blocks/foundation/errors/__init__.py +1 -0
  36. forging_blocks-0.3.6/src/forging_blocks/foundation/errors/base.py +195 -0
  37. forging_blocks-0.3.6/src/forging_blocks/foundation/errors/cant_modify_immutable_attribute_error.py +22 -0
  38. forging_blocks-0.3.6/src/forging_blocks/foundation/errors/core.py +28 -0
  39. forging_blocks-0.3.6/src/forging_blocks/foundation/errors/rule_violation_error.py +18 -0
  40. forging_blocks-0.3.6/src/forging_blocks/foundation/errors/validation_error.py +18 -0
  41. forging_blocks-0.3.6/src/forging_blocks/foundation/mapper.py +64 -0
  42. forging_blocks-0.3.6/src/forging_blocks/foundation/meta/__init__.py +1 -0
  43. forging_blocks-0.3.6/src/forging_blocks/foundation/meta/final_meta.py +56 -0
  44. forging_blocks-0.3.6/src/forging_blocks/foundation/ports.py +681 -0
  45. forging_blocks-0.3.6/src/forging_blocks/foundation/result.py +150 -0
  46. forging_blocks-0.3.6/src/forging_blocks/foundation/result_mapper.py +103 -0
  47. forging_blocks-0.3.6/src/forging_blocks/infrastructure/README.md +47 -0
  48. forging_blocks-0.3.6/src/forging_blocks/presentation/README.md +46 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Glauber Brennon
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.
@@ -0,0 +1,138 @@
1
+ Metadata-Version: 2.1
2
+ Name: forging-blocks
3
+ Version: 0.3.6
4
+ Summary: Composable toolkit for clean, testable, and maintainable Python applications
5
+ Home-page: https://forging-blocks-org.github.io/forging-blocks/
6
+ License: MIT
7
+ Keywords: python library,clean architecture,hexagonal architecture,domain-driven design,ddd,framework-agnostic,software architecture,software design,software engineering,ports and adapters,adapter pattern,application service,repository pattern,message bus,notifier pattern,data mapper,result pattern,result monad,ok,err,either type,debuggable,use case,domain layer,application layer,presentation layer,foundation,forging blocks
8
+ Author: ForgingBlocks Org
9
+ Author-email: forgingblocksorganization91@gmail.com
10
+ Requires-Python: >=3.12,<4.0
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Project-URL: Repository, https://github.com/forging-blocks-org/forging-blocks
15
+ Description-Content-Type: text/markdown
16
+
17
+ # ForgingBlocks
18
+
19
+ Composable **abstractions and interfaces** for writing clean, testable, and maintainable Python code.
20
+
21
+ [![Python](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
22
+ [![Poetry](https://img.shields.io/badge/packaging-poetry-blue.svg)](https://python-poetry.org/)
23
+ [![Type checked: mypy](https://img.shields.io/badge/type%20checked-mypy-blue.svg)](https://mypy-lang.org/)
24
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
25
+ [![Security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)
26
+
27
+ ---
28
+
29
+ ## 🌱 Overview
30
+
31
+ > Not a framework — a **toolkit** of composable contracts and abstractions.
32
+
33
+ **ForgingBlocks** helps you create codebases that are:
34
+ - **Clean** — with clear boundaries and intent
35
+ - **Testable** — by design, through explicit interfaces
36
+ - **Maintainable** — by isolating concerns and dependencies
37
+
38
+ It doesn’t dictate your architecture.
39
+ Instead, it provides **foundations and reusable** abstractions for **forging** your own **blocks**.
40
+
41
+ Isolate external concerns from your core logic you will achieve systems that are adaptable and resilient.
42
+ If you **forge** your own **block** you will achieve software with intent and clarity
43
+ If you use **blocks** you will achieve consistency and reusability.
44
+ **ForgingBlocks** helps you build systems that last.
45
+
46
+ You can use it to:
47
+ - Learn and apply **architecture and design principles**
48
+ - Build **decoupled applications** that scale safely
49
+ - Model systems with **type safety and explicit intent**
50
+ - Experiment with **Clean**, **Hexagonal**, **DDD**, or **Message-Driven** styles
51
+
52
+ ---
53
+
54
+ ## 🧩 Core Concepts
55
+
56
+ > Foundations, not frameworks — ForgingBlocks provides the *language* for clean architecture.
57
+
58
+ This toolkit defines **layer-agnostic foundations** that compose into any design:
59
+
60
+ - `Result`, `Ok`, `Err` → explicit success/failure handling
61
+ - `Port`, `InboundPort`, `OutboundPort` → communication boundaries
62
+ - `Entity`, `ValueObject`, `AggregateRoot` → domain modeling
63
+ - `Repository`, `UnitOfWork` → persistence contracts
64
+ - `Event`, `EventBus`, `CommandHandler` → messaging and orchestration
65
+
66
+ ---
67
+
68
+ ## 🚀 Installation
69
+
70
+ ```bash
71
+ poetry add forging-blocks
72
+ # or
73
+ pip install forging-blocks
74
+ ```
75
+
76
+ ---
77
+
78
+ ## ⚡ Quick Example
79
+
80
+ ```python
81
+ from forging_blocks.foundation import Result, Ok, Err
82
+
83
+ def divide(a: int, b: int) -> Result[int, str]:
84
+ if b == 0:
85
+ return Err("division by zero")
86
+ return Ok(a // b)
87
+
88
+ result = divide(10, 2)
89
+ if result.is_ok():
90
+ print(result.value) # → 5
91
+ ```
92
+
93
+ ---
94
+
95
+ ## 📚 Learn More
96
+
97
+ - [📘 Documentation](https://forging-blocks-org.github.io/forging-blocks/)
98
+ - [🚀 Getting Started Guide](docs/guide/getting-started.md)
99
+ - [🏗️ Architecture Overview](docs/guide/architecture.md)
100
+ - [🧱 Packages & Layers](docs/guide/packages_and_layers.md)
101
+ - [🧩 Release Process](docs/guide/release_guide.md)
102
+
103
+ ---
104
+
105
+ ## 🧠 Why It Matters
106
+
107
+ Most systems fail not because of missing features,
108
+ but because of **tight coupling**, **implicit dependencies**, and **unclear responsibilities**.
109
+
110
+ **ForgingBlocks** helps you *design code intentionally* —
111
+ so your system remains testable, extensible, and adaptable as it grows.
112
+
113
+ ---
114
+
115
+ ## 🤝 Contributing
116
+
117
+ Contributions are welcome! 🎉
118
+
119
+ 1. Fork the repository
120
+ 2. Install dependencies with Poetry
121
+ 3. Run tests and lint checks:
122
+ ```bash
123
+ poetry run poe ci:simulate
124
+ ```
125
+ 4. Submit a pull request with a clear description of your improvement
126
+
127
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
128
+
129
+ ---
130
+
131
+ ## ⚖️ License
132
+
133
+ MIT — see [LICENSE](LICENSE)
134
+
135
+ ---
136
+
137
+ _**ForgingBlocks** — foundations for clean, testable, and maintainable Python architectures._
138
+
@@ -0,0 +1,121 @@
1
+ # ForgingBlocks
2
+
3
+ Composable **abstractions and interfaces** for writing clean, testable, and maintainable Python code.
4
+
5
+ [![Python](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
6
+ [![Poetry](https://img.shields.io/badge/packaging-poetry-blue.svg)](https://python-poetry.org/)
7
+ [![Type checked: mypy](https://img.shields.io/badge/type%20checked-mypy-blue.svg)](https://mypy-lang.org/)
8
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
9
+ [![Security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)
10
+
11
+ ---
12
+
13
+ ## 🌱 Overview
14
+
15
+ > Not a framework — a **toolkit** of composable contracts and abstractions.
16
+
17
+ **ForgingBlocks** helps you create codebases that are:
18
+ - **Clean** — with clear boundaries and intent
19
+ - **Testable** — by design, through explicit interfaces
20
+ - **Maintainable** — by isolating concerns and dependencies
21
+
22
+ It doesn’t dictate your architecture.
23
+ Instead, it provides **foundations and reusable** abstractions for **forging** your own **blocks**.
24
+
25
+ Isolate external concerns from your core logic you will achieve systems that are adaptable and resilient.
26
+ If you **forge** your own **block** you will achieve software with intent and clarity
27
+ If you use **blocks** you will achieve consistency and reusability.
28
+ **ForgingBlocks** helps you build systems that last.
29
+
30
+ You can use it to:
31
+ - Learn and apply **architecture and design principles**
32
+ - Build **decoupled applications** that scale safely
33
+ - Model systems with **type safety and explicit intent**
34
+ - Experiment with **Clean**, **Hexagonal**, **DDD**, or **Message-Driven** styles
35
+
36
+ ---
37
+
38
+ ## 🧩 Core Concepts
39
+
40
+ > Foundations, not frameworks — ForgingBlocks provides the *language* for clean architecture.
41
+
42
+ This toolkit defines **layer-agnostic foundations** that compose into any design:
43
+
44
+ - `Result`, `Ok`, `Err` → explicit success/failure handling
45
+ - `Port`, `InboundPort`, `OutboundPort` → communication boundaries
46
+ - `Entity`, `ValueObject`, `AggregateRoot` → domain modeling
47
+ - `Repository`, `UnitOfWork` → persistence contracts
48
+ - `Event`, `EventBus`, `CommandHandler` → messaging and orchestration
49
+
50
+ ---
51
+
52
+ ## 🚀 Installation
53
+
54
+ ```bash
55
+ poetry add forging-blocks
56
+ # or
57
+ pip install forging-blocks
58
+ ```
59
+
60
+ ---
61
+
62
+ ## ⚡ Quick Example
63
+
64
+ ```python
65
+ from forging_blocks.foundation import Result, Ok, Err
66
+
67
+ def divide(a: int, b: int) -> Result[int, str]:
68
+ if b == 0:
69
+ return Err("division by zero")
70
+ return Ok(a // b)
71
+
72
+ result = divide(10, 2)
73
+ if result.is_ok():
74
+ print(result.value) # → 5
75
+ ```
76
+
77
+ ---
78
+
79
+ ## 📚 Learn More
80
+
81
+ - [📘 Documentation](https://forging-blocks-org.github.io/forging-blocks/)
82
+ - [🚀 Getting Started Guide](docs/guide/getting-started.md)
83
+ - [🏗️ Architecture Overview](docs/guide/architecture.md)
84
+ - [🧱 Packages & Layers](docs/guide/packages_and_layers.md)
85
+ - [🧩 Release Process](docs/guide/release_guide.md)
86
+
87
+ ---
88
+
89
+ ## 🧠 Why It Matters
90
+
91
+ Most systems fail not because of missing features,
92
+ but because of **tight coupling**, **implicit dependencies**, and **unclear responsibilities**.
93
+
94
+ **ForgingBlocks** helps you *design code intentionally* —
95
+ so your system remains testable, extensible, and adaptable as it grows.
96
+
97
+ ---
98
+
99
+ ## 🤝 Contributing
100
+
101
+ Contributions are welcome! 🎉
102
+
103
+ 1. Fork the repository
104
+ 2. Install dependencies with Poetry
105
+ 3. Run tests and lint checks:
106
+ ```bash
107
+ poetry run poe ci:simulate
108
+ ```
109
+ 4. Submit a pull request with a clear description of your improvement
110
+
111
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
112
+
113
+ ---
114
+
115
+ ## ⚖️ License
116
+
117
+ MIT — see [LICENSE](LICENSE)
118
+
119
+ ---
120
+
121
+ _**ForgingBlocks** — foundations for clean, testable, and maintainable Python architectures._
@@ -0,0 +1,245 @@
1
+ [tool.poetry]
2
+ name = "forging-blocks"
3
+ version = "0.3.6"
4
+ description = "Composable toolkit for clean, testable, and maintainable Python applications"
5
+ authors = ["ForgingBlocks Org <forgingblocksorganization91@gmail.com>"]
6
+ license = "MIT"
7
+ readme = "README.md"
8
+ repository = "https://github.com/forging-blocks-org/forging-blocks"
9
+ homepage = "https://forging-blocks-org.github.io/forging-blocks/"
10
+ packages = [{ include = "forging_blocks", from = "src" }]
11
+ keywords = [
12
+ "python library",
13
+ "clean architecture",
14
+ "hexagonal architecture",
15
+ "domain-driven design",
16
+ "ddd",
17
+ "framework-agnostic",
18
+ "software architecture",
19
+ "software design",
20
+ "software engineering",
21
+ "ports and adapters",
22
+ "adapter pattern",
23
+ "application service",
24
+ "repository pattern",
25
+ "message bus",
26
+ "notifier pattern",
27
+ "data mapper",
28
+ "result pattern",
29
+ "result monad",
30
+ "ok",
31
+ "err",
32
+ "either type",
33
+ "debuggable",
34
+ "use case",
35
+ "domain layer",
36
+ "application layer",
37
+ "presentation layer",
38
+ "foundation",
39
+ "forging blocks",
40
+ ]
41
+
42
+ [tool.poetry.dependencies]
43
+ python = "^3.12"
44
+
45
+ [tool.poetry.group.dev.dependencies]
46
+ mypy = "^1.16.1"
47
+ ruff = "^0.14.3"
48
+ black = "^25.1.0"
49
+ pytest = "^8.3.3"
50
+ pytest-cov = "^5.0.0"
51
+ pytest-asyncio = "^0.25.0"
52
+ bandit = "^1.7.10"
53
+ pre-commit = "^4.2.0"
54
+ pyright = "^1.1.404"
55
+ poethepoet = "^0.37.0"
56
+ libcst = "^1.8.6"
57
+
58
+ [tool.poetry.group.docs.dependencies]
59
+ mkdocs = "^1.6.1"
60
+ mkdocs-material = "^9.6.23"
61
+ mkdocstrings = {extras = ["python"], version = "^0.30.1"}
62
+ mkdocs-gen-files = "^0.5.0"
63
+ mkdocs-literate-nav = "^0.6.2"
64
+ mkdocs-autorefs = "^1.4.3"
65
+ mkdocs-mermaid2-plugin = "^1.2.3"
66
+ mkdocs-section-index = "^0.3.10"
67
+
68
+ [build-system]
69
+ requires = ["poetry-core"]
70
+ build-backend = "poetry.core.masonry.api"
71
+
72
+ [tool.ruff]
73
+ target-version = "py312"
74
+ line-length = 100
75
+ src = ["src", "tests", "scripts"]
76
+ fix = true
77
+ extend-exclude = ["site"]
78
+
79
+ [tool.ruff.lint]
80
+ select = ["E", "W", "F", "I", "B", "C4", "D"]
81
+ ignore = [
82
+ "B008",
83
+ "D105",
84
+ "D106",
85
+ "D107",
86
+ "D203",
87
+ "D213",
88
+ ]
89
+
90
+ [tool.ruff.lint.pydocstyle]
91
+ convention = "google"
92
+
93
+ [tool.ruff.lint.isort]
94
+ known-first-party = ["forging_blocks"]
95
+
96
+ [tool.ruff.lint.per-file-ignores]
97
+ "tests/**/*" = ["D"]
98
+ "scripts/*.py" = ["D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107"]
99
+
100
+ [tool.ruff.format]
101
+ quote-style = "double"
102
+ indent-style = "space"
103
+ docstring-code-format = true
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # Mypy Configuration
107
+ # ---------------------------------------------------------------------------
108
+ [tool.mypy]
109
+ python_version = "3.12"
110
+ warn_return_any = true
111
+ warn_unused_configs = true
112
+ disallow_untyped_defs = true
113
+ disallow_incomplete_defs = false
114
+ allow_untyped_globals = true
115
+ check_untyped_defs = false
116
+ no_implicit_optional = true
117
+ warn_redundant_casts = true
118
+ warn_unused_ignores = true
119
+ strict_equality = true
120
+ show_error_codes = true
121
+ mypy_path = "src"
122
+ exclude = ["^tests/"]
123
+
124
+ [[tool.mypy.overrides]]
125
+ module = "src.*"
126
+ disallow_incomplete_defs = true
127
+ check_untyped_defs = true
128
+
129
+ [[tool.mypy.overrides]]
130
+ module = "tests.*"
131
+ disallow_untyped_defs = true
132
+ disallow_incomplete_defs = false
133
+ check_untyped_defs = false
134
+ allow_untyped_globals = true
135
+
136
+ # ---------------------------------------------------------------------------
137
+ # Pytest Configuration
138
+ # ---------------------------------------------------------------------------
139
+ [tool.pytest.ini_options]
140
+ testpaths = ["tests"]
141
+ python_files = ["test_*.py"]
142
+ asyncio_mode = "auto"
143
+ addopts = [
144
+ "--strict-markers",
145
+ "--strict-config",
146
+ "--cov=forging_blocks",
147
+ "--cov-report=term-missing",
148
+ ]
149
+
150
+ # ---------------------------------------------------------------------------
151
+ # Coverage Configuration
152
+ # ---------------------------------------------------------------------------
153
+ [tool.coverage.report]
154
+ exclude_lines = [
155
+ "@abstractmethod"
156
+ ]
157
+
158
+ [tool.coverage.run]
159
+ omit = [
160
+ "src/forging_blocks/application/ports/inbound/*.py",
161
+ "src/forging_blocks/application/ports/outbound/*.py",
162
+ "src/forging_blocks/foundation/mapper.py",
163
+ "src/forging_blocks/foundation/result_mapper.py",
164
+ "src/forging_blocks/foundation/ports.py",
165
+ "src/forging_blocks/foundation/debuggable.py"
166
+ ]
167
+
168
+ [tool.black]
169
+ line-length = 100
170
+ target-version = ["py312"]
171
+
172
+ [tool.flake8]
173
+ max-line-length = 100
174
+ extend-ignore = "E203,W503"
175
+ class-order-style = "google"
176
+
177
+ [tool.flake8-class-attributes-order]
178
+ order = [
179
+ "__new__",
180
+ "__init__",
181
+ "classmethod",
182
+ "staticmethod",
183
+ "property",
184
+ "method",
185
+ "dunder"
186
+ ]
187
+
188
+ class_attributes_order_style = "custom"
189
+
190
+ [tool.poe.tasks]
191
+ lint = { cmd = "ruff check ." }
192
+
193
+ "lint:docs" = { cmd = "ruff check --select D100,D101,D102,D103,D104 src" }
194
+
195
+ "lint:fix" = { cmd = "ruff check . --fix" }
196
+
197
+ format.sequence = [
198
+ { cmd = "ruff check . --fix" },
199
+ { cmd = "ruff check --fix --unsafe-fixes src" },
200
+ ]
201
+ "format:docs" = { cmd = "ruff check --select D --fix --unsafe-fixes src" }
202
+ "type:mypy" = { cmd = "mypy --config-file pyproject.toml src" }
203
+ "type:pyright" = { cmd = "pyright" }
204
+ bandit = { cmd = "bandit -r src -x tests" }
205
+ pre-commit = { cmd = "pre-commit run --all-files" }
206
+ test = { cmd = "pytest -q" }
207
+ "test:quick" = { cmd = "pytest -x -q" }
208
+ "test:strict" = { cmd = "pytest -x --strict-markers" }
209
+ "test:nocov" = { cmd = "pytest -q --no-cov" }
210
+ "test:pipeline" = { cmd = "pytest --cov=src --cov-report=xml --cov-fail-under=80" }
211
+ "mkdocs:serve" = { cmd = "mkdocs serve" }
212
+ "mkdocs:build" = { cmd = "mkdocs build" }
213
+ "mkdocs:clean" = { cmd = "mkdocs build --clean" }
214
+ "mkdocs:deploy" = { cmd = "mkdocs gh-deploy --clean" }
215
+ "docs:generate" = { cmd = "python scripts/generate_autodoc_pages.py" }
216
+ "docs:serve".sequence = [
217
+ { cmd = "mkdocs build --clean" },
218
+ { cmd = "mkdocs serve" },
219
+ ]
220
+ "docs:check".sequence = [
221
+ { cmd = "ruff check --select D" },
222
+ { cmd = "mkdocs build --strict" },
223
+ ]
224
+ "docs:clean" = { cmd = "git restore mkdocs.yml" }
225
+ "ci:simulate".sequence = [
226
+ { cmd = "poetry install --with dev,docs" },
227
+ { cmd = "poetry run poe lint" },
228
+ { cmd = "poetry run poe type:mypy" },
229
+ { cmd = "poetry run poe test:pipeline" },
230
+ { cmd = "poetry run python scripts/generate_autodoc_pages.py" },
231
+ { shell = "poetry run mkdocs build --strict || echo '❌ MkDocs build failed (warnings treated as errors). See log above.'" },
232
+ { cmd = "git restore mkdocs.yml" } # revert autogenerated changes
233
+ ]
234
+ "ci:clean".sequence = [
235
+ { cmd = "rm -rf .venv ~/.cache/pypoetry" },
236
+ { cmd = "poetry env use 3.12" },
237
+ { cmd = "poetry lock --no-update" },
238
+ { cmd = "poetry install --with dev,docs" },
239
+ { cmd = "poetry run poe lint" },
240
+ { cmd = "poetry run poe type:mypy" },
241
+ { cmd = "poetry run poe test:pipeline" },
242
+ { cmd = "poetry run python scripts/generate_autodoc_pages.py" },
243
+ { cmd = "poetry run mkdocs build --strict" },
244
+ { cmd = "git restore mkdocs.yml" } # clean after run
245
+ ]
@@ -0,0 +1 @@
1
+ """ForgingBlocks package initialization."""
@@ -0,0 +1,136 @@
1
+ # Application Layer ⚙️
2
+
3
+ The **application layer** orchestrates use cases, coordinates domain logic, and manages cross-cutting concerns such as transactions and notifications.
4
+ It acts as a bridge between the domain layer (business logic) and the outside world (presentation, infrastructure, external services).
5
+
6
+ ---
7
+
8
+ ## 📁 Directory Structure
9
+
10
+ ```
11
+ application/
12
+ ├── ports/
13
+ │ ├── inbound/
14
+ │ │ └── use_case.py # Abstract base for use cases/handlers
15
+ │ └── outbound/
16
+ │ ├── event_publisher.py # Contract for publishing integration events
17
+ │ ├── notifier.py # Contract for sending notifications
18
+ │ └── unit_of_work.py # Contract for transaction management
19
+ └── services/ # Implementations of application use cases
20
+ ```
21
+
22
+ ---
23
+
24
+ ## ✨ Core Concepts
25
+
26
+ ### 1. **Application Inbound Ports**
27
+ - **Purpose:** Define the entry points for your application's business workflows (use cases).
28
+ - **What goes here:** Abstract base classes/interfaces for commands, queries, and use cases.
29
+ - **Example:** `AsyncUseCase` and `SyncUseCase` ABCs in `ports/inbound/use_case.py`.
30
+
31
+ ### 2. **Application Services**
32
+ - **Purpose:** Implement the business workflows and coordinate domain objects, repositories, and outbound ports.
33
+ - **What goes here:** Concrete classes that implement inbound port interfaces and orchestrate use cases.
34
+ - **Example:** `services/CreateUserService` (you provide your own implementations).
35
+
36
+ ### 3. **Application Outbound Ports**
37
+ - **Purpose:** Abstract external systems or cross-cutting concerns that the application interacts with.
38
+ - **What goes here:** Interfaces for things like event publishing, notifications, and transaction management.
39
+ - **Examples:**
40
+ - `event_publisher.py`: Publish integration/application events
41
+ - `notifier.py`: Send notifications (email, SMS, etc.)
42
+ - `unit_of_work.py`: Coordinate transactional boundaries for use cases
43
+
44
+ ---
45
+
46
+ ## 🧩 How to Use
47
+
48
+ > **Best Practice:**
49
+ > Application services (use cases) should use DTOs (Data Transfer Objects) as their input and output types, not domain entities.
50
+ > This keeps your application layer decoupled from domain and presentation concerns, and ensures a stable contract between layers.
51
+
52
+ ### 1. Define Use Case, Request, and Response (with type hints, using AsyncUseCase or SyncUseCase)
53
+
54
+ ```python
55
+ from abc import ABC, abstractmethod
56
+ from dataclasses import dataclass
57
+
58
+ from forging_blocks.application.ports.inbound.use_case import AsyncUseCase
59
+
60
+ @dataclass(frozen=True)
61
+ class CreateUserRequest:
62
+ email: str
63
+ name: str
64
+
65
+ @dataclass(frozen=True)
66
+ class CreateUserResponse:
67
+ user_id: str
68
+
69
+ class CreateUserUseCase(AsyncUseCase[CreateUserRequest, CreateUserResponse], ABC):
70
+ @abstractmethod
71
+ async def execute(self, request: CreateUserRequest) -> CreateUserResponse:
72
+ """
73
+ Execute the use case to create a user.
74
+
75
+ Args:
76
+ request: The CreateUserRequest DTO with input data.
77
+
78
+ Returns:
79
+ CreateUserResponse DTO with the result user_id.
80
+ """
81
+ ```
82
+
83
+ > You can use either `AsyncUseCase` or `SyncUseCase` for your interfaces, depending on your application's needs.
84
+ > Keeping request and response DTOs close to the use case interface helps with discoverability and cohesion.
85
+
86
+ ### 2. Implement the Use Case
87
+
88
+ ```python
89
+ from forging_blocks.application.ports.outbound.notifier import AsyncNotifier
90
+ from forging_blocks.application.ports.outbound.unit_of_work import (
91
+ AsyncUnitOfWork
92
+ )
93
+ from forging_blocks.domain.ports.outbound.repository import AsyncRepository
94
+
95
+ class CreateUserService(CreateUserUseCase):
96
+ def __init__(
97
+ self,
98
+ user_repo: AsyncRepository,
99
+ notifier: AsyncNotifier,
100
+ uow: AsyncUnitOfWork
101
+ ) -> None:
102
+ self._user_repo = user_repo
103
+ self._notifier = notifier
104
+ self._uow = uow
105
+
106
+ async def execute(self, request: CreateUserRequest) -> CreateUserResponse:
107
+ async with self._uow:
108
+ user = User(...)
109
+ # Create a new User entity, possibly using a factory method
110
+ await self._user_repo.save(user)
111
+ await self._notifier.notify(...)
112
+ # Send a notification (e.g., welcome email)
113
+ return CreateUserResponse(user_id=user.id)
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 🏗️ Why This Matters
119
+
120
+ - **Separation of Concerns:** Keeps business workflows free from technical details.
121
+ - **Testability:** Use cases can be tested by mocking outbound ports.
122
+ - **Flexibility:** Infrastructure can be swapped (e.g., different notification services) without changing application logic.
123
+ - **Explicit Boundaries:** Makes dependencies and orchestration visible and intentional.
124
+ - **Decoupling:** Using DTOs for input/output prevents leaking domain details to the outside world.
125
+
126
+ ---
127
+
128
+ ## 🧑‍💻 Extending the Application Layer
129
+
130
+ - **Add new inbound ports** for new use cases.
131
+ - **Add new outbound ports** for new integrations (e.g., background jobs, analytics, etc.).
132
+ - **Implement services** for each use case, orchestrating domain and infrastructure as needed.
133
+
134
+ ---
135
+
136
+ **For more examples and usage, see the project root [README](../../README.md) and the `/examples` directory.**
@@ -0,0 +1 @@
1
+ """ForgingBlocks for application-specific modules."""