decorates 3.1.1__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 (40) hide show
  1. decorates-3.1.1/LICENSE +6 -0
  2. decorates-3.1.1/PKG-INFO +279 -0
  3. decorates-3.1.1/README.md +252 -0
  4. decorates-3.1.1/pyproject.toml +58 -0
  5. decorates-3.1.1/setup.cfg +4 -0
  6. decorates-3.1.1/src/decorates/cli/__init__.py +56 -0
  7. decorates-3.1.1/src/decorates/cli/container.py +71 -0
  8. decorates-3.1.1/src/decorates/cli/dispatcher.py +119 -0
  9. decorates-3.1.1/src/decorates/cli/exceptions.py +58 -0
  10. decorates-3.1.1/src/decorates/cli/middleware.py +84 -0
  11. decorates-3.1.1/src/decorates/cli/parser.py +186 -0
  12. decorates-3.1.1/src/decorates/cli/plugins.py +76 -0
  13. decorates-3.1.1/src/decorates/cli/registry.py +271 -0
  14. decorates-3.1.1/src/decorates/cli/utils/reflection.py +65 -0
  15. decorates-3.1.1/src/decorates/cli/utils/typing.py +62 -0
  16. decorates-3.1.1/src/decorates/db/__init__.py +91 -0
  17. decorates-3.1.1/src/decorates/db/decorators.py +314 -0
  18. decorates-3.1.1/src/decorates/db/engine.py +125 -0
  19. decorates-3.1.1/src/decorates/db/exceptions.py +113 -0
  20. decorates-3.1.1/src/decorates/db/fields.py +21 -0
  21. decorates-3.1.1/src/decorates/db/metadata.py +97 -0
  22. decorates-3.1.1/src/decorates/db/operators.py +80 -0
  23. decorates-3.1.1/src/decorates/db/registry.py +1081 -0
  24. decorates-3.1.1/src/decorates/db/relations.py +297 -0
  25. decorates-3.1.1/src/decorates/db/schema.py +263 -0
  26. decorates-3.1.1/src/decorates/db/security.py +61 -0
  27. decorates-3.1.1/src/decorates/db/typing_utils.py +138 -0
  28. decorates-3.1.1/src/decorates/py.typed +1 -0
  29. decorates-3.1.1/src/decorates.egg-info/PKG-INFO +279 -0
  30. decorates-3.1.1/src/decorates.egg-info/SOURCES.txt +38 -0
  31. decorates-3.1.1/src/decorates.egg-info/dependency_links.txt +1 -0
  32. decorates-3.1.1/src/decorates.egg-info/requires.txt +9 -0
  33. decorates-3.1.1/src/decorates.egg-info/top_level.txt +1 -0
  34. decorates-3.1.1/tests/test_cli_registry.py +321 -0
  35. decorates-3.1.1/tests/test_cli_registry_edge_cases.py +396 -0
  36. decorates-3.1.1/tests/test_db_registry.py +947 -0
  37. decorates-3.1.1/tests/test_db_registry_edge_cases.py +268 -0
  38. decorates-3.1.1/tests/test_db_registry_fastapi_integration.py +214 -0
  39. decorates-3.1.1/tests/test_db_registry_migration.py +75 -0
  40. decorates-3.1.1/tests/test_db_registry_spec_features.py +177 -0
