stratae 0.0.1a0__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 (35) hide show
  1. stratae-0.0.1a0/PKG-INFO +303 -0
  2. stratae-0.0.1a0/pyproject.toml +117 -0
  3. stratae-0.0.1a0/readme.md +273 -0
  4. stratae-0.0.1a0/setup.cfg +4 -0
  5. stratae-0.0.1a0/stratae/__init__.py +31 -0
  6. stratae-0.0.1a0/stratae/cache/__init__.py +8 -0
  7. stratae-0.0.1a0/stratae/cache/cache.py +44 -0
  8. stratae-0.0.1a0/stratae/cache/memory.py +64 -0
  9. stratae-0.0.1a0/stratae/cache/thread.py +62 -0
  10. stratae-0.0.1a0/stratae/cache/util.py +8 -0
  11. stratae-0.0.1a0/stratae/context/__init__.py +5 -0
  12. stratae-0.0.1a0/stratae/context/context.py +58 -0
  13. stratae-0.0.1a0/stratae/depends/__init__.py +13 -0
  14. stratae-0.0.1a0/stratae/depends/_wrappers.py +124 -0
  15. stratae-0.0.1a0/stratae/depends/depends.py +59 -0
  16. stratae-0.0.1a0/stratae/depends/exceptions.py +21 -0
  17. stratae-0.0.1a0/stratae/depends/inject.py +49 -0
  18. stratae-0.0.1a0/stratae/depends/resolver.py +161 -0
  19. stratae-0.0.1a0/stratae/integrations/asgi.py +38 -0
  20. stratae-0.0.1a0/stratae/lifecycle/__init__.py +9 -0
  21. stratae-0.0.1a0/stratae/lifecycle/_context.py +63 -0
  22. stratae-0.0.1a0/stratae/lifecycle/_decorators.py +159 -0
  23. stratae-0.0.1a0/stratae/lifecycle/_scope.py +73 -0
  24. stratae-0.0.1a0/stratae/lifecycle/_wrappers.py +173 -0
  25. stratae-0.0.1a0/stratae/lifecycle/async_lifecycle.py +157 -0
  26. stratae-0.0.1a0/stratae/lifecycle/exceptions.py +21 -0
  27. stratae-0.0.1a0/stratae/lifecycle/lifecycle.py +151 -0
  28. stratae-0.0.1a0/stratae/lifecycle/manage.py +33 -0
  29. stratae-0.0.1a0/stratae/lifecycle/resource.py +168 -0
  30. stratae-0.0.1a0/stratae/lifecycle/scope.py +73 -0
  31. stratae-0.0.1a0/stratae.egg-info/PKG-INFO +303 -0
  32. stratae-0.0.1a0/stratae.egg-info/SOURCES.txt +33 -0
  33. stratae-0.0.1a0/stratae.egg-info/dependency_links.txt +1 -0
  34. stratae-0.0.1a0/stratae.egg-info/requires.txt +15 -0
  35. stratae-0.0.1a0/stratae.egg-info/top_level.txt +1 -0
