zayt 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zayt-0.1.0/LICENSE +21 -0
- zayt-0.1.0/PKG-INFO +76 -0
- zayt-0.1.0/README.md +38 -0
- zayt-0.1.0/pyproject.toml +117 -0
- zayt-0.1.0/src/zayt/__init__.py +0 -0
- zayt-0.1.0/src/zayt/__version__.py +1 -0
- zayt-0.1.0/src/zayt/conf/__init__.py +2 -0
- zayt-0.1.0/src/zayt/conf/defaults.py +24 -0
- zayt-0.1.0/src/zayt/conf/environment.py +52 -0
- zayt-0.1.0/src/zayt/conf/settings.py +165 -0
- zayt-0.1.0/src/zayt/di/__init__.py +5 -0
- zayt-0.1.0/src/zayt/di/call.py +25 -0
- zayt-0.1.0/src/zayt/di/container.py +311 -0
- zayt-0.1.0/src/zayt/di/decorator.py +101 -0
- zayt-0.1.0/src/zayt/di/error.py +128 -0
- zayt-0.1.0/src/zayt/di/inject.py +14 -0
- zayt-0.1.0/src/zayt/di/interceptor.py +7 -0
- zayt-0.1.0/src/zayt/di/service/__init__.py +0 -0
- zayt-0.1.0/src/zayt/di/service/model.py +28 -0
- zayt-0.1.0/src/zayt/di/service/parse.py +158 -0
- zayt-0.1.0/src/zayt/di/service/registry.py +65 -0
- zayt-0.1.0/src/zayt/ext/__init__.py +0 -0
- zayt-0.1.0/src/zayt/ext/error.py +10 -0
- zayt-0.1.0/src/zayt/ext/jinja/__init__.py +15 -0
- zayt-0.1.0/src/zayt/ext/jinja/service.py +81 -0
- zayt-0.1.0/src/zayt/ext/memcached/__init__.py +17 -0
- zayt-0.1.0/src/zayt/ext/memcached/service.py +24 -0
- zayt-0.1.0/src/zayt/ext/redis/__init__.py +14 -0
- zayt-0.1.0/src/zayt/ext/redis/service.py +21 -0
- zayt-0.1.0/src/zayt/ext/sqlalchemy/__init__.py +24 -0
- zayt-0.1.0/src/zayt/ext/sqlalchemy/service.py +114 -0
- zayt-0.1.0/src/zayt/logging.py +190 -0
- zayt-0.1.0/src/zayt/py.typed +0 -0
- zayt-0.1.0/src/zayt/run.py +6 -0
- zayt-0.1.0/src/zayt/util/__init__.py +0 -0
- zayt-0.1.0/src/zayt/util/asgi.py +26 -0
- zayt-0.1.0/src/zayt/util/base_types.py +39 -0
- zayt-0.1.0/src/zayt/util/dotenv.py +10 -0
- zayt-0.1.0/src/zayt/util/import_item.py +25 -0
- zayt-0.1.0/src/zayt/util/maybe_async.py +24 -0
- zayt-0.1.0/src/zayt/util/package_scan.py +49 -0
- zayt-0.1.0/src/zayt/web/__init__.py +5 -0
- zayt-0.1.0/src/zayt/web/application.py +300 -0
- zayt-0.1.0/src/zayt/web/exception.py +42 -0
- zayt-0.1.0/src/zayt/web/exception_handler/__init__.py +0 -0
- zayt-0.1.0/src/zayt/web/exception_handler/decorator.py +26 -0
- zayt-0.1.0/src/zayt/web/exception_handler/discover.py +26 -0
- zayt-0.1.0/src/zayt/web/handler/__init__.py +0 -0
- zayt-0.1.0/src/zayt/web/handler/model.py +9 -0
- zayt-0.1.0/src/zayt/web/handler/parse.py +62 -0
- zayt-0.1.0/src/zayt/web/lifecycle/__init__.py +0 -0
- zayt-0.1.0/src/zayt/web/lifecycle/decorator.py +16 -0
- zayt-0.1.0/src/zayt/web/lifecycle/discover.py +18 -0
- zayt-0.1.0/src/zayt/web/middleware/__init__.py +0 -0
- zayt-0.1.0/src/zayt/web/middleware/files.py +122 -0
- zayt-0.1.0/src/zayt/web/middleware/request_id.py +18 -0
- zayt-0.1.0/src/zayt/web/routing/__init__.py +0 -0
- zayt-0.1.0/src/zayt/web/routing/decorator.py +126 -0
- zayt-0.1.0/src/zayt/web/routing/exception.py +46 -0
- zayt-0.1.0/src/zayt/web/routing/route.py +111 -0
- zayt-0.1.0/src/zayt/web/routing/router.py +94 -0
- zayt-0.1.0/tests/__init__.py +0 -0
- zayt-0.1.0/tests/conf/__init__.py +0 -0
- zayt-0.1.0/tests/conf/alternate/configuration/application.py +5 -0
- zayt-0.1.0/tests/conf/alternate/configuration/application_prd.py +1 -0
- zayt-0.1.0/tests/conf/base/configuration/settings.py +5 -0
- zayt-0.1.0/tests/conf/dotenv/.env +1 -0
- zayt-0.1.0/tests/conf/dotenv/.gitignore +1 -0
- zayt-0.1.0/tests/conf/empty/configuration/settings.py +0 -0
- zayt-0.1.0/tests/conf/invalid_configuration/invalid_yaml/configuration/settings.py +1 -0
- zayt-0.1.0/tests/conf/multiple_profiles/configuration/settings.py +0 -0
- zayt-0.1.0/tests/conf/multiple_profiles/configuration/settings_dev.py +1 -0
- zayt-0.1.0/tests/conf/multiple_profiles/configuration/settings_prd.py +1 -0
- zayt-0.1.0/tests/conf/multiple_profiles/configuration/settings_stg.py +1 -0
- zayt-0.1.0/tests/conf/override/configuration/settings.py +1 -0
- zayt-0.1.0/tests/conf/override/configuration/settings_dev.py +1 -0
- zayt-0.1.0/tests/conf/override/configuration/settings_prd.py +1 -0
- zayt-0.1.0/tests/conf/override/configuration/settings_stg.py +1 -0
- zayt-0.1.0/tests/conf/profiles/configuration/settings.py +1 -0
- zayt-0.1.0/tests/conf/profiles/configuration/settings_dev.py +1 -0
- zayt-0.1.0/tests/conf/profiles/configuration/settings_prd.py +1 -0
- zayt-0.1.0/tests/conf/profiles/configuration/settings_stg.py +1 -0
- zayt-0.1.0/tests/conf/test_dotenv.py +20 -0
- zayt-0.1.0/tests/conf/test_envvar.py +65 -0
- zayt-0.1.0/tests/conf/test_settings.py +341 -0
- zayt-0.1.0/tests/conftest.py +13 -0
- zayt-0.1.0/tests/di/__init__.py +0 -0
- zayt-0.1.0/tests/di/conftest.py +8 -0
- zayt-0.1.0/tests/di/service/__init__.py +0 -0
- zayt-0.1.0/tests/di/service/parse/__init__.py +0 -0
- zayt-0.1.0/tests/di/service/parse/test_parse_service_spec.py +14 -0
- zayt-0.1.0/tests/di/service/parse/test_service_class.py +149 -0
- zayt-0.1.0/tests/di/service/parse/test_service_factory.py +145 -0
- zayt-0.1.0/tests/di/test_decorator.py +65 -0
- zayt-0.1.0/tests/di/test_define_instance.py +26 -0
- zayt-0.1.0/tests/di/test_dependency_complex_graph.py +100 -0
- zayt-0.1.0/tests/di/test_dependency_loop.py +47 -0
- zayt-0.1.0/tests/di/test_dependency_options.py +28 -0
- zayt-0.1.0/tests/di/test_generics.py +73 -0
- zayt-0.1.0/tests/di/test_interceptor.py +24 -0
- zayt-0.1.0/tests/di/test_iter_service.py +55 -0
- zayt-0.1.0/tests/di/test_lifecycle_callbacks.py +171 -0
- zayt-0.1.0/tests/di/test_named_dependencies.py +89 -0
- zayt-0.1.0/tests/di/test_register.py +20 -0
- zayt-0.1.0/tests/di/test_scope.py +56 -0
- zayt-0.1.0/tests/di/test_scope_mixed.py +34 -0
- zayt-0.1.0/tests/di/test_service_class.py +74 -0
- zayt-0.1.0/tests/di/test_service_function.py +113 -0
- zayt-0.1.0/tests/di/test_service_generator.py +88 -0
- zayt-0.1.0/tests/di/test_service_generator_async.py +103 -0
- zayt-0.1.0/tests/di/test_unknown_service.py +26 -0
- zayt-0.1.0/tests/examples/__init__.py +0 -0
- zayt-0.1.0/tests/examples/test_background_tasks.py +31 -0
- zayt-0.1.0/tests/examples/test_exception_handler.py +31 -0
- zayt-0.1.0/tests/examples/test_exceptions.py +33 -0
- zayt-0.1.0/tests/examples/test_hello_world.py +39 -0
- zayt-0.1.0/tests/examples/test_htmx.py +33 -0
- zayt-0.1.0/tests/examples/test_jinja.py +30 -0
- zayt-0.1.0/tests/examples/test_memcached.py +38 -0
- zayt-0.1.0/tests/examples/test_middleware.py +39 -0
- zayt-0.1.0/tests/examples/test_middleware_files.py +39 -0
- zayt-0.1.0/tests/examples/test_redis.py +38 -0
- zayt-0.1.0/tests/examples/test_settings.py +33 -0
- zayt-0.1.0/tests/examples/test_sqlalchemy.py +44 -0
- zayt-0.1.0/tests/examples/test_websocket.py +37 -0
- zayt-0.1.0/tests/examples/util.py +19 -0
- zayt-0.1.0/tests/ext/__init__.py +0 -0
- zayt-0.1.0/tests/ext/application.py +0 -0
- zayt-0.1.0/tests/ext/extension.py +0 -0
- zayt-0.1.0/tests/ext/jinja/__init__.py +0 -0
- zayt-0.1.0/tests/ext/jinja/application.py +29 -0
- zayt-0.1.0/tests/ext/jinja/templates/template.html +1 -0
- zayt-0.1.0/tests/ext/jinja/test_response.py +59 -0
- zayt-0.1.0/tests/ext/memcached/__init__.py +0 -0
- zayt-0.1.0/tests/ext/memcached/application.py +14 -0
- zayt-0.1.0/tests/ext/memcached/application_named.py +16 -0
- zayt-0.1.0/tests/ext/memcached/test_application.py +46 -0
- zayt-0.1.0/tests/ext/memcached/test_service.py +61 -0
- zayt-0.1.0/tests/ext/redis/__init__.py +0 -0
- zayt-0.1.0/tests/ext/redis/application.py +13 -0
- zayt-0.1.0/tests/ext/redis/application_named.py +16 -0
- zayt-0.1.0/tests/ext/redis/test_application.py +46 -0
- zayt-0.1.0/tests/ext/redis/test_service.py +127 -0
- zayt-0.1.0/tests/ext/register_extension/__init__.py +0 -0
- zayt-0.1.0/tests/ext/register_extension/application.py +1 -0
- zayt-0.1.0/tests/ext/register_extension/extension.py +2 -0
- zayt-0.1.0/tests/ext/register_extension/test_extension.py +20 -0
- zayt-0.1.0/tests/ext/sqlalchemy/__init__.py +0 -0
- zayt-0.1.0/tests/ext/sqlalchemy/application.py +14 -0
- zayt-0.1.0/tests/ext/sqlalchemy/application_named.py +17 -0
- zayt-0.1.0/tests/ext/sqlalchemy/application_session.py +22 -0
- zayt-0.1.0/tests/ext/sqlalchemy/application_session_startup.py +9 -0
- zayt-0.1.0/tests/ext/sqlalchemy/test_application.py +39 -0
- zayt-0.1.0/tests/ext/sqlalchemy/test_service.py +187 -0
- zayt-0.1.0/tests/ext/sqlalchemy/test_service_postgres.py +154 -0
- zayt-0.1.0/tests/ext/sqlalchemy/test_session.py +49 -0
- zayt-0.1.0/tests/ext/sqlalchemy/test_sessionmaker_service.py +240 -0
- zayt-0.1.0/tests/ext/test_extensions.py +40 -0
- zayt-0.1.0/tests/util/__init__.py +0 -0
- zayt-0.1.0/tests/util/package_to_scan/__init__.py +0 -0
- zayt-0.1.0/tests/util/package_to_scan/module_to_scan.py +6 -0
- zayt-0.1.0/tests/util/test_get_base_types.py +67 -0
- zayt-0.1.0/tests/util/test_import_item.py +44 -0
- zayt-0.1.0/tests/util/test_maybe_async.py +109 -0
- zayt-0.1.0/tests/util/test_maybe_async_sync.py +91 -0
- zayt-0.1.0/tests/util/test_package_scan.py +58 -0
- zayt-0.1.0/tests/web/__init__.py +0 -0
- zayt-0.1.0/tests/web/application/__init__.py +0 -0
- zayt-0.1.0/tests/web/application/application.py +18 -0
- zayt-0.1.0/tests/web/application/test_application.py +29 -0
- zayt-0.1.0/tests/web/application/test_middleware.py +69 -0
- zayt-0.1.0/tests/web/application/test_service_startup.py +24 -0
- zayt-0.1.0/tests/web/application/test_websocket.py +46 -0
- zayt-0.1.0/tests/web/exception_handler/__init__.py +0 -0
- zayt-0.1.0/tests/web/exception_handler/application.py +39 -0
- zayt-0.1.0/tests/web/exception_handler/test_exception_handler.py +44 -0
- zayt-0.1.0/tests/web/middleware/__init__.py +0 -0
- zayt-0.1.0/tests/web/middleware/files/__init__.py +0 -0
- zayt-0.1.0/tests/web/middleware/files/application.py +0 -0
- zayt-0.1.0/tests/web/middleware/files/resources/assets/style.css +1 -0
- zayt-0.1.0/tests/web/middleware/files/resources/media/data.json +1 -0
- zayt-0.1.0/tests/web/middleware/files/resources/static/lorem-ipsum.txt +1 -0
- zayt-0.1.0/tests/web/middleware/files/resources/uploads/lorem-ipsum.txt +1 -0
- zayt-0.1.0/tests/web/middleware/files/test_staticfiles.py +116 -0
- zayt-0.1.0/tests/web/middleware/files/test_uploadedfiles.py +89 -0
- zayt-0.1.0/tests/web/routing/__init__.py +0 -0
- zayt-0.1.0/tests/web/routing/test_decorator.py +158 -0
- zayt-0.1.0/tests/web/routing/test_router.py +21 -0
- zayt-0.1.0/tests/web/test_exception.py +39 -0
zayt-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Livio Ribeiro
|
|
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.
|
zayt-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: zayt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: ASGI Web Framework with Dependency Injection
|
|
5
|
+
Keywords: asgi,framework,asyncio,web
|
|
6
|
+
Author: Livio Ribeiro
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Web Environment
|
|
10
|
+
Classifier: Framework :: AsyncIO
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
20
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
23
|
+
Project-URL: Source, https://codeberg.org/python-zayt/zayt
|
|
24
|
+
Project-URL: Documentation, https://python-zayt.codeberg.page/zayt
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Requires-Dist: asgikit~=0.18.0
|
|
27
|
+
Requires-Dist: python-dotenv~=1.2.1
|
|
28
|
+
Requires-Dist: structlog~=25.5.0
|
|
29
|
+
Provides-Extra: jinja
|
|
30
|
+
Requires-Dist: jinja2~=3.1.6; extra == "jinja"
|
|
31
|
+
Provides-Extra: sqlalchemy
|
|
32
|
+
Requires-Dist: SQLAlchemy[asyncio]~=2.0.45; extra == "sqlalchemy"
|
|
33
|
+
Provides-Extra: redis
|
|
34
|
+
Requires-Dist: redis~=7.1.0; extra == "redis"
|
|
35
|
+
Provides-Extra: memcached
|
|
36
|
+
Requires-Dist: aiomcache~=0.8.2; extra == "memcached"
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# Project Zayt
|
|
40
|
+
|
|
41
|
+
Zayt is a Python ASGI web framework built on top of [asgikit](https://pypi.org/project/asgikit/)
|
|
42
|
+
and inspired by Spring Boot, AspNet, FastAPI and Go's net/http.
|
|
43
|
+
|
|
44
|
+
It features a Dependency Injection system to help build robust and reliable applications.
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
Install `zayt` and `uvicorn` to run application:
|
|
49
|
+
|
|
50
|
+
```shell
|
|
51
|
+
pip install zayt uvicorn[standard]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Create a module called `application.py`:
|
|
55
|
+
|
|
56
|
+
```shell
|
|
57
|
+
touch application.py
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Create a handler:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from asgikit import Request
|
|
64
|
+
from zayt.web import get
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@get
|
|
68
|
+
async def hello(request: Request):
|
|
69
|
+
await request.respond_text("Hello, World!")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Run application with `uvicorn` (Zayt will automatically load `application.py`):
|
|
73
|
+
|
|
74
|
+
```shell
|
|
75
|
+
uvicorn zayt.run:app --reload
|
|
76
|
+
```
|
zayt-0.1.0/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Project Zayt
|
|
2
|
+
|
|
3
|
+
Zayt is a Python ASGI web framework built on top of [asgikit](https://pypi.org/project/asgikit/)
|
|
4
|
+
and inspired by Spring Boot, AspNet, FastAPI and Go's net/http.
|
|
5
|
+
|
|
6
|
+
It features a Dependency Injection system to help build robust and reliable applications.
|
|
7
|
+
|
|
8
|
+
## Quick start
|
|
9
|
+
|
|
10
|
+
Install `zayt` and `uvicorn` to run application:
|
|
11
|
+
|
|
12
|
+
```shell
|
|
13
|
+
pip install zayt uvicorn[standard]
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Create a module called `application.py`:
|
|
17
|
+
|
|
18
|
+
```shell
|
|
19
|
+
touch application.py
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Create a handler:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from asgikit import Request
|
|
26
|
+
from zayt.web import get
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@get
|
|
30
|
+
async def hello(request: Request):
|
|
31
|
+
await request.respond_text("Hello, World!")
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Run application with `uvicorn` (Zayt will automatically load `application.py`):
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
uvicorn zayt.run:app --reload
|
|
38
|
+
```
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "zayt"
|
|
3
|
+
description = "ASGI Web Framework with Dependency Injection"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name = "Livio Ribeiro" },
|
|
6
|
+
]
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
keywords = [
|
|
9
|
+
"asgi",
|
|
10
|
+
"framework",
|
|
11
|
+
"asyncio",
|
|
12
|
+
"web",
|
|
13
|
+
]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Web Environment",
|
|
17
|
+
"Framework :: AsyncIO",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: 3.14",
|
|
26
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
27
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
28
|
+
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
|
29
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
30
|
+
]
|
|
31
|
+
dynamic = []
|
|
32
|
+
requires-python = ">=3.11"
|
|
33
|
+
dependencies = [
|
|
34
|
+
"asgikit~=0.18.0",
|
|
35
|
+
"python-dotenv~=1.2.1",
|
|
36
|
+
"structlog~=25.5.0",
|
|
37
|
+
]
|
|
38
|
+
version = "0.1.0"
|
|
39
|
+
|
|
40
|
+
[project.license]
|
|
41
|
+
text = "MIT"
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Source = "https://codeberg.org/python-zayt/zayt"
|
|
45
|
+
Documentation = "https://python-zayt.codeberg.page/zayt"
|
|
46
|
+
|
|
47
|
+
[project.optional-dependencies]
|
|
48
|
+
jinja = [
|
|
49
|
+
"jinja2~=3.1.6",
|
|
50
|
+
]
|
|
51
|
+
sqlalchemy = [
|
|
52
|
+
"SQLAlchemy[asyncio]~=2.0.45",
|
|
53
|
+
]
|
|
54
|
+
redis = [
|
|
55
|
+
"redis~=7.1.0",
|
|
56
|
+
]
|
|
57
|
+
memcached = [
|
|
58
|
+
"aiomcache~=0.8.2",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[dependency-groups]
|
|
62
|
+
dev = [
|
|
63
|
+
"uvicorn[standard]~=0.40",
|
|
64
|
+
"granian~=2.5",
|
|
65
|
+
]
|
|
66
|
+
test = [
|
|
67
|
+
"pytest~=9.0.0",
|
|
68
|
+
"pytest-asyncio~=1.3.0",
|
|
69
|
+
"pytest-mock~=3.15.1",
|
|
70
|
+
"pytest-cov~=7.0.0",
|
|
71
|
+
"httpx~=0.28.1",
|
|
72
|
+
"httpx-ws~=0.8.2",
|
|
73
|
+
"aiosqlite~=0.22.0",
|
|
74
|
+
"asyncpg~=0.31.0",
|
|
75
|
+
]
|
|
76
|
+
lint = [
|
|
77
|
+
"pylint~=4.0",
|
|
78
|
+
"ruff~=0.14",
|
|
79
|
+
"mypy~=1.19",
|
|
80
|
+
"isort~=7.0",
|
|
81
|
+
]
|
|
82
|
+
docs = [
|
|
83
|
+
"sphinx~=9.0.4",
|
|
84
|
+
"sphinx-intl~=2.3.2",
|
|
85
|
+
"sphinx-autobuild>=2025.8.25",
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
[tool.pdm]
|
|
89
|
+
distribution = true
|
|
90
|
+
|
|
91
|
+
[tool.pdm.version]
|
|
92
|
+
source = "file"
|
|
93
|
+
path = "src/zayt/__version__.py"
|
|
94
|
+
|
|
95
|
+
[tool.pdm.scripts]
|
|
96
|
+
test = "pytest --cov src --cov-report term --cov-report html"
|
|
97
|
+
|
|
98
|
+
[tool.pdm.scripts.docs]
|
|
99
|
+
shell = " sphinx-autobuild --doctree-dir=docs/_build/doctrees --post-build='{pdm} run docs/build_locales.py' docs docs/_build/html\n"
|
|
100
|
+
|
|
101
|
+
[tool.pdm.scripts.docs-build]
|
|
102
|
+
composite = [
|
|
103
|
+
"sphinx-build -d docs/_build/doctrees/en/ docs docs/_build/html",
|
|
104
|
+
"{pdm} run docs/build_locales.py",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
[tool.pdm.scripts.docs-intl]
|
|
108
|
+
composite = [
|
|
109
|
+
"sphinx-build -M gettext docs docs/_build",
|
|
110
|
+
"sphinx-intl update -d docs/_locale -p docs/_build/gettext -l pt_BR",
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
[build-system]
|
|
114
|
+
requires = [
|
|
115
|
+
"pdm-backend",
|
|
116
|
+
]
|
|
117
|
+
build-backend = "pdm.backend"
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
default_settings = {
|
|
2
|
+
"__application__": "application",
|
|
3
|
+
"extensions": [],
|
|
4
|
+
"asgi_middleware": [],
|
|
5
|
+
"logging": {
|
|
6
|
+
"setup": "zayt.logging:setup",
|
|
7
|
+
},
|
|
8
|
+
"jinja": {},
|
|
9
|
+
"memcached": {},
|
|
10
|
+
"redis": {},
|
|
11
|
+
"sqlalchemy": {
|
|
12
|
+
"connections": {},
|
|
13
|
+
"session": {},
|
|
14
|
+
},
|
|
15
|
+
"staticfiles": {
|
|
16
|
+
"path": "/static",
|
|
17
|
+
"root": "resources/static",
|
|
18
|
+
"mappings": {},
|
|
19
|
+
},
|
|
20
|
+
"uploadedfiles": {
|
|
21
|
+
"path": "/uploads",
|
|
22
|
+
"root": "resources/uploads",
|
|
23
|
+
},
|
|
24
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import TypeVar
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def env(name: str, cast: Callable[[str], T] = str, default: T = None) -> T:
|
|
10
|
+
"""Read an environment variable, optionally applying a cast
|
|
11
|
+
|
|
12
|
+
:param name: Name of the environment variable
|
|
13
|
+
:param cast: Type to convert the value
|
|
14
|
+
Should be a callable that accepts a ``str`` and returns the converted value.
|
|
15
|
+
:py:type:`bool` receives a special treatment:
|
|
16
|
+
If the value is empty, return ``None``.
|
|
17
|
+
If the value is ``"1"`` or ``"true"`` (ignoring case), the result is ``True``.
|
|
18
|
+
Any other value is ``False``.
|
|
19
|
+
:py:module:`json` also receives a special treatment, in which the value
|
|
20
|
+
is converted with ``json.loads``.
|
|
21
|
+
:param default: Default value if the environment variable is not set
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
value = os.environ.get(name)
|
|
25
|
+
|
|
26
|
+
if value is None:
|
|
27
|
+
if default is not None:
|
|
28
|
+
return default
|
|
29
|
+
raise ValueError(f"Environment variable '{name}' not set")
|
|
30
|
+
|
|
31
|
+
if len(value.strip()) == 0:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
if cast is bool:
|
|
35
|
+
return _cast_bool(value)
|
|
36
|
+
|
|
37
|
+
if cast is json:
|
|
38
|
+
return json.loads(value)
|
|
39
|
+
|
|
40
|
+
return cast(value)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _cast_bool(value: str) -> bool | None:
|
|
44
|
+
"""Cast value to bool from str
|
|
45
|
+
|
|
46
|
+
:return:
|
|
47
|
+
True if `value.lower()` is "true" or "1"
|
|
48
|
+
None if `value.strip` is empty
|
|
49
|
+
False otherwise
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
return value.lower() in {"true", "1"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The processing of settings occur as follows:
|
|
3
|
+
|
|
4
|
+
1. Get main settings values
|
|
5
|
+
2. Recursively merge values with settings from profiles, in the order of declaration
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import copy
|
|
9
|
+
import importlib.util
|
|
10
|
+
import os
|
|
11
|
+
from collections.abc import Mapping
|
|
12
|
+
from copy import deepcopy
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from zayt.conf.defaults import default_settings
|
|
17
|
+
|
|
18
|
+
__all__ = (
|
|
19
|
+
"Settings",
|
|
20
|
+
"SettingsError",
|
|
21
|
+
"get_settings",
|
|
22
|
+
"get_settings_meta",
|
|
23
|
+
"get_settings_for_profile",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
SETTINGS_DIR_ENV = "ZAYT_SETTINGS_DIR"
|
|
27
|
+
SETTINGS_FILE_ENV = "ZAYT_SETTINGS_FILE"
|
|
28
|
+
|
|
29
|
+
DEFAULT_SETTINGS_DIR = str(Path("configuration"))
|
|
30
|
+
DEFAULT_SETTINGS_FILE = "settings.py"
|
|
31
|
+
SETTINGS_KEY = "settings"
|
|
32
|
+
|
|
33
|
+
ZAYT_PROFILE_ENV = "ZAYT_PROFILE"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Settings(Mapping[str, Any]):
|
|
37
|
+
def __init__(self, data: dict):
|
|
38
|
+
self._original_data = copy.deepcopy(data)
|
|
39
|
+
self.__data = data
|
|
40
|
+
|
|
41
|
+
for key, value in data.items():
|
|
42
|
+
if isinstance(value, dict):
|
|
43
|
+
self.__data[key] = Settings(value)
|
|
44
|
+
|
|
45
|
+
def __getattr__(self, item: str):
|
|
46
|
+
try:
|
|
47
|
+
return self.__data[item]
|
|
48
|
+
except KeyError:
|
|
49
|
+
# pylint: disable=raise-missing-from
|
|
50
|
+
raise AttributeError(item)
|
|
51
|
+
|
|
52
|
+
def __len__(self) -> int:
|
|
53
|
+
return len(self.__data)
|
|
54
|
+
|
|
55
|
+
def __iter__(self):
|
|
56
|
+
return iter(self.__data)
|
|
57
|
+
|
|
58
|
+
def __contains__(self, key: str):
|
|
59
|
+
return key in self.__data
|
|
60
|
+
|
|
61
|
+
def __getitem__(self, key: str):
|
|
62
|
+
return self.__data[key]
|
|
63
|
+
|
|
64
|
+
def __copy__(self):
|
|
65
|
+
return Settings(copy.copy(self._original_data))
|
|
66
|
+
|
|
67
|
+
def __deepcopy__(self, memodict):
|
|
68
|
+
data = copy.deepcopy(self._original_data, memodict)
|
|
69
|
+
return Settings(data)
|
|
70
|
+
|
|
71
|
+
def __eq__(self, other: Any) -> bool:
|
|
72
|
+
if isinstance(other, Settings):
|
|
73
|
+
return self._original_data == other._original_data
|
|
74
|
+
if isinstance(other, Mapping):
|
|
75
|
+
return self._original_data == other
|
|
76
|
+
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
def __str__(self):
|
|
80
|
+
return str(self.__data)
|
|
81
|
+
|
|
82
|
+
def __repr__(self):
|
|
83
|
+
return repr(self.__data)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class SettingsError(Exception):
|
|
87
|
+
def __init__(self, path: Path):
|
|
88
|
+
super().__init__(f"cannot load settings from {path}")
|
|
89
|
+
self.path = path
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_settings() -> Settings:
|
|
93
|
+
"""Get settings from files and environment"""
|
|
94
|
+
return get_settings_meta()[0]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_settings_meta() -> tuple[Settings, list[tuple[Path, str | None, bool]]]:
|
|
98
|
+
"""Get settings from files and environment with metadata
|
|
99
|
+
|
|
100
|
+
Metadata contains the files used to load settings from and if the file exists or not
|
|
101
|
+
"""
|
|
102
|
+
# get default settings
|
|
103
|
+
settings = deepcopy(default_settings)
|
|
104
|
+
settings_files = []
|
|
105
|
+
|
|
106
|
+
# merge with main settings file (settings.yaml)
|
|
107
|
+
profile_settings, profile_settings_file = get_settings_for_profile()
|
|
108
|
+
_merge_recursive(settings, profile_settings)
|
|
109
|
+
settings_files.append(profile_settings_file)
|
|
110
|
+
|
|
111
|
+
# merge with profile settings files (settings_$ZAYT_PROFILE.yaml)
|
|
112
|
+
if active_profile_list := os.getenv(ZAYT_PROFILE_ENV):
|
|
113
|
+
for active_profile in active_profile_list.split(","):
|
|
114
|
+
active_profile = active_profile.strip()
|
|
115
|
+
profile_settings, profile_settings_file = get_settings_for_profile(
|
|
116
|
+
active_profile
|
|
117
|
+
)
|
|
118
|
+
_merge_recursive(settings, profile_settings)
|
|
119
|
+
settings_files.append(profile_settings_file)
|
|
120
|
+
|
|
121
|
+
return Settings(settings), settings_files
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_settings_for_profile(
|
|
125
|
+
profile: str = None,
|
|
126
|
+
) -> tuple[dict, tuple[Path, str | None, bool]]:
|
|
127
|
+
settings_file = os.getenv(SETTINGS_FILE_ENV, DEFAULT_SETTINGS_FILE)
|
|
128
|
+
settings_dir_path = Path(os.getenv(SETTINGS_DIR_ENV, DEFAULT_SETTINGS_DIR))
|
|
129
|
+
settings_file_path = settings_dir_path / settings_file
|
|
130
|
+
|
|
131
|
+
if profile:
|
|
132
|
+
settings_file_path = settings_file_path.with_stem(
|
|
133
|
+
f"{settings_file_path.stem}_{profile}"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
settings_file_path = settings_file_path.absolute()
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
spec = importlib.util.spec_from_file_location(
|
|
140
|
+
"settings" if not profile else f"settings_{profile}",
|
|
141
|
+
settings_file_path,
|
|
142
|
+
)
|
|
143
|
+
settings_module = importlib.util.module_from_spec(spec)
|
|
144
|
+
spec.loader.exec_module(settings_module)
|
|
145
|
+
|
|
146
|
+
result = getattr(settings_module, SETTINGS_KEY, {})
|
|
147
|
+
if not isinstance(result, dict):
|
|
148
|
+
raise TypeError("invalid settings object type")
|
|
149
|
+
return result, (settings_file_path, profile, True)
|
|
150
|
+
except FileNotFoundError:
|
|
151
|
+
return {}, (settings_file_path, profile, False)
|
|
152
|
+
except TypeError as err:
|
|
153
|
+
raise SettingsError(settings_file_path) from err
|
|
154
|
+
except Exception as err:
|
|
155
|
+
raise SettingsError(settings_file_path) from err
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _merge_recursive(destination: dict, source: dict):
|
|
159
|
+
for key in source:
|
|
160
|
+
if key in destination and all(
|
|
161
|
+
isinstance(arg[key], dict) for arg in (destination, source)
|
|
162
|
+
):
|
|
163
|
+
_merge_recursive(destination[key], source[key])
|
|
164
|
+
else:
|
|
165
|
+
destination[key] = deepcopy(source[key])
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from zayt.util.maybe_async import call_maybe_async
|
|
5
|
+
from zayt.di.container import Container
|
|
6
|
+
from zayt.di.service.parse import get_dependencies
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def call_with_dependencies(
|
|
10
|
+
di: Container,
|
|
11
|
+
target: Callable,
|
|
12
|
+
*,
|
|
13
|
+
args: list | tuple = None,
|
|
14
|
+
context: Any = None,
|
|
15
|
+
):
|
|
16
|
+
args = args or []
|
|
17
|
+
|
|
18
|
+
dependencies = {
|
|
19
|
+
name: await di.get(
|
|
20
|
+
dep.service, name=dep.name, optional=dep.optional, context=context
|
|
21
|
+
)
|
|
22
|
+
for name, dep in get_dependencies(target, skip=len(args))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return await call_maybe_async(target, *args, **dependencies)
|