@@ -0,0 +1,6 @@
1
+ ## LICENSE
2
+ Copyright (c) [2026] [Charles DeFreese]
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
5
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
6
+
@@ -0,0 +1,279 @@
1
+ Metadata-Version: 2.4
2
+ Name: decorates
3
+ Version: 3.1.1
4
+ Summary: Decorator-driven persistence registry for Pydantic models and CLI tooling
5
+ Author: Charles DeFreese
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/nexustech101/decorates
8
+ Project-URL: Repository, https://github.com/nexustech101/decorates
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: pydantic>=2.0
19
+ Requires-Dist: sqlalchemy>=2.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7.4; extra == "dev"
22
+ Requires-Dist: pytest-cov>=4.1; extra == "dev"
23
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
24
+ Requires-Dist: psycopg[binary]>=3.2; extra == "dev"
25
+ Requires-Dist: pymysql>=1.1; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # Framework
29
+
30
+ Decorator-driven tooling for Python:
31
+
32
+ - `decorates.cli` for ergonomic command-line apps
33
+ - `decorates.db` for Pydantic + SQLAlchemy persistence
34
+
35
+ The philosophy is simple: minimal setup, predictable behavior, and a fast path to shipping.
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pip install decorates
41
+ ```
42
+
43
+ ## Quick Start Guide
44
+
45
+ 1. Build one CLI command with a decorator.
46
+ 2. Build one DB model with a decorator.
47
+ 3. Use `Model.objects` for CRUD.
48
+
49
+ ### CLI in 60 seconds
50
+
51
+ ```python
52
+ from decorates.cli import CommandRegistry
53
+
54
+ cli = CommandRegistry()
55
+
56
+ # ── built-in help alias ────────────────────────────────────────────────────
57
+
58
+ @cli.register(
59
+ options=["-g", "--greet"],
60
+ name="greet",
61
+ description="Greet someone",
62
+ )
63
+ def greet(name: str) -> str:
64
+ return f"Hello, {name}!"
65
+
66
+ @cli.register(
67
+ options=["-h", "--help"],
68
+ name="help",
69
+ description="List all registered commands",
70
+ )
71
+ def list_clis() -> None:
72
+ cli.list_clis()
73
+
74
+
75
+ if __name__ == "__main__":
76
+ cli.run()
77
+ ```
78
+
79
+ ```bash
80
+ python app.py greet Alice
81
+ python app.py --greet Alice
82
+ python app.py g Alice
83
+
84
+ python app.py help
85
+ python app.py --help
86
+ python app.py h
87
+ ```
88
+
89
+ ```bash
90
+ Hello, Alice!
91
+
92
+ Available commands:
93
+ greet [-g, --greet]: Greet someone
94
+ help [-h, --help]: List all registered commands
95
+ ```
96
+
97
+ ### Database + FastAPI in 5 minutes
98
+
99
+ ```python
100
+ from contextlib import asynccontextmanager
101
+ from fastapi import FastAPI, HTTPException
102
+ from pydantic import BaseModel
103
+ from decorates.db import (
104
+ RecordNotFoundError,
105
+ UniqueConstraintError,
106
+ database_registry,
107
+ )
108
+
109
+ DB_URL = "sqlite:///shop.db"
110
+
111
+
112
+ @database_registry(DB_URL, table_name="customers", unique_fields=["email"])
113
+ class Customer(BaseModel):
114
+ id: int | None = None
115
+ name: str
116
+ email: str
117
+
118
+
119
+ @database_registry(DB_URL, table_name="products")
120
+ class Product(BaseModel):
121
+ id: int | None = None
122
+ name: str
123
+ price: float
124
+
125
+
126
+ @database_registry(DB_URL, table_name="orders")
127
+ class Order(BaseModel):
128
+ id: int | None = None
129
+ customer_id: int
130
+ product_id: int
131
+ quantity: int
132
+ total: float
133
+
134
+
135
+ class CreateCustomer(BaseModel):
136
+ name: str
137
+ email: str
138
+
139
+
140
+ class CreateProduct(BaseModel):
141
+ name: str
142
+ price: float
143
+
144
+
145
+ class CreateOrder(BaseModel):
146
+ customer_id: int
147
+ product_id: int
148
+ quantity: int
149
+
150
+
151
+ @asynccontextmanager
152
+ async def lifespan(app: FastAPI):
153
+ for model in (Customer, Product, Order):
154
+ model.create_schema()
155
+ yield
156
+ for model in (Customer, Product, Order):
157
+ model.objects.dispose()
158
+
159
+
160
+ app = FastAPI(lifespan=lifespan)
161
+
162
+
163
+ @app.post("/customers", response_model=Customer, status_code=201)
164
+ def create_customer(payload: CreateCustomer):
165
+ try:
166
+ return Customer.objects.create(**payload.model_dump())
167
+ except UniqueConstraintError:
168
+ raise HTTPException(status_code=409, detail="Email already exists")
169
+
170
+
171
+ @app.get("/customers/{customer_id}", response_model=Customer)
172
+ def get_customer(customer_id: int):
173
+ try:
174
+ return Customer.objects.require(customer_id)
175
+ except RecordNotFoundError:
176
+ raise HTTPException(status_code=404, detail="Customer not found")
177
+
178
+
179
+ @app.post("/products", response_model=Product, status_code=201)
180
+ def create_product(payload: CreateProduct):
181
+ return Product.objects.create(**payload.model_dump())
182
+
183
+
184
+ @app.post("/orders", response_model=Order, status_code=201)
185
+ def create_order(payload: CreateOrder):
186
+ customer = Customer.objects.get(payload.customer_id)
187
+ if customer is None:
188
+ raise HTTPException(status_code=404, detail="Customer not found")
189
+
190
+ product = Product.objects.get(payload.product_id)
191
+ if product is None:
192
+ raise HTTPException(status_code=404, detail="Product not found")
193
+
194
+ return Order.objects.create(
195
+ customer_id=customer.id,
196
+ product_id=product.id,
197
+ quantity=payload.quantity,
198
+ total=product.price * payload.quantity,
199
+ )
200
+
201
+
202
+ @app.get("/orders/desc", response_model=list[Order])
203
+ def list_orders_desc(limit: int = 20, offset: int = 0): # Filter by oldest (1, 2, 3...n)
204
+ return Order.objects.filter(order_by="id", limit=limit, offset=offset)
205
+
206
+
207
+ @app.get("/orders/asc", response_model=list[Order])
208
+ def list_orders_asc(limit: int = 20, offset: int = 0): # Filter by newest (n...3, 2, 1)
209
+ return Order.objects.filter(order_by="-id", limit=limit, offset=offset)
210
+ ```
211
+
212
+ ## Core Concepts
213
+
214
+ ### `decorates.cli`
215
+
216
+ - Register functions as commands with `@cli.register(...)`.
217
+ - Type annotations drive argument parsing.
218
+ - Optional command aliases with `options=["-x", "--long"]`.
219
+ - Optional DI (`DIContainer`) and middleware (`MiddlewareChain`).
220
+ - `CommandRegistry.run()` preserves decorates exceptions and wraps unexpected handler crashes as `CommandExecutionError` (with original exception chaining).
221
+ - Operational logs use standard Python logging namespaces under `decorates.cli.*`.
222
+
223
+ ### `decorates.db`
224
+
225
+ - Register `BaseModel` classes with `@database_registry(...)`.
226
+ - Access all persistence through `Model.objects`.
227
+ - `id: int | None = None` gives database-managed autoincrement IDs.
228
+ - Schema helpers are available as class methods: `create_schema`, `drop_schema`, `schema_exists`, `truncate`.
229
+ - Unexpected SQLAlchemy runtime failures are normalized into `SchemaError` for cleaner, predictable error handling.
230
+ - Operational logs use standard Python logging namespaces under `decorates.db.*`.
231
+ - DB exceptions provide structured metadata (`exc.context`, `exc.to_dict()`) for production diagnostics.
232
+
233
+ ## `decorates.db` Usage Snapshot
234
+
235
+ ```python
236
+ # Filtering operators
237
+ Order.objects.filter(total__gte=100)
238
+ Customer.objects.filter(email__ilike="%@example.com")
239
+ Order.objects.filter(quantity__in=[1, 2, 3])
240
+
241
+ # Sorting and pagination
242
+ Order.objects.filter(order_by="-id", limit=20, offset=0)
243
+
244
+ # Bulk writes
245
+ Product.objects.bulk_create([...])
246
+ Product.objects.bulk_upsert([...])
247
+
248
+ # Additive migration helpers
249
+ Customer.objects.ensure_column("phone", str | None, nullable=True)
250
+ Customer.objects.rename_table("customers_archive")
251
+ ```
252
+
253
+ After `rename_table(...)` succeeds, the same `Model.objects` manager and
254
+ schema helpers are immediately bound to the new table name.
255
+
256
+ If your model contains a field named `password`, password values are automatically hashed on write, and instances receive `verify_password(...)`.
257
+
258
+ ## Documentation
259
+
260
+ - DB guide: `src/decorates/db/USAGE.md`
261
+ - CLI source API: `src/decorates/cli`
262
+ - DB source API: `src/decorates/db`
263
+
264
+ ## Requirements
265
+
266
+ - Python 3.10+
267
+ - `pydantic>=2.0`
268
+ - `sqlalchemy>=2.0`
269
+
270
+ ## Testing
271
+
272
+ - Default `pytest` includes SQLite plus PostgreSQL/MySQL rename-state integration tests.
273
+ - Start Docker Desktop (or another Docker engine) before running tests so
274
+ `docker-compose.test-db.yml` services can boot.
275
+ - The decorates is backed by a rigorous, production-focused test suite (170+ tests) that covers unit, edge-case, and multi-dialect integration behavior.
276
+
277
+ ## License
278
+
279
+ MIT
@@ -0,0 +1,252 @@
1
+ # Framework
2
+
3
+ Decorator-driven tooling for Python:
4
+
5
+ - `decorates.cli` for ergonomic command-line apps
6
+ - `decorates.db` for Pydantic + SQLAlchemy persistence
7
+
8
+ The philosophy is simple: minimal setup, predictable behavior, and a fast path to shipping.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pip install decorates
14
+ ```
15
+
16
+ ## Quick Start Guide
17
+
18
+ 1. Build one CLI command with a decorator.
19
+ 2. Build one DB model with a decorator.
20
+ 3. Use `Model.objects` for CRUD.
21
+
22
+ ### CLI in 60 seconds
23
+
24
+ ```python
25
+ from decorates.cli import CommandRegistry
26
+
27
+ cli = CommandRegistry()
28
+
29
+ # ── built-in help alias ────────────────────────────────────────────────────
30
+
31
+ @cli.register(
32
+ options=["-g", "--greet"],
33
+ name="greet",
34
+ description="Greet someone",
35
+ )
36
+ def greet(name: str) -> str:
37
+ return f"Hello, {name}!"
38
+
39
+ @cli.register(
40
+ options=["-h", "--help"],
41
+ name="help",
42
+ description="List all registered commands",
43
+ )
44
+ def list_clis() -> None:
45
+ cli.list_clis()
46
+
47
+
48
+ if __name__ == "__main__":
49
+ cli.run()
50
+ ```
51
+
52
+ ```bash
53
+ python app.py greet Alice
54
+ python app.py --greet Alice
55
+ python app.py g Alice
56
+
57
+ python app.py help
58
+ python app.py --help
59
+ python app.py h
60
+ ```
61
+
62
+ ```bash
63
+ Hello, Alice!
64
+
65
+ Available commands:
66
+ greet [-g, --greet]: Greet someone
67
+ help [-h, --help]: List all registered commands
68
+ ```
69
+
70
+ ### Database + FastAPI in 5 minutes
71
+
72
+ ```python
73
+ from contextlib import asynccontextmanager
74
+ from fastapi import FastAPI, HTTPException
75
+ from pydantic import BaseModel
76
+ from decorates.db import (
77
+ RecordNotFoundError,
78
+ UniqueConstraintError,
79
+ database_registry,
80
+ )
81
+
82
+ DB_URL = "sqlite:///shop.db"
83
+
84
+
85
+ @database_registry(DB_URL, table_name="customers", unique_fields=["email"])
86
+ class Customer(BaseModel):
87
+ id: int | None = None
88
+ name: str
89
+ email: str
90
+
91
+
92
+ @database_registry(DB_URL, table_name="products")
93
+ class Product(BaseModel):
94
+ id: int | None = None
95
+ name: str
96
+ price: float
97
+
98
+
99
+ @database_registry(DB_URL, table_name="orders")
100
+ class Order(BaseModel):
101
+ id: int | None = None
102
+ customer_id: int
103
+ product_id: int
104
+ quantity: int
105
+ total: float
106
+
107
+
108
+ class CreateCustomer(BaseModel):
109
+ name: str
110
+ email: str
111
+
112
+
113
+ class CreateProduct(BaseModel):
114
+ name: str
115
+ price: float
116
+
117
+
118
+ class CreateOrder(BaseModel):
119
+ customer_id: int
120
+ product_id: int
121
+ quantity: int
122
+
123
+
124
+ @asynccontextmanager
125
+ async def lifespan(app: FastAPI):
126
+ for model in (Customer, Product, Order):
127
+ model.create_schema()
128
+ yield
129
+ for model in (Customer, Product, Order):
130
+ model.objects.dispose()
131
+
132
+
133
+ app = FastAPI(lifespan=lifespan)
134
+
135
+
136
+ @app.post("/customers", response_model=Customer, status_code=201)
137
+ def create_customer(payload: CreateCustomer):
138
+ try:
139
+ return Customer.objects.create(**payload.model_dump())
140
+ except UniqueConstraintError:
141
+ raise HTTPException(status_code=409, detail="Email already exists")
142
+
143
+
144
+ @app.get("/customers/{customer_id}", response_model=Customer)
145
+ def get_customer(customer_id: int):
146
+ try:
147
+ return Customer.objects.require(customer_id)
148
+ except RecordNotFoundError:
149
+ raise HTTPException(status_code=404, detail="Customer not found")
150
+
151
+
152
+ @app.post("/products", response_model=Product, status_code=201)
153
+ def create_product(payload: CreateProduct):
154
+ return Product.objects.create(**payload.model_dump())
155
+
156
+
157
+ @app.post("/orders", response_model=Order, status_code=201)
158
+ def create_order(payload: CreateOrder):
159
+ customer = Customer.objects.get(payload.customer_id)
160
+ if customer is None:
161
+ raise HTTPException(status_code=404, detail="Customer not found")
162
+
163
+ product = Product.objects.get(payload.product_id)
164
+ if product is None:
165
+ raise HTTPException(status_code=404, detail="Product not found")
166
+
167
+ return Order.objects.create(
168
+ customer_id=customer.id,
169
+ product_id=product.id,
170
+ quantity=payload.quantity,
171
+ total=product.price * payload.quantity,
172
+ )
173
+
174
+
175
+ @app.get("/orders/desc", response_model=list[Order])
176
+ def list_orders_desc(limit: int = 20, offset: int = 0): # Filter by oldest (1, 2, 3...n)
177
+ return Order.objects.filter(order_by="id", limit=limit, offset=offset)
178
+
179
+
180
+ @app.get("/orders/asc", response_model=list[Order])
181
+ def list_orders_asc(limit: int = 20, offset: int = 0): # Filter by newest (n...3, 2, 1)
182
+ return Order.objects.filter(order_by="-id", limit=limit, offset=offset)
183
+ ```
184
+
185
+ ## Core Concepts
186
+
187
+ ### `decorates.cli`
188
+
189
+ - Register functions as commands with `@cli.register(...)`.
190
+ - Type annotations drive argument parsing.
191
+ - Optional command aliases with `options=["-x", "--long"]`.
192
+ - Optional DI (`DIContainer`) and middleware (`MiddlewareChain`).
193
+ - `CommandRegistry.run()` preserves decorates exceptions and wraps unexpected handler crashes as `CommandExecutionError` (with original exception chaining).
194
+ - Operational logs use standard Python logging namespaces under `decorates.cli.*`.
195
+
196
+ ### `decorates.db`
197
+
198
+ - Register `BaseModel` classes with `@database_registry(...)`.
199
+ - Access all persistence through `Model.objects`.
200
+ - `id: int | None = None` gives database-managed autoincrement IDs.
201
+ - Schema helpers are available as class methods: `create_schema`, `drop_schema`, `schema_exists`, `truncate`.
202
+ - Unexpected SQLAlchemy runtime failures are normalized into `SchemaError` for cleaner, predictable error handling.
203
+ - Operational logs use standard Python logging namespaces under `decorates.db.*`.
204
+ - DB exceptions provide structured metadata (`exc.context`, `exc.to_dict()`) for production diagnostics.
205
+
206
+ ## `decorates.db` Usage Snapshot
207
+
208
+ ```python
209
+ # Filtering operators
210
+ Order.objects.filter(total__gte=100)
211
+ Customer.objects.filter(email__ilike="%@example.com")
212
+ Order.objects.filter(quantity__in=[1, 2, 3])
213
+
214
+ # Sorting and pagination
215
+ Order.objects.filter(order_by="-id", limit=20, offset=0)
216
+
217
+ # Bulk writes
218
+ Product.objects.bulk_create([...])
219
+ Product.objects.bulk_upsert([...])
220
+
221
+ # Additive migration helpers
222
+ Customer.objects.ensure_column("phone", str | None, nullable=True)
223
+ Customer.objects.rename_table("customers_archive")
224
+ ```
225
+
226
+ After `rename_table(...)` succeeds, the same `Model.objects` manager and
227
+ schema helpers are immediately bound to the new table name.
228
+
229
+ If your model contains a field named `password`, password values are automatically hashed on write, and instances receive `verify_password(...)`.
230
+
231
+ ## Documentation
232
+
233
+ - DB guide: `src/decorates/db/USAGE.md`
234
+ - CLI source API: `src/decorates/cli`
235
+ - DB source API: `src/decorates/db`
236
+
237
+ ## Requirements
238
+
239
+ - Python 3.10+
240
+ - `pydantic>=2.0`
241
+ - `sqlalchemy>=2.0`
242
+
243
+ ## Testing
244
+
245
+ - Default `pytest` includes SQLite plus PostgreSQL/MySQL rename-state integration tests.
246
+ - Start Docker Desktop (or another Docker engine) before running tests so
247
+ `docker-compose.test-db.yml` services can boot.
248
+ - The decorates is backed by a rigorous, production-focused test suite (170+ tests) that covers unit, edge-case, and multi-dialect integration behavior.
249
+
250
+ ## License
251
+
252
+ MIT
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "decorates"
7
+ version = "3.1.1"
8
+ description = "Decorator-driven persistence registry for Pydantic models and CLI tooling"
9
+ readme = { file = "README.md", content-type = "text/markdown" }
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+
13
+ authors = [
14
+ { name = "Charles DeFreese" }
15
+ ]
16
+
17
+ classifiers = [
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Operating System :: OS Independent",
24
+ ]
25
+
26
+ dependencies = [
27
+ "pydantic>=2.0",
28
+ "sqlalchemy>=2.0",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ dev = [
33
+ "pytest>=7.4",
34
+ "pytest-cov>=4.1",
35
+ "pytest-asyncio>=0.23",
36
+ "psycopg[binary]>=3.2",
37
+ "pymysql>=1.1",
38
+ ]
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/nexustech101/decorates"
42
+ Repository = "https://github.com/nexustech101/decorates"
43
+
44
+ [tool.pytest.ini_options]
45
+ testpaths = ["tests"]
46
+ addopts = "-v --tb=short"
47
+ asyncio_mode = "auto"
48
+
49
+ [tool.setuptools.packages.find]
50
+ where = ["src"]
51
+ include = ["decorates*"]
52
+ exclude = ["*.tests*", "*.tests.*"]
53
+
54
+ [tool.setuptools]
55
+ package-dir = {"" = "src"} # Container for package modules
56
+
57
+ [tool.setuptools.package-data]
58
+ decorates = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,56 @@
1
+ """
2
+ A lightweight, decorates-based CLI decorates.
3
+
4
+ Public API surface::
5
+
6
+ from decorates.cli import (
7
+ CommandRegistry,
8
+ DIContainer,
9
+ MiddlewareChain,
10
+ Dispatcher,
11
+ CommandExecutionError,
12
+ build_parser,
13
+ load_plugins,
14
+ logging_middleware_pre,
15
+ logging_middleware_post,
16
+ )
17
+ """
18
+
19
+ from decorates.cli.dispatcher import Dispatcher
20
+ from decorates.cli.middleware import (
21
+ MiddlewareChain,
22
+ logging_middleware_post,
23
+ logging_middleware_pre,
24
+ )
25
+ from decorates.cli.parser import build_parser
26
+ from decorates.cli.container import DIContainer
27
+ from decorates.cli.exceptions import (
28
+ CommandExecutionError,
29
+ DependencyNotFoundError,
30
+ DuplicateCommandError,
31
+ FrameworkError,
32
+ PluginLoadError,
33
+ UnknownCommandError,
34
+ )
35
+ from decorates.cli.registry import CommandRegistry
36
+ from decorates.cli.plugins import load_plugins
37
+
38
+ __all__ = [
39
+ # Core decorates
40
+ "CommandRegistry",
41
+ "DIContainer",
42
+ "Dispatcher",
43
+ "MiddlewareChain",
44
+ "build_parser",
45
+ "load_plugins",
46
+ "logging_middleware_pre",
47
+ "logging_middleware_post",
48
+
49
+ # Exceptions
50
+ "CommandExecutionError",
51
+ "DependencyNotFoundError",
52
+ "DuplicateCommandError",
53
+ "FrameworkError",
54
+ "PluginLoadError",
55
+ "UnknownCommandError",
56
+ ]