openframe-core 1.0.0__py3-none-any.whl
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.
- openframe/__init__.py +1 -0
- openframe/core/__init__.py +1 -0
- openframe/core/config/__init__.py +17 -0
- openframe/core/config/settings.py +70 -0
- openframe/core/exceptions/__init__.py +39 -0
- openframe/core/exceptions/errors.py +189 -0
- openframe/core/health/__init__.py +19 -0
- openframe/core/health/protocol.py +94 -0
- openframe/core/middleware/__init__.py +55 -0
- openframe/core/middleware/telemetry.py +315 -0
- openframe/core/middleware/types.py +56 -0
- openframe/core/ports/__init__.py +29 -0
- openframe/core/ports/consumer.py +125 -0
- openframe/core/ports/producer.py +94 -0
- openframe/core/ports/repository.py +143 -0
- openframe/core/telemetry/__init__.py +43 -0
- openframe/core/telemetry/setup.py +230 -0
- openframe/core/tracing/__init__.py +24 -0
- openframe/core/tracing/proxy.py +135 -0
- openframe_core-1.0.0.dist-info/METADATA +219 -0
- openframe_core-1.0.0.dist-info/RECORD +23 -0
- openframe_core-1.0.0.dist-info/WHEEL +4 -0
- openframe_core-1.0.0.dist-info/licenses/LICENSE +21 -0
openframe/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
openframe/core/config/
|
|
3
|
+
=======================
|
|
4
|
+
Base configuration primitives for all OpenFrame adapter packages.
|
|
5
|
+
|
|
6
|
+
Every adapter config class subclasses ``BaseAdapterSettings``, which reads
|
|
7
|
+
field values from environment variables via Pydantic Settings.
|
|
8
|
+
|
|
9
|
+
Usage::
|
|
10
|
+
|
|
11
|
+
from openframe.core.config import BaseAdapterSettings
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from openframe.core.config.settings import BaseAdapterSettings
|
|
16
|
+
|
|
17
|
+
__all__ = ["BaseAdapterSettings"]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
openframe/core/config/settings.py
|
|
3
|
+
====================================
|
|
4
|
+
Base settings class for all OpenFrame adapter packages.
|
|
5
|
+
|
|
6
|
+
Every adapter config (e.g. ``PostgresSettings``, ``RedisSettings``) must
|
|
7
|
+
subclass ``BaseAdapterSettings`` and declare its adapter-specific fields.
|
|
8
|
+
Pydantic Settings reads field values from environment variables automatically
|
|
9
|
+
— the field name maps directly to the env var name (case-insensitive).
|
|
10
|
+
|
|
11
|
+
Missing required fields raise ``pydantic_core.ValidationError`` at
|
|
12
|
+
instantiation time, not at first use. This means misconfigured deployments
|
|
13
|
+
fail fast on startup rather than at the first request.
|
|
14
|
+
|
|
15
|
+
Dependency order: this module imports only from pydantic-settings (external).
|
|
16
|
+
No openframe.core imports.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
21
|
+
|
|
22
|
+
__all__ = ["BaseAdapterSettings"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BaseAdapterSettings(BaseSettings):
|
|
26
|
+
"""
|
|
27
|
+
Base settings for all OpenFrame adapter packages.
|
|
28
|
+
|
|
29
|
+
Subclass in each adapter's ``config/settings.py`` and declare
|
|
30
|
+
adapter-specific fields. All fields are read from environment variables
|
|
31
|
+
automatically by Pydantic Settings.
|
|
32
|
+
|
|
33
|
+
Configuration:
|
|
34
|
+
- ``env_prefix = ""`` — field names map directly to env var names.
|
|
35
|
+
- ``case_sensitive = False`` — ``DATABASE_URL`` maps to ``database_url``.
|
|
36
|
+
- ``extra = "ignore"`` — unknown env vars are silently ignored.
|
|
37
|
+
|
|
38
|
+
Example::
|
|
39
|
+
|
|
40
|
+
class PostgresSettings(BaseAdapterSettings):
|
|
41
|
+
database_url: str # reads DATABASE_URL — required
|
|
42
|
+
pool_size: int = 10 # reads POOL_SIZE — optional, default 10
|
|
43
|
+
pool_max_overflow: int = 5 # reads POOL_MAX_OVERFLOW
|
|
44
|
+
|
|
45
|
+
# Missing DATABASE_URL raises ValidationError immediately:
|
|
46
|
+
settings = PostgresSettings()
|
|
47
|
+
|
|
48
|
+
Common fields available on every adapter settings instance:
|
|
49
|
+
|
|
50
|
+
Attributes:
|
|
51
|
+
adapter_name: Identifies the adapter in logs and traces.
|
|
52
|
+
Reads ``ADAPTER_NAME`` env var.
|
|
53
|
+
connection_timeout: Seconds to wait when establishing a connection.
|
|
54
|
+
Reads ``CONNECTION_TIMEOUT``. Default: 30.0.
|
|
55
|
+
operation_timeout: Seconds to wait for a single operation to complete.
|
|
56
|
+
Reads ``OPERATION_TIMEOUT``. Default: 10.0.
|
|
57
|
+
max_retries: Maximum number of retry attempts on transient failure.
|
|
58
|
+
Reads ``MAX_RETRIES``. Default: 3.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
model_config = SettingsConfigDict(
|
|
62
|
+
env_prefix="",
|
|
63
|
+
case_sensitive=False,
|
|
64
|
+
extra="ignore",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
adapter_name: str = "openframe"
|
|
68
|
+
connection_timeout: float = 30.0
|
|
69
|
+
operation_timeout: float = 10.0
|
|
70
|
+
max_retries: int = 3
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
openframe/core/exceptions/
|
|
3
|
+
============================
|
|
4
|
+
Structured exception hierarchy for all OpenFrame adapter packages.
|
|
5
|
+
|
|
6
|
+
All adapter packages raise only these exceptions — never raw driver
|
|
7
|
+
exceptions. Services catch ``AdapterError`` as a single catch point
|
|
8
|
+
regardless of which adapter is wired in.
|
|
9
|
+
|
|
10
|
+
Usage::
|
|
11
|
+
|
|
12
|
+
from openframe.core.exceptions import (
|
|
13
|
+
AdapterError,
|
|
14
|
+
AdapterConnectionError,
|
|
15
|
+
AdapterQueryError,
|
|
16
|
+
AdapterNotFoundError,
|
|
17
|
+
AdapterConfigurationError,
|
|
18
|
+
AdapterTimeoutError,
|
|
19
|
+
)
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from openframe.core.exceptions.errors import (
|
|
24
|
+
AdapterConfigurationError,
|
|
25
|
+
AdapterConnectionError,
|
|
26
|
+
AdapterError,
|
|
27
|
+
AdapterNotFoundError,
|
|
28
|
+
AdapterQueryError,
|
|
29
|
+
AdapterTimeoutError,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"AdapterError",
|
|
34
|
+
"AdapterConnectionError",
|
|
35
|
+
"AdapterQueryError",
|
|
36
|
+
"AdapterNotFoundError",
|
|
37
|
+
"AdapterConfigurationError",
|
|
38
|
+
"AdapterTimeoutError",
|
|
39
|
+
]
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""
|
|
2
|
+
openframe/core/exceptions/errors.py
|
|
3
|
+
=====================================
|
|
4
|
+
Structured exception hierarchy for all OpenFrame adapter packages.
|
|
5
|
+
|
|
6
|
+
Every adapter package raises only AdapterError subclasses — never raw
|
|
7
|
+
driver exceptions (psycopg errors, aioredis errors, aiokafka errors, etc.).
|
|
8
|
+
Services catch AdapterError at a single point regardless of which adapter
|
|
9
|
+
is wired in.
|
|
10
|
+
|
|
11
|
+
Raise with cause chaining at the raise site::
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
result = await conn.execute(query)
|
|
15
|
+
except SomeDriverError as exc:
|
|
16
|
+
raise AdapterQueryError(
|
|
17
|
+
message="Query failed",
|
|
18
|
+
adapter="postgres",
|
|
19
|
+
operation="get",
|
|
20
|
+
cause=exc,
|
|
21
|
+
) from exc
|
|
22
|
+
|
|
23
|
+
Dependency order: this module has no imports from openframe.core.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"AdapterError",
|
|
29
|
+
"AdapterConnectionError",
|
|
30
|
+
"AdapterQueryError",
|
|
31
|
+
"AdapterNotFoundError",
|
|
32
|
+
"AdapterConfigurationError",
|
|
33
|
+
"AdapterTimeoutError",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AdapterError(Exception):
|
|
38
|
+
"""
|
|
39
|
+
Base exception for all OpenFrame adapter packages.
|
|
40
|
+
|
|
41
|
+
All adapter packages raise only AdapterError subclasses — never raw
|
|
42
|
+
driver exceptions. Services catch AdapterError for a single catch
|
|
43
|
+
point regardless of which adapter is wired in.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
message: Human-readable description of what went wrong.
|
|
47
|
+
adapter: Adapter identifier, e.g. "postgres", "redis", "kafka".
|
|
48
|
+
operation: Operation that failed, e.g. "get", "create", "publish".
|
|
49
|
+
cause: Underlying driver exception, if any. Always chain at
|
|
50
|
+
the raise site: ``raise AdapterXError(...) from exc``.
|
|
51
|
+
|
|
52
|
+
The ``__str__`` format is::
|
|
53
|
+
|
|
54
|
+
[adapter.operation] message
|
|
55
|
+
[adapter.operation] message — caused by: <cause>
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
message: str,
|
|
61
|
+
adapter: str,
|
|
62
|
+
operation: str,
|
|
63
|
+
cause: Exception | None = None,
|
|
64
|
+
) -> None:
|
|
65
|
+
"""
|
|
66
|
+
Initialise the exception.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
message: Human-readable description of the failure.
|
|
70
|
+
adapter: Adapter identifier (e.g. "postgres", "redis").
|
|
71
|
+
operation: Operation that failed (e.g. "get", "create").
|
|
72
|
+
cause: Underlying driver exception, or None.
|
|
73
|
+
"""
|
|
74
|
+
super().__init__(message) # only message → self.args = (message,)
|
|
75
|
+
self.message = message
|
|
76
|
+
self.adapter = adapter
|
|
77
|
+
self.operation = operation
|
|
78
|
+
self.cause = cause
|
|
79
|
+
|
|
80
|
+
def __str__(self) -> str:
|
|
81
|
+
"""
|
|
82
|
+
Return a human-readable string including adapter, operation, and message.
|
|
83
|
+
|
|
84
|
+
If a cause is present it is appended after \" — caused by: \".
|
|
85
|
+
"""
|
|
86
|
+
base = f"[{self.adapter}.{self.operation}] {self.message}"
|
|
87
|
+
if self.cause:
|
|
88
|
+
return f"{base} — caused by: {self.cause}"
|
|
89
|
+
return base
|
|
90
|
+
|
|
91
|
+
def __repr__(self) -> str:
|
|
92
|
+
return (
|
|
93
|
+
f"{type(self).__name__}("
|
|
94
|
+
f"message={self.message!r}, "
|
|
95
|
+
f"adapter={self.adapter!r}, "
|
|
96
|
+
f"operation={self.operation!r}, "
|
|
97
|
+
f"cause={self.cause!r})"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class AdapterConnectionError(AdapterError):
|
|
102
|
+
"""
|
|
103
|
+
Raised when the adapter cannot establish a connection to its backend.
|
|
104
|
+
|
|
105
|
+
Typical causes: network unreachable, wrong host/port, TLS handshake
|
|
106
|
+
failure, authentication rejected before a connection is established.
|
|
107
|
+
|
|
108
|
+
Raise with cause chaining::
|
|
109
|
+
|
|
110
|
+
raise AdapterConnectionError(
|
|
111
|
+
"Cannot reach Postgres at localhost:5432",
|
|
112
|
+
adapter="postgres",
|
|
113
|
+
operation="connect",
|
|
114
|
+
cause=original_exc,
|
|
115
|
+
) from original_exc
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class AdapterQueryError(AdapterError):
|
|
120
|
+
"""
|
|
121
|
+
Raised when an operation fails after a connection is established.
|
|
122
|
+
|
|
123
|
+
Typical causes: constraint violation, syntax error, permission denied
|
|
124
|
+
on a specific table or topic, partial failure mid-batch.
|
|
125
|
+
|
|
126
|
+
Raise with cause chaining::
|
|
127
|
+
|
|
128
|
+
raise AdapterQueryError(
|
|
129
|
+
"INSERT violated unique constraint on items.slug",
|
|
130
|
+
adapter="postgres",
|
|
131
|
+
operation="create",
|
|
132
|
+
cause=original_exc,
|
|
133
|
+
) from original_exc
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class AdapterNotFoundError(AdapterError):
|
|
138
|
+
"""
|
|
139
|
+
Raised when the requested entity does not exist in the backend.
|
|
140
|
+
|
|
141
|
+
This is a semantic "not found" — the adapter successfully queried
|
|
142
|
+
the backend but the entity is absent. Distinct from AdapterQueryError
|
|
143
|
+
(which means the query itself failed).
|
|
144
|
+
|
|
145
|
+
Raise without a cause when the backend returns a definitive empty
|
|
146
|
+
result::
|
|
147
|
+
|
|
148
|
+
raise AdapterNotFoundError(
|
|
149
|
+
f"Item {item_id!r} not found",
|
|
150
|
+
adapter="postgres",
|
|
151
|
+
operation="get",
|
|
152
|
+
)
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class AdapterConfigurationError(AdapterError):
|
|
157
|
+
"""
|
|
158
|
+
Raised when required configuration is missing or invalid.
|
|
159
|
+
|
|
160
|
+
Typical causes: required env var absent, value fails validation
|
|
161
|
+
(wrong type, out of range), conflicting options.
|
|
162
|
+
|
|
163
|
+
Raise at adapter initialisation time (not at first query) so
|
|
164
|
+
misconfigured deployments fail fast on startup::
|
|
165
|
+
|
|
166
|
+
raise AdapterConfigurationError(
|
|
167
|
+
"DATABASE_URL must be set",
|
|
168
|
+
adapter="postgres",
|
|
169
|
+
operation="init",
|
|
170
|
+
)
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class AdapterTimeoutError(AdapterError):
|
|
175
|
+
"""
|
|
176
|
+
Raised when an operation exceeds its configured timeout.
|
|
177
|
+
|
|
178
|
+
Distinct from AdapterConnectionError — the connection was established
|
|
179
|
+
but the operation did not complete within the allowed window.
|
|
180
|
+
|
|
181
|
+
Raise with cause chaining when wrapping an asyncio.TimeoutError::
|
|
182
|
+
|
|
183
|
+
raise AdapterTimeoutError(
|
|
184
|
+
"Query exceeded 10s operation_timeout",
|
|
185
|
+
adapter="postgres",
|
|
186
|
+
operation="get",
|
|
187
|
+
cause=original_exc,
|
|
188
|
+
) from original_exc
|
|
189
|
+
"""
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
openframe/core/health/
|
|
3
|
+
=======================
|
|
4
|
+
Health check port for all OpenFrame adapter packages.
|
|
5
|
+
|
|
6
|
+
Every adapter implements ``HealthCheck`` so the application layer can
|
|
7
|
+
verify liveness and readiness without knowing which adapter is wired in.
|
|
8
|
+
|
|
9
|
+
Usage::
|
|
10
|
+
|
|
11
|
+
from openframe.core.health import HealthCheck
|
|
12
|
+
|
|
13
|
+
assert isinstance(my_adapter, HealthCheck)
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from openframe.core.health.protocol import HealthCheck
|
|
18
|
+
|
|
19
|
+
__all__ = ["HealthCheck"]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
openframe/core/health/protocol.py
|
|
3
|
+
====================================
|
|
4
|
+
Health check port for all OpenFrame adapter packages.
|
|
5
|
+
|
|
6
|
+
Every adapter implements ``HealthCheck`` so the application layer can verify
|
|
7
|
+
adapter liveness and readiness without knowing which adapter is wired in.
|
|
8
|
+
|
|
9
|
+
Two levels of check:
|
|
10
|
+
- ``ping()`` — cheap liveness. Used by load balancers and watchdogs. Should
|
|
11
|
+
complete in milliseconds (TCP connect, ``SELECT 1``).
|
|
12
|
+
- ``is_ready()`` — full readiness. Used at startup. More expensive — verifies
|
|
13
|
+
schema migrations applied, connection pool healthy, etc.
|
|
14
|
+
|
|
15
|
+
Runtime isinstance check::
|
|
16
|
+
|
|
17
|
+
isinstance(adapter, HealthCheck) # ✓ works
|
|
18
|
+
isinstance(obj, HealthCheck) # checks for ping and is_ready methods
|
|
19
|
+
|
|
20
|
+
Dependency order: this module imports only from Python stdlib typing.
|
|
21
|
+
No openframe.core imports.
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from typing import Protocol, runtime_checkable
|
|
26
|
+
|
|
27
|
+
__all__ = ["HealthCheck"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@runtime_checkable
|
|
31
|
+
class HealthCheck(Protocol):
|
|
32
|
+
"""
|
|
33
|
+
Health check port. Every adapter implements ``ping()`` and ``is_ready()``.
|
|
34
|
+
|
|
35
|
+
Adapters satisfy this protocol structurally — no inheritance needed.
|
|
36
|
+
Any class with both async method signatures satisfies ``HealthCheck``.
|
|
37
|
+
|
|
38
|
+
``ping()`` vs ``is_ready()``:
|
|
39
|
+
|
|
40
|
+
- ``ping()`` is a low-cost liveness check. Verify a TCP connection can be
|
|
41
|
+
opened, or run ``SELECT 1``. Should complete within milliseconds.
|
|
42
|
+
Called frequently by load balancers and watchdog processes.
|
|
43
|
+
|
|
44
|
+
- ``is_ready()`` is a full readiness check. Verify the connection pool is
|
|
45
|
+
healthy, required schemas exist, pending migrations have been applied,
|
|
46
|
+
etc. More expensive — called once at application startup and on
|
|
47
|
+
scheduled readiness probes.
|
|
48
|
+
|
|
49
|
+
Usage::
|
|
50
|
+
|
|
51
|
+
class PostgresRepository:
|
|
52
|
+
async def ping(self) -> bool:
|
|
53
|
+
try:
|
|
54
|
+
await self._pool.fetchval("SELECT 1")
|
|
55
|
+
return True
|
|
56
|
+
except Exception:
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
async def is_ready(self) -> bool:
|
|
60
|
+
try:
|
|
61
|
+
await self._pool.fetchval("SELECT COUNT(*) FROM pg_tables")
|
|
62
|
+
return True
|
|
63
|
+
except Exception:
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
assert isinstance(PostgresRepository(), HealthCheck)
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
async def ping(self) -> bool:
|
|
70
|
+
"""
|
|
71
|
+
Low-cost liveness check.
|
|
72
|
+
|
|
73
|
+
Verify the adapter can reach its backend. Should be fast and cheap —
|
|
74
|
+
a TCP connect attempt or ``SELECT 1`` is appropriate.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
True if the backend is reachable, False otherwise.
|
|
78
|
+
Must not raise — return False on any failure.
|
|
79
|
+
"""
|
|
80
|
+
...
|
|
81
|
+
|
|
82
|
+
async def is_ready(self) -> bool:
|
|
83
|
+
"""
|
|
84
|
+
Full readiness check.
|
|
85
|
+
|
|
86
|
+
Verify the adapter is fully ready to serve requests. More thorough
|
|
87
|
+
than ``ping()`` — check that the schema is correct, migrations are
|
|
88
|
+
applied, and the connection pool has healthy connections.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
True if the adapter is ready to serve, False otherwise.
|
|
92
|
+
Must not raise — return False on any failure.
|
|
93
|
+
"""
|
|
94
|
+
...
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
openframe/core/middleware/
|
|
3
|
+
===========================
|
|
4
|
+
Pure ASGI telemetry middleware and shared ASGI type aliases for the
|
|
5
|
+
OpenFrame ecosystem.
|
|
6
|
+
|
|
7
|
+
The middleware is framework-agnostic — it works with FastAPI, Starlette,
|
|
8
|
+
Litestar, or any bare ASGI application.
|
|
9
|
+
|
|
10
|
+
Usage::
|
|
11
|
+
|
|
12
|
+
from openframe.core.middleware import TelemetryMiddleware
|
|
13
|
+
|
|
14
|
+
app = TelemetryMiddleware(app)
|
|
15
|
+
|
|
16
|
+
ASGI type aliases::
|
|
17
|
+
|
|
18
|
+
from openframe.core.middleware import ASGIApp, ASGIScope, ASGIMessage, Receive, Send
|
|
19
|
+
|
|
20
|
+
def my_middleware(app: ASGIApp) -> ASGIApp:
|
|
21
|
+
async def wrapped(scope: ASGIScope, receive: Receive, send: Send) -> None:
|
|
22
|
+
...
|
|
23
|
+
return wrapped
|
|
24
|
+
|
|
25
|
+
Note on ``isinstance`` with type aliases:
|
|
26
|
+
``ASGIScope = MutableMapping[str, Any]`` is a subscripted generic alias.
|
|
27
|
+
``isinstance({}, ASGIScope)`` raises ``TypeError`` — use the origin type::
|
|
28
|
+
|
|
29
|
+
from collections.abc import MutableMapping
|
|
30
|
+
isinstance({}, MutableMapping) # ✓ works
|
|
31
|
+
|
|
32
|
+
To inspect the alias::
|
|
33
|
+
|
|
34
|
+
from typing import get_origin
|
|
35
|
+
get_origin(ASGIScope) is MutableMapping # ✓ True
|
|
36
|
+
"""
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
from openframe.core.middleware.telemetry import TelemetryMiddleware
|
|
40
|
+
from openframe.core.middleware.types import (
|
|
41
|
+
ASGIApp,
|
|
42
|
+
ASGIMessage,
|
|
43
|
+
ASGIScope,
|
|
44
|
+
Receive,
|
|
45
|
+
Send,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"TelemetryMiddleware",
|
|
50
|
+
"ASGIApp",
|
|
51
|
+
"ASGIScope",
|
|
52
|
+
"ASGIMessage",
|
|
53
|
+
"Receive",
|
|
54
|
+
"Send",
|
|
55
|
+
]
|