@@ -0,0 +1,303 @@
1
+ Metadata-Version: 2.4
2
+ Name: stratae
3
+ Version: 0.0.1a0
4
+ Summary: A toolkit of composable tools for dependency injection, lifecycle management, and more.
5
+ Author: Scott Wahl
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Eudaetics/stratae
8
+ Project-URL: Repository, https://github.com/Eudaetics/stratae
9
+ Project-URL: Issues, https://github.com/Eudaetics/stratae/issues
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ Provides-Extra: dev
19
+ Requires-Dist: pre-commit>=4.2.0; extra == "dev"
20
+ Requires-Dist: pyright>=1.1.403; extra == "dev"
21
+ Requires-Dist: ruff>=0.12.2; extra == "dev"
22
+ Provides-Extra: test
23
+ Requires-Dist: pytest>=8.4.1; extra == "test"
24
+ Requires-Dist: pytest-asyncio>=1.0.0; extra == "test"
25
+ Requires-Dist: pytest-cov>=6.1.1; extra == "test"
26
+ Requires-Dist: pytest-mock>=3.14.1; extra == "test"
27
+ Provides-Extra: asgi
28
+ Requires-Dist: httpx>=0.28.1; extra == "asgi"
29
+ Requires-Dist: fastapi>=0.100.0; extra == "asgi"
30
+
31
+ # Stratae
32
+
33
+ **Composable tools for dependency injection in Python. Fast, simple, and framework-agnostic.**
34
+
35
+ Stratae is a toolkit designed of small, focused components that layer together to create more powerful systems. It's built to complement Python's 3.12+ features, leveraging decorators, contexts, and functions to create a system that works anywhere.
36
+
37
+ Write your business logic once, use it everywhere: APIs, CLIs, workers, and tests.
38
+
39
+ ```bash
40
+ pip install stratae
41
+ ```
42
+
43
+ ## Quick Start
44
+
45
+ ```python
46
+ from stratae.depends import Depends, inject
47
+ from stratae.lifecycle import Lifecycle
48
+
49
+ lifecycle = Lifecycle(["application"])
50
+
51
+ type Database = dict[str, list[dict[str, str]]]
52
+
53
+ # Simple database connection (just a dict for demo)
54
+ @lifecycle.cache('application')
55
+ def get_database() -> Database:
56
+ return {"users": []}
57
+
58
+ @inject
59
+ def create_user(name: str, db: Database = Depends(get_database)):
60
+ user = {"name": name}
61
+ db["users"].append(user)
62
+ return user
63
+
64
+ # Use anywhere: APIs, CLIs, workers, tests
65
+ with lifecycle.start('application'):
66
+ user = create_user("Alice")
67
+ print(f"Created user: {user['name']}")
68
+ ```
69
+
70
+ ## Why Stratae?
71
+
72
+ **Simple by design.** Stratae doesn't impose architectural patterns or force you into a framework. It provides focused tools that solve specific problems: dependency injection, lifecycle management, and context variables. Use what you need, ignore what you don't.
73
+
74
+ **Zero lock-in.** Your business logic is just functions with decorators. Remove Stratae anytime by replacing `Depends()` with actual values. No container to untangle, no framework to escape.
75
+
76
+ **Built for performance.** Stratae keeps overhead minimal through straightforward design:
77
+
78
+ - Analyze dependencies once at decoration time
79
+ - No runtime introspection or provider lookups
80
+ - Direct function calls with minimal indirection
81
+
82
+ The result is a system that minimizes overhead and stays out of your way. If you're using a system that has dependency injection, we encourage you to test it yourself. Change one small piece to use Stratae and see if it works for you.
83
+
84
+ ## Features
85
+
86
+ ### Dependency Injection
87
+
88
+ Dependency injection in Stratae uses familiar decorator syntax that works with any callable. Use this to send values, objects, or anything into a function.
89
+
90
+ ```python
91
+ from stratae.depends import Depends, inject
92
+
93
+ def get_config():
94
+ return {"env": "dev", "mode": "strict"}
95
+
96
+ # Decorate the function with inject to resolve dependencies
97
+ @inject
98
+ # Use Depends(...) to mark parameters for injection
99
+ def endpoint(config: dict[str, str] = Depends(get_config)):
100
+ print(f"Environment: {config['env']}, Mode: {config['mode']}")
101
+
102
+ endpoint()
103
+ # Environment: dev, Mode: strict
104
+ ```
105
+
106
+ ### Lifecycle Management
107
+
108
+ Use lifecycle management when you want to cache objects or guarantee resource cleanup for context managers. With managed resources, everything is cleaned up automatically at the end of a lifecycle scope.
109
+
110
+ ```python
111
+ # Set up the lifecycle with your application scopes
112
+ lifecycle = Lifecycle(['application', 'request'])
113
+
114
+ # Lifecycle will cache the yielded value and return it for all calls within a request
115
+ @lifecycle.cache('request')
116
+ # Mark get_session as a contextmanager that will be auto-entered to get the session
117
+ @managed
118
+ def get_session():
119
+ session = Session()
120
+ try:
121
+ yield session
122
+ session.commit()
123
+ except:
124
+ session.rollback()
125
+ raise
126
+ finally:
127
+ session.close()
128
+
129
+ # Set up your lifecycle boundaries
130
+ with lifecycle.start('application'):
131
+ with lifecycle.start('request'):
132
+ # Session is created at first call and cached automatically
133
+ # All get_session calls in this request will return the same session
134
+ db = get_session()
135
+ db.users.create_user('John')
136
+ with lifecycle.start('request'):
137
+ # New request, new session
138
+ db = get_session()
139
+ ```
140
+
141
+ ### Context Variables
142
+
143
+ To enable decoupled systems, Stratae uses context variables for setting values that need to be shared among components. This is particularly useful for setting values at runtime that are needed deep in dependency chains. Change values at runtime, or even whole behavior, without needing to thread parameters or manipulate overrides.
144
+
145
+ ```python
146
+ from stratae.context import Context
147
+
148
+ lifecycle = Lifecycle('request')
149
+ user_id = Context[int]("user_id")
150
+
151
+ @lifecycle.cache('request')
152
+ @inject
153
+ def get_current_user(uid: int = Depends(user_id)) -> User:
154
+ return fetch_user(uid)
155
+
156
+ @inject
157
+ def create_post(
158
+ content: str,
159
+ user: User = Depends(get_current_user),
160
+ ) -> Post:
161
+ return Post(author=user, content=content)
162
+
163
+ with lifecycle.start('request'), user_id.use(123):
164
+ post = create_post("Hello world!")
165
+ ```
166
+
167
+ ### Async Support
168
+
169
+ Stratae is fully async compatible. Injection natively works with sync or async functions. Lifecycle offers versions for sync and async handling of resources.
170
+
171
+ ```python
172
+ from stratae.depends import Depends, inject
173
+ from stratae.lifecycle import AsyncLifecycle
174
+
175
+ lifecycle = AsyncLifecycle(['application', 'request'])
176
+
177
+ @lifecycle.cache('application')
178
+ async def get_database() -> Database:
179
+ return await Database(url="postgresql://...")
180
+
181
+ @inject
182
+ async def create_user(
183
+ name: str,
184
+ db: Database = Depends(get_database),
185
+ ) -> User:
186
+ return await db.users.create(name=name)
187
+
188
+ # Use anywhere: APIs, CLIs, workers, tests
189
+ async with lifecycle.start('application'):
190
+ async with lifecycle.start('request'):
191
+ user = await create_user("Alice")
192
+ ```
193
+
194
+ ### Easy Testing
195
+
196
+ With no complex configuration, testing functions decorated with Stratae is easy. The function signature isn't changed, just pass in the mocks you need.
197
+
198
+ ```python
199
+ @inject
200
+ def create_user(name: str, db = Depends(get_db)):
201
+ db.user.create(name=name)
202
+
203
+ # Use normally
204
+ create_user('Steve')
205
+
206
+ # Test
207
+ create_user('Jason', db=MockDB())
208
+ ```
209
+
210
+ ### Framework Agnostic
211
+
212
+ Stratae doesn't have a complex framework to configure or objects to pass around. Write your business logic once with injection, then simply call those functions anywhere.
213
+
214
+ ```python
215
+ # Business logic - framework-independent
216
+ @inject
217
+ async def create_user(
218
+ name: str,
219
+ db: Database = Depends(get_database),
220
+ ) -> User:
221
+ return await db.users.create(name=name)
222
+
223
+ # FastAPI
224
+ @app.post("/users")
225
+ async def api_create(name: str):
226
+ return await create_user(name)
227
+
228
+ # CLI
229
+ @click.command()
230
+ def cli_create(name: str):
231
+ asyncio.run(create_user(name))
232
+
233
+ # Tests
234
+ async def test_create():
235
+ user = await create_user("Alice", db=mock_db)
236
+ ```
237
+
238
+ ### Simple Integrations
239
+
240
+ The design of Stratae means integrating with other tools or frameworks is typically easy. For FastAPI, an ASGI middleware that starts the request lifecycle is enough to add Stratae's lifecycle management.
241
+
242
+ ```python
243
+ from fastapi import FastAPI
244
+ from stratae.integrations import RequestLifecycleMiddleware
245
+ from stratae.lifecycle import AsyncLifecycle, managed
246
+
247
+
248
+ app = FastAPI()
249
+ lifecycle = AsyncLifecycle(['request'])
250
+
251
+ # Add the middleware that starts a lifecycle request
252
+ app.add_middleware(RequestLifecycleMiddleware, lifecycle, 'request')
253
+
254
+ # Everything that needs the session will get the same session
255
+ @lifecycle.cache('request')
256
+ @managed
257
+ async def get_session():
258
+ session = AsyncSession()
259
+ try:
260
+ yield session
261
+ session.commit()
262
+ except:
263
+ session.rollback()
264
+ raise
265
+ finally:
266
+ session.close()
267
+
268
+ @inject
269
+ async def create_user(
270
+ name: str,
271
+ # Using Stratae Depends
272
+ db: Session = Depends(get_session)
273
+ ):
274
+ await db.users.create(name=name)
275
+
276
+ # Every request will get a new session
277
+ @app.post('/users')
278
+ async def post_user(name: str):
279
+ await create_user(name)
280
+ ```
281
+
282
+ ## Documentation
283
+
284
+ More detailed documentation will be published soon.
285
+
286
+ ## Contributing
287
+
288
+ Contributions are welcome! Please feel free to submit a Pull Request.
289
+
290
+ Before contributing, please:
291
+
292
+ 1. Check for open issues or open a new issue to start a discussion
293
+ 2. Fork the repository on GitHub
294
+ 3. Install development dependencies with `pip install -e ".[dev]"`
295
+ 4. Run pre-commit hooks with `pre-commit install`
296
+ 5. Make your changes following the project's coding style
297
+ 6. Write tests that cover your changes
298
+ 7. Update documentation if needed
299
+ 8. Submit a pull request
300
+
301
+ ## License
302
+
303
+ This project is licensed under the MIT License - see the LICENSE file for details.
@@ -0,0 +1,117 @@
1
+ [build-system]
2
+ requires = ["setuptools>=80.9.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "stratae"
7
+ description = "A toolkit of composable tools for dependency injection, lifecycle management, and more."
8
+ version = "0.0.1a"
9
+ readme = "readme.md"
10
+ requires-python = ">=3.12"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Scott Wahl"}
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ ]
23
+ dependencies = []
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/Eudaetics/stratae"
27
+ Repository = "https://github.com/Eudaetics/stratae"
28
+ Issues = "https://github.com/Eudaetics/stratae/issues"
29
+
30
+ [project.optional-dependencies]
31
+ dev = [
32
+ "pre-commit>=4.2.0",
33
+ "pyright>=1.1.403",
34
+ "ruff>=0.12.2",
35
+ ]
36
+ test = [
37
+ "pytest>=8.4.1",
38
+ "pytest-asyncio>=1.0.0",
39
+ "pytest-cov>=6.1.1",
40
+ "pytest-mock>=3.14.1",
41
+ ]
42
+ asgi = [
43
+ "httpx>=0.28.1",
44
+ "fastapi>=0.100.0"
45
+ ]
46
+
47
+ [tool.setuptools.packages.find]
48
+ where = ["."]
49
+ include = ["stratae*"]
50
+
51
+ [tool.ruff]
52
+ lint.select = ["E", "F", "B", "I", "D", "S", "W"]
53
+ lint.ignore = ["D203", "D212", "B008"]
54
+ lint.external = ["S7497"]
55
+ line-length = 100
56
+
57
+
58
+ [tool.ruff.lint.per-file-ignores]
59
+ "tests/**/*.py" = [
60
+ # at least these three should be fine in tests:
61
+ "S101", # asserts allowed in tests...
62
+ "ARG", # Unused function args -> fixtures nevertheless are functionally relevant...
63
+ "FBT", # Don't care about booleans as positional arguments in tests, e.g. via @pytest.mark.parametrize()
64
+ "S311", # Not using cryptographic safe randomness in tests, just getting random values
65
+ ]
66
+
67
+ [tool.pytest.ini_options]
68
+ minversion = "8.0"
69
+ testpaths = "tests/"
70
+ cache_dir = ".pytest_cache"
71
+ addopts = [
72
+ "--cov=stratae/",
73
+ "--cov-report=html:coverage/cov.html",
74
+ "--no-cov-on-fail"
75
+ ]
76
+ asyncio_mode = "auto"
77
+ asyncio_default_fixture_loop_scope = "function"
78
+ markers = [
79
+ "asgi: ASGI framework integration tests (requires fastapi, httpx)",
80
+ ]
81
+
82
+ [tool.coverage.run]
83
+ omit = [
84
+ "*/venv/*",
85
+ "*/.venv/*",
86
+ "*/migrations/*",
87
+ "*/alembic/*",
88
+ "*/_version.py",
89
+ ]
90
+
91
+ [tool.coverage.report]
92
+ exclude_lines = [
93
+ "@(abc\\.)?abstractmethod",
94
+ "^\\s*\\.\\.\\.$",
95
+ "if TYPE_CHECKING:"
96
+ ]
97
+
98
+ [tool.pyright]
99
+ include = ["stratae", "tests"]
100
+ exclude = ["**/__pycache__"]
101
+
102
+ typeCheckingMode = "strict"
103
+ reportMissingImports = true
104
+ reportMissingTypeStubs = false
105
+ reportUnusedImport = true
106
+ reportUnusedClass = true
107
+ reportUnusedFunction = true
108
+ reportUnusedVariable = true
109
+ reportDuplicateImport = true
110
+ reportOptionalSubscript = true
111
+ reportOptionalMemberAccess = true
112
+ reportOptionalCall = true
113
+ reportOptionalIterable = true
114
+ reportUntypedFunctionDecorator = true
115
+
116
+ # Python version
117
+ pythonVersion = "3.12"
@@ -0,0 +1,273 @@
1
+ # Stratae
2
+
3
+ **Composable tools for dependency injection in Python. Fast, simple, and framework-agnostic.**
4
+
5
+ Stratae is a toolkit designed of small, focused components that layer together to create more powerful systems. It's built to complement Python's 3.12+ features, leveraging decorators, contexts, and functions to create a system that works anywhere.
6
+
7
+ Write your business logic once, use it everywhere: APIs, CLIs, workers, and tests.
8
+
9
+ ```bash
10
+ pip install stratae
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from stratae.depends import Depends, inject
17
+ from stratae.lifecycle import Lifecycle
18
+
19
+ lifecycle = Lifecycle(["application"])
20
+
21
+ type Database = dict[str, list[dict[str, str]]]
22
+
23
+ # Simple database connection (just a dict for demo)
24
+ @lifecycle.cache('application')
25
+ def get_database() -> Database:
26
+ return {"users": []}
27
+
28
+ @inject
29
+ def create_user(name: str, db: Database = Depends(get_database)):
30
+ user = {"name": name}
31
+ db["users"].append(user)
32
+ return user
33
+
34
+ # Use anywhere: APIs, CLIs, workers, tests
35
+ with lifecycle.start('application'):
36
+ user = create_user("Alice")
37
+ print(f"Created user: {user['name']}")
38
+ ```
39
+
40
+ ## Why Stratae?
41
+
42
+ **Simple by design.** Stratae doesn't impose architectural patterns or force you into a framework. It provides focused tools that solve specific problems: dependency injection, lifecycle management, and context variables. Use what you need, ignore what you don't.
43
+
44
+ **Zero lock-in.** Your business logic is just functions with decorators. Remove Stratae anytime by replacing `Depends()` with actual values. No container to untangle, no framework to escape.
45
+
46
+ **Built for performance.** Stratae keeps overhead minimal through straightforward design:
47
+
48
+ - Analyze dependencies once at decoration time
49
+ - No runtime introspection or provider lookups
50
+ - Direct function calls with minimal indirection
51
+
52
+ The result is a system that minimizes overhead and stays out of your way. If you're using a system that has dependency injection, we encourage you to test it yourself. Change one small piece to use Stratae and see if it works for you.
53
+
54
+ ## Features
55
+
56
+ ### Dependency Injection
57
+
58
+ Dependency injection in Stratae uses familiar decorator syntax that works with any callable. Use this to send values, objects, or anything into a function.
59
+
60
+ ```python
61
+ from stratae.depends import Depends, inject
62
+
63
+ def get_config():
64
+ return {"env": "dev", "mode": "strict"}
65
+
66
+ # Decorate the function with inject to resolve dependencies
67
+ @inject
68
+ # Use Depends(...) to mark parameters for injection
69
+ def endpoint(config: dict[str, str] = Depends(get_config)):
70
+ print(f"Environment: {config['env']}, Mode: {config['mode']}")
71
+
72
+ endpoint()
73
+ # Environment: dev, Mode: strict
74
+ ```
75
+
76
+ ### Lifecycle Management
77
+
78
+ Use lifecycle management when you want to cache objects or guarantee resource cleanup for context managers. With managed resources, everything is cleaned up automatically at the end of a lifecycle scope.
79
+
80
+ ```python
81
+ # Set up the lifecycle with your application scopes
82
+ lifecycle = Lifecycle(['application', 'request'])
83
+
84
+ # Lifecycle will cache the yielded value and return it for all calls within a request
85
+ @lifecycle.cache('request')
86
+ # Mark get_session as a contextmanager that will be auto-entered to get the session
87
+ @managed
88
+ def get_session():
89
+ session = Session()
90
+ try:
91
+ yield session
92
+ session.commit()
93
+ except:
94
+ session.rollback()
95
+ raise
96
+ finally:
97
+ session.close()
98
+
99
+ # Set up your lifecycle boundaries
100
+ with lifecycle.start('application'):
101
+ with lifecycle.start('request'):
102
+ # Session is created at first call and cached automatically
103
+ # All get_session calls in this request will return the same session
104
+ db = get_session()
105
+ db.users.create_user('John')
106
+ with lifecycle.start('request'):
107
+ # New request, new session
108
+ db = get_session()
109
+ ```
110
+
111
+ ### Context Variables
112
+
113
+ To enable decoupled systems, Stratae uses context variables for setting values that need to be shared among components. This is particularly useful for setting values at runtime that are needed deep in dependency chains. Change values at runtime, or even whole behavior, without needing to thread parameters or manipulate overrides.
114
+
115
+ ```python
116
+ from stratae.context import Context
117
+
118
+ lifecycle = Lifecycle('request')
119
+ user_id = Context[int]("user_id")
120
+
121
+ @lifecycle.cache('request')
122
+ @inject
123
+ def get_current_user(uid: int = Depends(user_id)) -> User:
124
+ return fetch_user(uid)
125
+
126
+ @inject
127
+ def create_post(
128
+ content: str,
129
+ user: User = Depends(get_current_user),
130
+ ) -> Post:
131
+ return Post(author=user, content=content)
132
+
133
+ with lifecycle.start('request'), user_id.use(123):
134
+ post = create_post("Hello world!")
135
+ ```
136
+
137
+ ### Async Support
138
+
139
+ Stratae is fully async compatible. Injection natively works with sync or async functions. Lifecycle offers versions for sync and async handling of resources.
140
+
141
+ ```python
142
+ from stratae.depends import Depends, inject
143
+ from stratae.lifecycle import AsyncLifecycle
144
+
145
+ lifecycle = AsyncLifecycle(['application', 'request'])
146
+
147
+ @lifecycle.cache('application')
148
+ async def get_database() -> Database:
149
+ return await Database(url="postgresql://...")
150
+
151
+ @inject
152
+ async def create_user(
153
+ name: str,
154
+ db: Database = Depends(get_database),
155
+ ) -> User:
156
+ return await db.users.create(name=name)
157
+
158
+ # Use anywhere: APIs, CLIs, workers, tests
159
+ async with lifecycle.start('application'):
160
+ async with lifecycle.start('request'):
161
+ user = await create_user("Alice")
162
+ ```
163
+
164
+ ### Easy Testing
165
+
166
+ With no complex configuration, testing functions decorated with Stratae is easy. The function signature isn't changed, just pass in the mocks you need.
167
+
168
+ ```python
169
+ @inject
170
+ def create_user(name: str, db = Depends(get_db)):
171
+ db.user.create(name=name)
172
+
173
+ # Use normally
174
+ create_user('Steve')
175
+
176
+ # Test
177
+ create_user('Jason', db=MockDB())
178
+ ```
179
+
180
+ ### Framework Agnostic
181
+
182
+ Stratae doesn't have a complex framework to configure or objects to pass around. Write your business logic once with injection, then simply call those functions anywhere.
183
+
184
+ ```python
185
+ # Business logic - framework-independent
186
+ @inject
187
+ async def create_user(
188
+ name: str,
189
+ db: Database = Depends(get_database),
190
+ ) -> User:
191
+ return await db.users.create(name=name)
192
+
193
+ # FastAPI
194
+ @app.post("/users")
195
+ async def api_create(name: str):
196
+ return await create_user(name)
197
+
198
+ # CLI
199
+ @click.command()
200
+ def cli_create(name: str):
201
+ asyncio.run(create_user(name))
202
+
203
+ # Tests
204
+ async def test_create():
205
+ user = await create_user("Alice", db=mock_db)
206
+ ```
207
+
208
+ ### Simple Integrations
209
+
210
+ The design of Stratae means integrating with other tools or frameworks is typically easy. For FastAPI, an ASGI middleware that starts the request lifecycle is enough to add Stratae's lifecycle management.
211
+
212
+ ```python
213
+ from fastapi import FastAPI
214
+ from stratae.integrations import RequestLifecycleMiddleware
215
+ from stratae.lifecycle import AsyncLifecycle, managed
216
+
217
+
218
+ app = FastAPI()
219
+ lifecycle = AsyncLifecycle(['request'])
220
+
221
+ # Add the middleware that starts a lifecycle request
222
+ app.add_middleware(RequestLifecycleMiddleware, lifecycle, 'request')
223
+
224
+ # Everything that needs the session will get the same session
225
+ @lifecycle.cache('request')
226
+ @managed
227
+ async def get_session():
228
+ session = AsyncSession()
229
+ try:
230
+ yield session
231
+ session.commit()
232
+ except:
233
+ session.rollback()
234
+ raise
235
+ finally:
236
+ session.close()
237
+
238
+ @inject
239
+ async def create_user(
240
+ name: str,
241
+ # Using Stratae Depends
242
+ db: Session = Depends(get_session)
243
+ ):
244
+ await db.users.create(name=name)
245
+
246
+ # Every request will get a new session
247
+ @app.post('/users')
248
+ async def post_user(name: str):
249
+ await create_user(name)
250
+ ```
251
+
252
+ ## Documentation
253
+
254
+ More detailed documentation will be published soon.
255
+
256
+ ## Contributing
257
+
258
+ Contributions are welcome! Please feel free to submit a Pull Request.
259
+
260
+ Before contributing, please:
261
+
262
+ 1. Check for open issues or open a new issue to start a discussion
263
+ 2. Fork the repository on GitHub
264
+ 3. Install development dependencies with `pip install -e ".[dev]"`
265
+ 4. Run pre-commit hooks with `pre-commit install`
266
+ 5. Make your changes following the project's coding style
267
+ 6. Write tests that cover your changes
268
+ 7. Update documentation if needed
269
+ 8. Submit a pull request
270
+
271
+ ## License
272
+
273
+ This project is licensed under the MIT License - see the LICENSE file for details.
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+