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.
Files changed (189) hide show
  1. zayt-0.1.0/LICENSE +21 -0
  2. zayt-0.1.0/PKG-INFO +76 -0
  3. zayt-0.1.0/README.md +38 -0
  4. zayt-0.1.0/pyproject.toml +117 -0
  5. zayt-0.1.0/src/zayt/__init__.py +0 -0
  6. zayt-0.1.0/src/zayt/__version__.py +1 -0
  7. zayt-0.1.0/src/zayt/conf/__init__.py +2 -0
  8. zayt-0.1.0/src/zayt/conf/defaults.py +24 -0
  9. zayt-0.1.0/src/zayt/conf/environment.py +52 -0
  10. zayt-0.1.0/src/zayt/conf/settings.py +165 -0
  11. zayt-0.1.0/src/zayt/di/__init__.py +5 -0
  12. zayt-0.1.0/src/zayt/di/call.py +25 -0
  13. zayt-0.1.0/src/zayt/di/container.py +311 -0
  14. zayt-0.1.0/src/zayt/di/decorator.py +101 -0
  15. zayt-0.1.0/src/zayt/di/error.py +128 -0
  16. zayt-0.1.0/src/zayt/di/inject.py +14 -0
  17. zayt-0.1.0/src/zayt/di/interceptor.py +7 -0
  18. zayt-0.1.0/src/zayt/di/service/__init__.py +0 -0
  19. zayt-0.1.0/src/zayt/di/service/model.py +28 -0
  20. zayt-0.1.0/src/zayt/di/service/parse.py +158 -0
  21. zayt-0.1.0/src/zayt/di/service/registry.py +65 -0
  22. zayt-0.1.0/src/zayt/ext/__init__.py +0 -0
  23. zayt-0.1.0/src/zayt/ext/error.py +10 -0
  24. zayt-0.1.0/src/zayt/ext/jinja/__init__.py +15 -0
  25. zayt-0.1.0/src/zayt/ext/jinja/service.py +81 -0
  26. zayt-0.1.0/src/zayt/ext/memcached/__init__.py +17 -0
  27. zayt-0.1.0/src/zayt/ext/memcached/service.py +24 -0
  28. zayt-0.1.0/src/zayt/ext/redis/__init__.py +14 -0
  29. zayt-0.1.0/src/zayt/ext/redis/service.py +21 -0
  30. zayt-0.1.0/src/zayt/ext/sqlalchemy/__init__.py +24 -0
  31. zayt-0.1.0/src/zayt/ext/sqlalchemy/service.py +114 -0
  32. zayt-0.1.0/src/zayt/logging.py +190 -0
  33. zayt-0.1.0/src/zayt/py.typed +0 -0
  34. zayt-0.1.0/src/zayt/run.py +6 -0
  35. zayt-0.1.0/src/zayt/util/__init__.py +0 -0
  36. zayt-0.1.0/src/zayt/util/asgi.py +26 -0
  37. zayt-0.1.0/src/zayt/util/base_types.py +39 -0
  38. zayt-0.1.0/src/zayt/util/dotenv.py +10 -0
  39. zayt-0.1.0/src/zayt/util/import_item.py +25 -0
  40. zayt-0.1.0/src/zayt/util/maybe_async.py +24 -0
  41. zayt-0.1.0/src/zayt/util/package_scan.py +49 -0
  42. zayt-0.1.0/src/zayt/web/__init__.py +5 -0
  43. zayt-0.1.0/src/zayt/web/application.py +300 -0
  44. zayt-0.1.0/src/zayt/web/exception.py +42 -0
  45. zayt-0.1.0/src/zayt/web/exception_handler/__init__.py +0 -0
  46. zayt-0.1.0/src/zayt/web/exception_handler/decorator.py +26 -0
  47. zayt-0.1.0/src/zayt/web/exception_handler/discover.py +26 -0
  48. zayt-0.1.0/src/zayt/web/handler/__init__.py +0 -0
  49. zayt-0.1.0/src/zayt/web/handler/model.py +9 -0
  50. zayt-0.1.0/src/zayt/web/handler/parse.py +62 -0
  51. zayt-0.1.0/src/zayt/web/lifecycle/__init__.py +0 -0
  52. zayt-0.1.0/src/zayt/web/lifecycle/decorator.py +16 -0
  53. zayt-0.1.0/src/zayt/web/lifecycle/discover.py +18 -0
  54. zayt-0.1.0/src/zayt/web/middleware/__init__.py +0 -0
  55. zayt-0.1.0/src/zayt/web/middleware/files.py +122 -0
  56. zayt-0.1.0/src/zayt/web/middleware/request_id.py +18 -0
  57. zayt-0.1.0/src/zayt/web/routing/__init__.py +0 -0
  58. zayt-0.1.0/src/zayt/web/routing/decorator.py +126 -0
  59. zayt-0.1.0/src/zayt/web/routing/exception.py +46 -0
  60. zayt-0.1.0/src/zayt/web/routing/route.py +111 -0
  61. zayt-0.1.0/src/zayt/web/routing/router.py +94 -0
  62. zayt-0.1.0/tests/__init__.py +0 -0
  63. zayt-0.1.0/tests/conf/__init__.py +0 -0
  64. zayt-0.1.0/tests/conf/alternate/configuration/application.py +5 -0
  65. zayt-0.1.0/tests/conf/alternate/configuration/application_prd.py +1 -0
  66. zayt-0.1.0/tests/conf/base/configuration/settings.py +5 -0
  67. zayt-0.1.0/tests/conf/dotenv/.env +1 -0
  68. zayt-0.1.0/tests/conf/dotenv/.gitignore +1 -0
  69. zayt-0.1.0/tests/conf/empty/configuration/settings.py +0 -0
  70. zayt-0.1.0/tests/conf/invalid_configuration/invalid_yaml/configuration/settings.py +1 -0
  71. zayt-0.1.0/tests/conf/multiple_profiles/configuration/settings.py +0 -0
  72. zayt-0.1.0/tests/conf/multiple_profiles/configuration/settings_dev.py +1 -0
  73. zayt-0.1.0/tests/conf/multiple_profiles/configuration/settings_prd.py +1 -0
  74. zayt-0.1.0/tests/conf/multiple_profiles/configuration/settings_stg.py +1 -0
  75. zayt-0.1.0/tests/conf/override/configuration/settings.py +1 -0
  76. zayt-0.1.0/tests/conf/override/configuration/settings_dev.py +1 -0
  77. zayt-0.1.0/tests/conf/override/configuration/settings_prd.py +1 -0
  78. zayt-0.1.0/tests/conf/override/configuration/settings_stg.py +1 -0
  79. zayt-0.1.0/tests/conf/profiles/configuration/settings.py +1 -0
  80. zayt-0.1.0/tests/conf/profiles/configuration/settings_dev.py +1 -0
  81. zayt-0.1.0/tests/conf/profiles/configuration/settings_prd.py +1 -0
  82. zayt-0.1.0/tests/conf/profiles/configuration/settings_stg.py +1 -0
  83. zayt-0.1.0/tests/conf/test_dotenv.py +20 -0
  84. zayt-0.1.0/tests/conf/test_envvar.py +65 -0
  85. zayt-0.1.0/tests/conf/test_settings.py +341 -0
  86. zayt-0.1.0/tests/conftest.py +13 -0
  87. zayt-0.1.0/tests/di/__init__.py +0 -0
  88. zayt-0.1.0/tests/di/conftest.py +8 -0
  89. zayt-0.1.0/tests/di/service/__init__.py +0 -0
  90. zayt-0.1.0/tests/di/service/parse/__init__.py +0 -0
  91. zayt-0.1.0/tests/di/service/parse/test_parse_service_spec.py +14 -0
  92. zayt-0.1.0/tests/di/service/parse/test_service_class.py +149 -0
  93. zayt-0.1.0/tests/di/service/parse/test_service_factory.py +145 -0
  94. zayt-0.1.0/tests/di/test_decorator.py +65 -0
  95. zayt-0.1.0/tests/di/test_define_instance.py +26 -0
  96. zayt-0.1.0/tests/di/test_dependency_complex_graph.py +100 -0
  97. zayt-0.1.0/tests/di/test_dependency_loop.py +47 -0
  98. zayt-0.1.0/tests/di/test_dependency_options.py +28 -0
  99. zayt-0.1.0/tests/di/test_generics.py +73 -0
  100. zayt-0.1.0/tests/di/test_interceptor.py +24 -0
  101. zayt-0.1.0/tests/di/test_iter_service.py +55 -0
  102. zayt-0.1.0/tests/di/test_lifecycle_callbacks.py +171 -0
  103. zayt-0.1.0/tests/di/test_named_dependencies.py +89 -0
  104. zayt-0.1.0/tests/di/test_register.py +20 -0
  105. zayt-0.1.0/tests/di/test_scope.py +56 -0
  106. zayt-0.1.0/tests/di/test_scope_mixed.py +34 -0
  107. zayt-0.1.0/tests/di/test_service_class.py +74 -0
  108. zayt-0.1.0/tests/di/test_service_function.py +113 -0
  109. zayt-0.1.0/tests/di/test_service_generator.py +88 -0
  110. zayt-0.1.0/tests/di/test_service_generator_async.py +103 -0
  111. zayt-0.1.0/tests/di/test_unknown_service.py +26 -0
  112. zayt-0.1.0/tests/examples/__init__.py +0 -0
  113. zayt-0.1.0/tests/examples/test_background_tasks.py +31 -0
  114. zayt-0.1.0/tests/examples/test_exception_handler.py +31 -0
  115. zayt-0.1.0/tests/examples/test_exceptions.py +33 -0
  116. zayt-0.1.0/tests/examples/test_hello_world.py +39 -0
  117. zayt-0.1.0/tests/examples/test_htmx.py +33 -0
  118. zayt-0.1.0/tests/examples/test_jinja.py +30 -0
  119. zayt-0.1.0/tests/examples/test_memcached.py +38 -0
  120. zayt-0.1.0/tests/examples/test_middleware.py +39 -0
  121. zayt-0.1.0/tests/examples/test_middleware_files.py +39 -0
  122. zayt-0.1.0/tests/examples/test_redis.py +38 -0
  123. zayt-0.1.0/tests/examples/test_settings.py +33 -0
  124. zayt-0.1.0/tests/examples/test_sqlalchemy.py +44 -0
  125. zayt-0.1.0/tests/examples/test_websocket.py +37 -0
  126. zayt-0.1.0/tests/examples/util.py +19 -0
  127. zayt-0.1.0/tests/ext/__init__.py +0 -0
  128. zayt-0.1.0/tests/ext/application.py +0 -0
  129. zayt-0.1.0/tests/ext/extension.py +0 -0
  130. zayt-0.1.0/tests/ext/jinja/__init__.py +0 -0
  131. zayt-0.1.0/tests/ext/jinja/application.py +29 -0
  132. zayt-0.1.0/tests/ext/jinja/templates/template.html +1 -0
  133. zayt-0.1.0/tests/ext/jinja/test_response.py +59 -0
  134. zayt-0.1.0/tests/ext/memcached/__init__.py +0 -0
  135. zayt-0.1.0/tests/ext/memcached/application.py +14 -0
  136. zayt-0.1.0/tests/ext/memcached/application_named.py +16 -0
  137. zayt-0.1.0/tests/ext/memcached/test_application.py +46 -0
  138. zayt-0.1.0/tests/ext/memcached/test_service.py +61 -0
  139. zayt-0.1.0/tests/ext/redis/__init__.py +0 -0
  140. zayt-0.1.0/tests/ext/redis/application.py +13 -0
  141. zayt-0.1.0/tests/ext/redis/application_named.py +16 -0
  142. zayt-0.1.0/tests/ext/redis/test_application.py +46 -0
  143. zayt-0.1.0/tests/ext/redis/test_service.py +127 -0
  144. zayt-0.1.0/tests/ext/register_extension/__init__.py +0 -0
  145. zayt-0.1.0/tests/ext/register_extension/application.py +1 -0
  146. zayt-0.1.0/tests/ext/register_extension/extension.py +2 -0
  147. zayt-0.1.0/tests/ext/register_extension/test_extension.py +20 -0
  148. zayt-0.1.0/tests/ext/sqlalchemy/__init__.py +0 -0
  149. zayt-0.1.0/tests/ext/sqlalchemy/application.py +14 -0
  150. zayt-0.1.0/tests/ext/sqlalchemy/application_named.py +17 -0
  151. zayt-0.1.0/tests/ext/sqlalchemy/application_session.py +22 -0
  152. zayt-0.1.0/tests/ext/sqlalchemy/application_session_startup.py +9 -0
  153. zayt-0.1.0/tests/ext/sqlalchemy/test_application.py +39 -0
  154. zayt-0.1.0/tests/ext/sqlalchemy/test_service.py +187 -0
  155. zayt-0.1.0/tests/ext/sqlalchemy/test_service_postgres.py +154 -0
  156. zayt-0.1.0/tests/ext/sqlalchemy/test_session.py +49 -0
  157. zayt-0.1.0/tests/ext/sqlalchemy/test_sessionmaker_service.py +240 -0
  158. zayt-0.1.0/tests/ext/test_extensions.py +40 -0
  159. zayt-0.1.0/tests/util/__init__.py +0 -0
  160. zayt-0.1.0/tests/util/package_to_scan/__init__.py +0 -0
  161. zayt-0.1.0/tests/util/package_to_scan/module_to_scan.py +6 -0
  162. zayt-0.1.0/tests/util/test_get_base_types.py +67 -0
  163. zayt-0.1.0/tests/util/test_import_item.py +44 -0
  164. zayt-0.1.0/tests/util/test_maybe_async.py +109 -0
  165. zayt-0.1.0/tests/util/test_maybe_async_sync.py +91 -0
  166. zayt-0.1.0/tests/util/test_package_scan.py +58 -0
  167. zayt-0.1.0/tests/web/__init__.py +0 -0
  168. zayt-0.1.0/tests/web/application/__init__.py +0 -0
  169. zayt-0.1.0/tests/web/application/application.py +18 -0
  170. zayt-0.1.0/tests/web/application/test_application.py +29 -0
  171. zayt-0.1.0/tests/web/application/test_middleware.py +69 -0
  172. zayt-0.1.0/tests/web/application/test_service_startup.py +24 -0
  173. zayt-0.1.0/tests/web/application/test_websocket.py +46 -0
  174. zayt-0.1.0/tests/web/exception_handler/__init__.py +0 -0
  175. zayt-0.1.0/tests/web/exception_handler/application.py +39 -0
  176. zayt-0.1.0/tests/web/exception_handler/test_exception_handler.py +44 -0
  177. zayt-0.1.0/tests/web/middleware/__init__.py +0 -0
  178. zayt-0.1.0/tests/web/middleware/files/__init__.py +0 -0
  179. zayt-0.1.0/tests/web/middleware/files/application.py +0 -0
  180. zayt-0.1.0/tests/web/middleware/files/resources/assets/style.css +1 -0
  181. zayt-0.1.0/tests/web/middleware/files/resources/media/data.json +1 -0
  182. zayt-0.1.0/tests/web/middleware/files/resources/static/lorem-ipsum.txt +1 -0
  183. zayt-0.1.0/tests/web/middleware/files/resources/uploads/lorem-ipsum.txt +1 -0
  184. zayt-0.1.0/tests/web/middleware/files/test_staticfiles.py +116 -0
  185. zayt-0.1.0/tests/web/middleware/files/test_uploadedfiles.py +89 -0
  186. zayt-0.1.0/tests/web/routing/__init__.py +0 -0
  187. zayt-0.1.0/tests/web/routing/test_decorator.py +158 -0
  188. zayt-0.1.0/tests/web/routing/test_router.py +21 -0
  189. 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,2 @@
1
+ from zayt.conf.environment import env # noqa: F401
2
+ from zayt.conf.settings import Settings # noqa: F401
@@ -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,5 @@
1
+ # ruff: noqa: F401
2
+
3
+ from zayt.di.container import Container
4
+ from zayt.di.decorator import service
5
+ from zayt.di.inject import Inject
@@ -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)