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.
- forging_blocks-0.3.6/LICENSE +21 -0
- forging_blocks-0.3.6/PKG-INFO +138 -0
- forging_blocks-0.3.6/README.md +121 -0
- forging_blocks-0.3.6/pyproject.toml +245 -0
- forging_blocks-0.3.6/src/forging_blocks/__init__.py +1 -0
- forging_blocks-0.3.6/src/forging_blocks/application/README.md +136 -0
- forging_blocks-0.3.6/src/forging_blocks/application/__init__.py +1 -0
- forging_blocks-0.3.6/src/forging_blocks/application/ports/__init__.py +32 -0
- forging_blocks-0.3.6/src/forging_blocks/application/ports/inbound/__init__.py +4 -0
- forging_blocks-0.3.6/src/forging_blocks/application/ports/inbound/message_handler.py +41 -0
- forging_blocks-0.3.6/src/forging_blocks/application/ports/inbound/use_case.py +37 -0
- forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/__init__.py +4 -0
- forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/command_sender.py +22 -0
- forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/event_publisher.py +30 -0
- forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/message_bus.py +20 -0
- forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/notifier.py +23 -0
- forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/query_fetcher.py +31 -0
- forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/repository.py +179 -0
- forging_blocks-0.3.6/src/forging_blocks/application/ports/outbound/unit_of_work.py +69 -0
- forging_blocks-0.3.6/src/forging_blocks/domain/README.md +127 -0
- forging_blocks-0.3.6/src/forging_blocks/domain/__init__.py +1 -0
- forging_blocks-0.3.6/src/forging_blocks/domain/aggregate_root.py +84 -0
- forging_blocks-0.3.6/src/forging_blocks/domain/entity.py +87 -0
- forging_blocks-0.3.6/src/forging_blocks/domain/errors/__init__.py +1 -0
- forging_blocks-0.3.6/src/forging_blocks/domain/errors/draft_entity_is_not_hashable_error.py +19 -0
- forging_blocks-0.3.6/src/forging_blocks/domain/errors/entity_id_none_error.py +17 -0
- forging_blocks-0.3.6/src/forging_blocks/domain/messages/__init__.py +8 -0
- forging_blocks-0.3.6/src/forging_blocks/domain/messages/command.py +65 -0
- forging_blocks-0.3.6/src/forging_blocks/domain/messages/event.py +73 -0
- forging_blocks-0.3.6/src/forging_blocks/domain/messages/message.py +255 -0
- forging_blocks-0.3.6/src/forging_blocks/domain/messages/query.py +47 -0
- forging_blocks-0.3.6/src/forging_blocks/domain/value_object.py +94 -0
- forging_blocks-0.3.6/src/forging_blocks/foundation/__init__.py +1 -0
- forging_blocks-0.3.6/src/forging_blocks/foundation/debuggable.py +11 -0
- forging_blocks-0.3.6/src/forging_blocks/foundation/errors/__init__.py +1 -0
- forging_blocks-0.3.6/src/forging_blocks/foundation/errors/base.py +195 -0
- forging_blocks-0.3.6/src/forging_blocks/foundation/errors/cant_modify_immutable_attribute_error.py +22 -0
- forging_blocks-0.3.6/src/forging_blocks/foundation/errors/core.py +28 -0
- forging_blocks-0.3.6/src/forging_blocks/foundation/errors/rule_violation_error.py +18 -0
- forging_blocks-0.3.6/src/forging_blocks/foundation/errors/validation_error.py +18 -0
- forging_blocks-0.3.6/src/forging_blocks/foundation/mapper.py +64 -0
- forging_blocks-0.3.6/src/forging_blocks/foundation/meta/__init__.py +1 -0
- forging_blocks-0.3.6/src/forging_blocks/foundation/meta/final_meta.py +56 -0
- forging_blocks-0.3.6/src/forging_blocks/foundation/ports.py +681 -0
- forging_blocks-0.3.6/src/forging_blocks/foundation/result.py +150 -0
- forging_blocks-0.3.6/src/forging_blocks/foundation/result_mapper.py +103 -0
- forging_blocks-0.3.6/src/forging_blocks/infrastructure/README.md +47 -0
- 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
|
+
[](https://www.python.org/downloads/)
|
|
22
|
+
[](https://python-poetry.org/)
|
|
23
|
+
[](https://mypy-lang.org/)
|
|
24
|
+
[](https://github.com/psf/black)
|
|
25
|
+
[](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
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
[](https://python-poetry.org/)
|
|
7
|
+
[](https://mypy-lang.org/)
|
|
8
|
+
[](https://github.com/psf/black)
|
|
9
|
+
[](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."""
|