muscles-asgi 1.0.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 (37) hide show
  1. muscles_asgi-1.0.0/PKG-INFO +170 -0
  2. muscles_asgi-1.0.0/README.md +141 -0
  3. muscles_asgi-1.0.0/pyproject.toml +35 -0
  4. muscles_asgi-1.0.0/src/muscles/asgi/Makefile +26 -0
  5. muscles_asgi-1.0.0/src/muscles/asgi/__about__.py +6 -0
  6. muscles_asgi-1.0.0/src/muscles/asgi/__init__.py +62 -0
  7. muscles_asgi-1.0.0/src/muscles/asgi/action_bridge.py +260 -0
  8. muscles_asgi-1.0.0/src/muscles/asgi/app.py +72 -0
  9. muscles_asgi-1.0.0/src/muscles/asgi/asgi/__init__.py +43 -0
  10. muscles_asgi-1.0.0/src/muscles/asgi/asgi/actor.py +52 -0
  11. muscles_asgi-1.0.0/src/muscles/asgi/asgi/error_handler.py +68 -0
  12. muscles_asgi-1.0.0/src/muscles/asgi/asgi/http_code.py +397 -0
  13. muscles_asgi-1.0.0/src/muscles/asgi/asgi/request.py +1091 -0
  14. muscles_asgi-1.0.0/src/muscles/asgi/asgi/response.py +576 -0
  15. muscles_asgi-1.0.0/src/muscles/asgi/asgi/routers.py +159 -0
  16. muscles_asgi-1.0.0/src/muscles/asgi/asgi/server.py +739 -0
  17. muscles_asgi-1.0.0/src/muscles/asgi/asgi/strategy.py +130 -0
  18. muscles_asgi-1.0.0/src/muscles/asgi/assets/__init__.py +6 -0
  19. muscles_asgi-1.0.0/src/muscles/asgi/assets/asset.py +81 -0
  20. muscles_asgi-1.0.0/src/muscles/asgi/assets/axios.min.js +3 -0
  21. muscles_asgi-1.0.0/src/muscles/asgi/assets/vue.global.js +15934 -0
  22. muscles_asgi-1.0.0/src/muscles/asgi/providers.py +38 -0
  23. muscles_asgi-1.0.0/src/muscles/asgi/restful/__init__.py +31 -0
  24. muscles_asgi-1.0.0/src/muscles/asgi/restful/mount.py +83 -0
  25. muscles_asgi-1.0.0/src/muscles/asgi/restful/parameters.py +46 -0
  26. muscles_asgi-1.0.0/src/muscles/asgi/restful/request_body.py +58 -0
  27. muscles_asgi-1.0.0/src/muscles/asgi/restful/response_body.py +39 -0
  28. muscles_asgi-1.0.0/src/muscles/asgi/restful/restful.py +275 -0
  29. muscles_asgi-1.0.0/src/muscles/asgi/restful/swagger.py +290 -0
  30. muscles_asgi-1.0.0/src/muscles/asgi/restful/templates/swagger.jinja2 +38 -0
  31. muscles_asgi-1.0.0/src/muscles/asgi/template/__init__.py +10 -0
  32. muscles_asgi-1.0.0/src/muscles/asgi/template/template.py +464 -0
  33. muscles_asgi-1.0.0/src/muscles/asgi/testing.py +140 -0
  34. muscles_asgi-1.0.0/src/muscles/asgi/uwsgi/__init__.py +5 -0
  35. muscles_asgi-1.0.0/src/muscles/asgi/uwsgi/uwsgi.py +18 -0
  36. muscles_asgi-1.0.0/src/muscles/asgi/watchdog/__init__.py +6 -0
  37. muscles_asgi-1.0.0/src/muscles/asgi/watchdog/watchdog.py +137 -0
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: muscles-asgi
3
+ Version: 1.0.0
4
+ Summary: Muscles ASGI runtime adapter for Web/API applications
5
+ License: MIT
6
+ Author: Denis B.
7
+ Author-email: denis@butko.info
8
+ Requires-Python: >=3.10,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Provides-Extra: mime
17
+ Provides-Extra: server
18
+ Requires-Dist: dnspython (==2.6.1)
19
+ Requires-Dist: jinja2 (==3.1.2)
20
+ Requires-Dist: jsonschema (==4.16.0)
21
+ Requires-Dist: muscles (>=1.0.0rc1,<2.0.0)
22
+ Requires-Dist: python-magic (==0.4.27) ; extra == "mime"
23
+ Requires-Dist: requests (>=2.31.0,<3.0.0)
24
+ Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
25
+ Requires-Dist: uwsgi (==2.0.20) ; extra == "server"
26
+ Requires-Dist: watchdog (>=4.0.0,<5.0.0)
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Muscles ASGI
30
+
31
+ `muscles-asgi` is the ASGI runtime for Muscles. It mirrors the WSGI API model
32
+ while using ASGI scopes/events for async-capable servers.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install muscles-asgi
38
+ ```
39
+
40
+ Canonical ecosystem install matrix is documented in core:
41
+ [Muscles installation matrix](https://github.com/butkoden/muscles/blob/master/docs/installation.md).
42
+
43
+ ## Related Repositories
44
+
45
+ - [`muscles`](https://github.com/butkoden/muscles) - core contracts, routing, actions and canonical documentation.
46
+ - [`muscles-wsgi`](https://github.com/butkoden/muscles-wsgi) - WSGI runtime with the same application model.
47
+ - [`muscles-cli`](https://github.com/butkoden/muscles-cli) - CLI projection and developer commands.
48
+ - [`muscles-sse`](https://github.com/butkoden/muscles-sse) - streaming projection for action results.
49
+ - [`muscles-mcp`](https://github.com/butkoden/muscles-mcp) - MCP projection that can bind to ASGI entrypoints.
50
+ - [`muscles-benchmarks`](https://github.com/butkoden/muscles-benchmarks) - ASGI regression and architecture checks.
51
+
52
+ ## Runtime
53
+
54
+ An app binds `Context` to `AsgiStrategy`:
55
+
56
+ ```python
57
+ from muscles import ApplicationMeta, Configurator, Context
58
+ from muscles.asgi import AsgiStrategy
59
+
60
+
61
+ class App(metaclass=ApplicationMeta):
62
+ config = Configurator(obj={"main": {"HOST": "0.0.0.0", "PORT": "8080"}})
63
+ context = Context(AsgiStrategy, params={})
64
+
65
+ def run(self, *args):
66
+ return self.context.execute(*args, shutup=True)
67
+ ```
68
+
69
+ The ASGI package shares core schemas, route matching and OpenAPI generation with
70
+ the other runtimes. It should not duplicate core itinerary code.
71
+
72
+ ## Schema Ownership
73
+
74
+ ASGI does not ship its own `muscles.asgi.schema_` package. Framework schemas,
75
+ columns, fields, request/response bodies, security objects and value objects
76
+ belong to `muscles.core.schema` and should be imported from `muscles`:
77
+
78
+ ```python
79
+ from muscles import Column, JsonRequestBody, Model, String
80
+ ```
81
+
82
+ ## REST API And Swagger
83
+
84
+ `RestApi` controllers/actions are projected into OpenAPI automatically. Generated
85
+ paths include the external API prefix, so a mounted route such as
86
+ `/api/v1/bookings` appears in Swagger exactly as clients must call it.
87
+
88
+ More detail: [docs/openapi-and-routing.md](docs/openapi-and-routing.md).
89
+ Backend pipeline features are documented in
90
+ [docs/backend-pipeline.md](docs/backend-pipeline.md).
91
+
92
+ ## Application Inspection
93
+
94
+ Use `mount_api(app, api)` after declaring a `RestApi` to project ASGI routes into
95
+ the core `ApplicationRegistry`. This makes routes visible to
96
+ `inspect_application(app)` and CLI/doctor tooling without making the registry
97
+ the HTTP dispatch source.
98
+
99
+ Call it after all controllers/actions are registered and before running
100
+ inspection, doctor checks or generators. It is safe to call more than once:
101
+ already projected routes are skipped. `finalize_api(app, api)` is an alias for
102
+ projects that prefer a "declare, then finalize" bootstrap style.
103
+ `asgi_app(app)` also calls `mount_application_apis(app)` once when the ASGI
104
+ entrypoint is created, so APIs stored as attributes on the application are
105
+ visible to runtime inspection without additional app-side adapters.
106
+
107
+ ```python
108
+ from muscles import inspect_application
109
+ from muscles.asgi import RestApi, finalize_api
110
+
111
+ app = App()
112
+ api = RestApi(name="Api", prefix="/api")
113
+
114
+ @api.init("/ping", method="get")
115
+ def ping(request):
116
+ return {"ok": True}
117
+
118
+ finalize_api(app, api)
119
+ assert any(route["path"] == "/api/ping" for route in inspect_application(app)["routes"])
120
+ ```
121
+
122
+ Request dispatch still uses the ASGI `RestApi` route tree. The projected routes
123
+ are lightweight descriptors for tools that need to understand the application
124
+ without knowing ASGI internals.
125
+
126
+ When the application has a neutral `TelemetryProvider`, ASGI records a
127
+ `muscles.server.dispatch` span for matched routes with app, route, method and
128
+ HTTP status attributes.
129
+
130
+ ## Action Bridge
131
+
132
+ `ActionAsgiAdapter` is an optional HTTP projection for Muscles actions. It keeps
133
+ package-specific features out of ASGI routes: packages register normal core
134
+ actions, and ASGI exposes only the actions explicitly allowed by the project.
135
+
136
+ ```python
137
+ from muscles.asgi import ActionAsgiAdapter
138
+
139
+ application = ActionAsgiAdapter.from_application(
140
+ app,
141
+ allowed_actions={"bookings.echo"},
142
+ )
143
+ ```
144
+
145
+ The bridge accepts JSON `POST /actions/<action-name>` requests, executes through
146
+ core `ActionDispatcher(..., transport="http")`, returns JSON for non-stream
147
+ results, and returns `application/problem+json` for core action errors. Stream
148
+ actions should be projected through `muscles-sse`.
149
+
150
+ ## Request Handling
151
+
152
+ Request parsing does not require `cgi`, `multipart` or `python-magic` at import
153
+ time. Multipart form data uses the standard library path, and missing MIME
154
+ detection falls back safely.
155
+
156
+ ASGI request execution is stateless on `Context`: request-specific data
157
+ (`scope`, `receive`, `send`) is passed directly into `context.execute(...)`
158
+ per request. The strategy keeps a persistent server lifecycle while request
159
+ state stays isolated.
160
+
161
+ ## Development
162
+
163
+ Run tests with sibling packages on `PYTHONPATH`:
164
+
165
+ ```bash
166
+ PYTHONPATH=../muscles/src:src python -m pytest -q
167
+ ```
168
+
169
+ Production notes: [docs/production.md](docs/production.md).
170
+
@@ -0,0 +1,141 @@
1
+ # Muscles ASGI
2
+
3
+ `muscles-asgi` is the ASGI runtime for Muscles. It mirrors the WSGI API model
4
+ while using ASGI scopes/events for async-capable servers.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ pip install muscles-asgi
10
+ ```
11
+
12
+ Canonical ecosystem install matrix is documented in core:
13
+ [Muscles installation matrix](https://github.com/butkoden/muscles/blob/master/docs/installation.md).
14
+
15
+ ## Related Repositories
16
+
17
+ - [`muscles`](https://github.com/butkoden/muscles) - core contracts, routing, actions and canonical documentation.
18
+ - [`muscles-wsgi`](https://github.com/butkoden/muscles-wsgi) - WSGI runtime with the same application model.
19
+ - [`muscles-cli`](https://github.com/butkoden/muscles-cli) - CLI projection and developer commands.
20
+ - [`muscles-sse`](https://github.com/butkoden/muscles-sse) - streaming projection for action results.
21
+ - [`muscles-mcp`](https://github.com/butkoden/muscles-mcp) - MCP projection that can bind to ASGI entrypoints.
22
+ - [`muscles-benchmarks`](https://github.com/butkoden/muscles-benchmarks) - ASGI regression and architecture checks.
23
+
24
+ ## Runtime
25
+
26
+ An app binds `Context` to `AsgiStrategy`:
27
+
28
+ ```python
29
+ from muscles import ApplicationMeta, Configurator, Context
30
+ from muscles.asgi import AsgiStrategy
31
+
32
+
33
+ class App(metaclass=ApplicationMeta):
34
+ config = Configurator(obj={"main": {"HOST": "0.0.0.0", "PORT": "8080"}})
35
+ context = Context(AsgiStrategy, params={})
36
+
37
+ def run(self, *args):
38
+ return self.context.execute(*args, shutup=True)
39
+ ```
40
+
41
+ The ASGI package shares core schemas, route matching and OpenAPI generation with
42
+ the other runtimes. It should not duplicate core itinerary code.
43
+
44
+ ## Schema Ownership
45
+
46
+ ASGI does not ship its own `muscles.asgi.schema_` package. Framework schemas,
47
+ columns, fields, request/response bodies, security objects and value objects
48
+ belong to `muscles.core.schema` and should be imported from `muscles`:
49
+
50
+ ```python
51
+ from muscles import Column, JsonRequestBody, Model, String
52
+ ```
53
+
54
+ ## REST API And Swagger
55
+
56
+ `RestApi` controllers/actions are projected into OpenAPI automatically. Generated
57
+ paths include the external API prefix, so a mounted route such as
58
+ `/api/v1/bookings` appears in Swagger exactly as clients must call it.
59
+
60
+ More detail: [docs/openapi-and-routing.md](docs/openapi-and-routing.md).
61
+ Backend pipeline features are documented in
62
+ [docs/backend-pipeline.md](docs/backend-pipeline.md).
63
+
64
+ ## Application Inspection
65
+
66
+ Use `mount_api(app, api)` after declaring a `RestApi` to project ASGI routes into
67
+ the core `ApplicationRegistry`. This makes routes visible to
68
+ `inspect_application(app)` and CLI/doctor tooling without making the registry
69
+ the HTTP dispatch source.
70
+
71
+ Call it after all controllers/actions are registered and before running
72
+ inspection, doctor checks or generators. It is safe to call more than once:
73
+ already projected routes are skipped. `finalize_api(app, api)` is an alias for
74
+ projects that prefer a "declare, then finalize" bootstrap style.
75
+ `asgi_app(app)` also calls `mount_application_apis(app)` once when the ASGI
76
+ entrypoint is created, so APIs stored as attributes on the application are
77
+ visible to runtime inspection without additional app-side adapters.
78
+
79
+ ```python
80
+ from muscles import inspect_application
81
+ from muscles.asgi import RestApi, finalize_api
82
+
83
+ app = App()
84
+ api = RestApi(name="Api", prefix="/api")
85
+
86
+ @api.init("/ping", method="get")
87
+ def ping(request):
88
+ return {"ok": True}
89
+
90
+ finalize_api(app, api)
91
+ assert any(route["path"] == "/api/ping" for route in inspect_application(app)["routes"])
92
+ ```
93
+
94
+ Request dispatch still uses the ASGI `RestApi` route tree. The projected routes
95
+ are lightweight descriptors for tools that need to understand the application
96
+ without knowing ASGI internals.
97
+
98
+ When the application has a neutral `TelemetryProvider`, ASGI records a
99
+ `muscles.server.dispatch` span for matched routes with app, route, method and
100
+ HTTP status attributes.
101
+
102
+ ## Action Bridge
103
+
104
+ `ActionAsgiAdapter` is an optional HTTP projection for Muscles actions. It keeps
105
+ package-specific features out of ASGI routes: packages register normal core
106
+ actions, and ASGI exposes only the actions explicitly allowed by the project.
107
+
108
+ ```python
109
+ from muscles.asgi import ActionAsgiAdapter
110
+
111
+ application = ActionAsgiAdapter.from_application(
112
+ app,
113
+ allowed_actions={"bookings.echo"},
114
+ )
115
+ ```
116
+
117
+ The bridge accepts JSON `POST /actions/<action-name>` requests, executes through
118
+ core `ActionDispatcher(..., transport="http")`, returns JSON for non-stream
119
+ results, and returns `application/problem+json` for core action errors. Stream
120
+ actions should be projected through `muscles-sse`.
121
+
122
+ ## Request Handling
123
+
124
+ Request parsing does not require `cgi`, `multipart` or `python-magic` at import
125
+ time. Multipart form data uses the standard library path, and missing MIME
126
+ detection falls back safely.
127
+
128
+ ASGI request execution is stateless on `Context`: request-specific data
129
+ (`scope`, `receive`, `send`) is passed directly into `context.execute(...)`
130
+ per request. The strategy keeps a persistent server lifecycle while request
131
+ state stays isolated.
132
+
133
+ ## Development
134
+
135
+ Run tests with sibling packages on `PYTHONPATH`:
136
+
137
+ ```bash
138
+ PYTHONPATH=../muscles/src:src python -m pytest -q
139
+ ```
140
+
141
+ Production notes: [docs/production.md](docs/production.md).
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["poetry-core>=1.0.0"]
3
+ build-backend = "poetry.core.masonry.api"
4
+
5
+
6
+ [tool.poetry]
7
+ name = "muscles-asgi"
8
+ version = "1.0.0"
9
+ description = "Muscles ASGI runtime adapter for Web/API applications"
10
+ readme = "README.md"
11
+ authors = ["Denis B. <denis@butko.info>"]
12
+ license = "MIT"
13
+ packages = [{include = "muscles", from = "src"}]
14
+
15
+
16
+ [tool.poetry.dependencies]
17
+ python = ">=3.10,<4.0"
18
+ muscles = ">=1.0.0rc1,<2.0.0"
19
+ watchdog = "^4.0.0"
20
+ uwsgi = {version = "==2.0.20", optional = true}
21
+ jinja2 = "==3.1.2"
22
+ jsonschema = "==4.16.0"
23
+ python-magic = {version = "==0.4.27", optional = true}
24
+ requests = "^2.31.0"
25
+ requests-toolbelt = "^1.0.0"
26
+ dnspython = "==2.6.1"
27
+
28
+
29
+ [tool.poetry.dev-dependencies]
30
+ pytest = "==7.1.3"
31
+ setuptools = "==69.0.2"
32
+
33
+ [tool.poetry.extras]
34
+ server = ["uwsgi"]
35
+ mime = ["python-magic"]
@@ -0,0 +1,26 @@
1
+ # Makefile
2
+
3
+ start:
4
+ docker-compose up -d
5
+ docker exec -it butkoinfo_core_1 bash
6
+
7
+
8
+ stop:
9
+ docker-compose up -d
10
+ docker exec -it butkoinfo-core-1 bash
11
+
12
+
13
+ console:
14
+ docker exec -it butkoinfo_core_1 bash
15
+
16
+
17
+ logs:
18
+ docker-compose logs
19
+
20
+
21
+ develop:
22
+ #docker exec -it butkoinfo_core_1 pipenv install
23
+ pipenv run python setup.py develop
24
+ pipenv install
25
+
26
+
@@ -0,0 +1,6 @@
1
+ # -*- coding: utf-8 -*-
2
+ __name__ = "Muscular CLI Framework asgi"
3
+ __version__ = "1.0.0.dev"
4
+ __description__ = (
5
+ "Muscular CLI Framework"
6
+ )
@@ -0,0 +1,62 @@
1
+ from .asgi import *
2
+ from .watchdog import *
3
+ from .uwsgi import *
4
+ from .template import Template
5
+ from .template import *
6
+ from .restful import *
7
+ from .assets import *
8
+ from .app import MuscularAsgiApp, asgi_app
9
+ from .action_bridge import ActionAsgiAdapter
10
+ from .providers import AsgiGeneratorProvider
11
+ from .testing import TestClient, TestResponse
12
+
13
+
14
+ __all__ = (
15
+ "Asset",
16
+ "RestApi",
17
+ "finalize_api",
18
+ "mount_api",
19
+ "mount_application_apis",
20
+ "TemplateLoader",
21
+ "Filters",
22
+ "Template",
23
+ "UwsgiReload",
24
+ "Watchdog",
25
+ "PatternMatchingHandler",
26
+ "ResponseErrorHandler",
27
+ "AsgiStrategy",
28
+ "ImproperBodyPartContentException",
29
+ "NonMultipartContentTypeException",
30
+ "BodyPart",
31
+ "FileStorage",
32
+ "FieldStorage",
33
+ "Request",
34
+ "Response",
35
+ "BadResponse",
36
+ "BaseResponse",
37
+ "MakeResponse",
38
+ "JsonResponse",
39
+ "HtmlResponse",
40
+ "code_status",
41
+ "Transport",
42
+ "AsgiTransport",
43
+ "AsgiServer",
44
+ "RouteRule",
45
+ "RouteRuleDefault",
46
+ "RouteRuleVar",
47
+ "RouteRuleInt",
48
+ "RouteRuleFloat",
49
+ "Itinerary",
50
+ "Node",
51
+ "Routes",
52
+ "Api",
53
+ "api",
54
+ "routes",
55
+ "itinerary",
56
+ "MuscularAsgiApp",
57
+ "asgi_app",
58
+ "ActionAsgiAdapter",
59
+ "AsgiGeneratorProvider",
60
+ "TestClient",
61
+ "TestResponse",
62
+ )