cuneus 0.2.5__py3-none-any.whl → 0.2.7__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.
cuneus/__init__.py CHANGED
@@ -16,19 +16,21 @@ Example:
16
16
  """
17
17
 
18
18
  from .core.application import build_app
19
- from .core.execptions import (
19
+ from .core.exceptions import (
20
20
  AppException,
21
21
  BadRequest,
22
- Unauthorized,
22
+ Conflict,
23
+ DatabaseError,
24
+ ErrorResponse,
25
+ ExceptionExtension,
26
+ ExternalServiceError,
23
27
  Forbidden,
24
28
  NotFound,
25
- Conflict,
26
29
  RateLimited,
27
- ServiceUnavailable,
28
- DatabaseError,
29
30
  RedisError,
30
- ExternalServiceError,
31
- ExceptionExtension,
31
+ ServiceUnavailable,
32
+ Unauthorized,
33
+ error_responses,
32
34
  )
33
35
  from .core.extensions import BaseExtension, Extension
34
36
  from .core.settings import Settings
@@ -46,14 +48,16 @@ __all__ = [
46
48
  # Exceptions
47
49
  "AppException",
48
50
  "BadRequest",
49
- "Unauthorized",
51
+ "Conflict",
52
+ "DatabaseError",
53
+ "ErrorResponse",
54
+ "ExceptionExtension",
55
+ "ExternalServiceError",
50
56
  "Forbidden",
51
57
  "NotFound",
52
- "Conflict",
53
58
  "RateLimited",
54
- "ServiceUnavailable",
55
- "DatabaseError",
56
59
  "RedisError",
57
- "ExternalServiceError",
58
- "ExceptionExtension",
60
+ "ServiceUnavailable",
61
+ "Unauthorized",
62
+ "error_responses",
59
63
  ]
cuneus/cli.py CHANGED
@@ -44,7 +44,7 @@ def get_user_cli() -> click.Group | None:
44
44
 
45
45
  @click.group()
46
46
  @click.pass_context
47
- def cli(ctx: click.Context) -> None:
47
+ def cli(ctx: click.Context) -> None: # pragma: no cover
48
48
  """Cuneus CLI - FastAPI application framework."""
49
49
  ctx.ensure_object(dict)
50
50
 
@@ -63,6 +63,8 @@ def dev(host: str, port: int) -> None:
63
63
  host=host,
64
64
  port=port,
65
65
  reload=True,
66
+ log_config=None,
67
+ server_header=False,
66
68
  )
67
69
 
68
70
 
@@ -81,6 +83,8 @@ def prod(host: str, port: int, workers: int) -> None:
81
83
  host=host,
82
84
  port=port,
83
85
  workers=workers,
86
+ log_config=None,
87
+ server_header=False,
84
88
  )
85
89
 
86
90
 
@@ -91,7 +95,7 @@ def routes() -> None:
91
95
  app = import_from_string(config.app_module)
92
96
 
93
97
  for route in app.routes:
94
- if hasattr(route, "methods"):
98
+ if hasattr(route, "methods"): # pragma: no branch
95
99
  methods = ",".join(route.methods - {"HEAD", "OPTIONS"})
96
100
  click.echo(f"{methods:8} {route.path}")
97
101
 
@@ -11,44 +11,43 @@ from contextlib import AsyncExitStack, asynccontextmanager
11
11
  from typing import Any, AsyncIterator, Callable
12
12
 
13
13
  import click
14
+ import structlog
14
15
  import svcs
15
16
  from fastapi import FastAPI
16
17
  from starlette.middleware import Middleware
17
18
 
18
19
  from .settings import Settings
19
- from .execptions import ExceptionExtension
20
+ from .exceptions import ExceptionExtension
20
21
  from .logging import LoggingExtension
21
- from .extensions import Extension, HasCLI, HasMiddleware
22
+ from .extensions import Extension, HasCLI, HasExceptionHandler, HasMiddleware
22
23
  from ..ext.health import HealthExtension
23
24
 
25
+ logger = structlog.stdlib.get_logger("cuneus")
24
26
 
25
27
  type ExtensionInput = Extension | Callable[..., Extension]
26
28
 
27
- DEFAULT_EXTENSIONS = (
29
+ DEFAULTS = (
28
30
  LoggingExtension,
29
31
  HealthExtension,
30
32
  ExceptionExtension,
31
33
  )
32
34
 
33
35
 
36
+ class ExtensionConflictError(Exception):
37
+ """Raised when extensions have conflicting state keys."""
38
+
39
+ pass
40
+
41
+
34
42
  def _instantiate_extension(
35
43
  ext: ExtensionInput, settings: Settings | None = None
36
44
  ) -> Extension:
37
45
  if isinstance(ext, type) or callable(ext):
38
- sig = inspect.signature(ext)
39
-
40
- # Check if it accepts a 'settings' parameter
41
- if "settings" in sig.parameters:
42
- return ext(settings=settings)
43
-
44
- # Check if it accepts **kwargs
45
- has_var_keyword = any(
46
- p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values()
47
- )
48
- if has_var_keyword:
46
+ try:
49
47
  return ext(settings=settings)
48
+ except TypeError:
49
+ return ext()
50
50
 
51
- return ext()
52
51
  return ext
53
52
 
54
53
 
@@ -90,13 +89,7 @@ def build_app(
90
89
 
91
90
  settings = settings or Settings()
92
91
 
93
- if include_defaults:
94
- # Allow users to override a default extension
95
- user_types = {type(ext) for ext in extensions}
96
- defaults = [ext for ext in DEFAULT_EXTENSIONS if type(ext) not in user_types]
97
- all_inputs = (*defaults, *extensions)
98
- else:
99
- all_inputs = extensions
92
+ all_inputs = (*DEFAULTS, *extensions) if include_defaults else extensions
100
93
 
101
94
  all_extensions = [_instantiate_extension(ext, settings) for ext in all_inputs]
102
95
 
@@ -115,10 +108,13 @@ def build_app(
115
108
  state: dict[str, Any] = {}
116
109
 
117
110
  for ext in all_extensions:
111
+ ext_name = ext.__class__.__name__
118
112
  ext_state = await stack.enter_async_context(ext.register(registry, app))
119
113
  if ext_state:
120
114
  if overlap := state.keys() & ext_state.keys():
121
- raise ValueError(f"Extension state key collision: {overlap}")
115
+ msg = f"Extension {ext_name} state key collision: {overlap}"
116
+ logger.error(msg, ext=ext_name, overlap=overlap)
117
+ raise ExtensionConflictError(msg).with_traceback(None) from None
122
118
  state.update(ext_state)
123
119
 
124
120
  yield state
@@ -127,10 +123,21 @@ def build_app(
127
123
  middleware: list[Middleware] = []
128
124
 
129
125
  for ext in all_extensions:
126
+ ext_name = ext.__class__.__name__
130
127
  if isinstance(ext, HasMiddleware):
128
+ logger.debug(f"Loading middleware from {ext_name}")
131
129
  middleware.extend(ext.middleware())
132
130
  if isinstance(ext, HasCLI):
131
+ logger.debug(f"Adding cli commands from {ext_name}")
133
132
  ext.register_cli(app_cli)
134
133
 
135
134
  app = FastAPI(lifespan=lifespan, middleware=middleware, **fastapi_kwargs)
135
+
136
+ # Preform post app initialization extension customization
137
+ for ext in all_extensions:
138
+ ext_name = ext.__class__.__name__
139
+ if isinstance(ext, HasExceptionHandler):
140
+ logger.debug(f"Loading exception handlers from {ext_name}")
141
+ ext.add_exception_handler(app)
142
+
136
143
  return app, app_cli
@@ -7,7 +7,6 @@ from __future__ import annotations
7
7
  from typing import Any
8
8
 
9
9
  import structlog
10
- import svcs
11
10
  from fastapi import FastAPI, Request
12
11
  from fastapi.responses import JSONResponse
13
12
  from pydantic import BaseModel
@@ -152,24 +151,14 @@ class ExceptionExtension(BaseExtension):
152
151
 
153
152
  Catches AppException subclasses and converts to JSON responses.
154
153
  Catches unexpected exceptions and returns generic 500s.
155
-
156
- Usage:
157
- from qtip import build_app
158
- from qtip.core.exceptions import ExceptionExtension, ExceptionSettings
159
-
160
- app = build_app(
161
- settings,
162
- extensions=[ExceptionExtension(settings)],
163
- )
164
154
  """
165
155
 
166
156
  def __init__(self, settings: Settings | None = None) -> None:
167
157
  self.settings = settings or Settings()
168
158
 
169
- async def startup(self, registry: svcs.Registry, app: FastAPI) -> dict[str, Any]:
159
+ def add_exception_handler(self, app: FastAPI) -> None:
170
160
  app.add_exception_handler(AppException, self._handle_app_exception) # type: ignore[arg-type]
171
161
  app.add_exception_handler(Exception, self._handle_unexpected_exception)
172
- return {}
173
162
 
174
163
  def _handle_app_exception(
175
164
  self, request: Request, exc: AppException
@@ -179,7 +168,7 @@ class ExceptionExtension(BaseExtension):
179
168
  else:
180
169
  log.warning("client_error", error_code=exc.error_code, message=exc.message)
181
170
 
182
- response = exc.to_response(request.state.get("request_id", None))
171
+ response = exc.to_response(getattr(request.state, "request_id", None))
183
172
 
184
173
  headers = {}
185
174
  if isinstance(exc, RateLimited) and exc.retry_after:
@@ -194,8 +183,7 @@ class ExceptionExtension(BaseExtension):
194
183
  def _handle_unexpected_exception(
195
184
  self, request: Request, exc: Exception
196
185
  ) -> JSONResponse:
197
- log.exception("unexpected_error")
198
-
186
+ log.exception("unexpected_error", exc_info=exc)
199
187
  response: dict[str, Any] = {
200
188
  "error": {
201
189
  "code": "internal_error",
@@ -203,7 +191,7 @@ class ExceptionExtension(BaseExtension):
203
191
  }
204
192
  }
205
193
 
206
- if hasattr(request.state, "request_id"):
194
+ if hasattr(request.state, "request_id"): # pragma: no branch
207
195
  response["error"]["request_id"] = request.state.request_id
208
196
 
209
197
  if self.settings.debug:
cuneus/core/extensions.py CHANGED
@@ -35,7 +35,7 @@ class Extension(Protocol):
35
35
 
36
36
  def register(
37
37
  self, registry: svcs.Registry, app: FastAPI
38
- ) -> AsyncContextManager[dict[str, Any]]:
38
+ ) -> AsyncContextManager[dict[str, Any]]: # pragma: no cover
39
39
  """
40
40
  Async context manager for lifecycle.
41
41
 
@@ -50,14 +50,21 @@ class Extension(Protocol):
50
50
  class HasMiddleware(Protocol):
51
51
  """Extension that provides middleware."""
52
52
 
53
- def middleware(self) -> list[Middleware]: ...
53
+ def middleware(self) -> list[Middleware]: ... # pragma: no cover
54
54
 
55
55
 
56
56
  @runtime_checkable
57
57
  class HasCLI(Protocol):
58
58
  """Extension that provides CLI commands."""
59
59
 
60
- def register_cli(self, cli_group: Group) -> None: ...
60
+ def register_cli(self, cli_group: Group) -> None: ... # pragma: no cover
61
+
62
+
63
+ @runtime_checkable
64
+ class HasExceptionHandler(Protocol):
65
+ """Extension that provides exception handlers."""
66
+
67
+ def add_exception_handler(self, app: FastAPI) -> None: ... # pragma: no cover
61
68
 
62
69
 
63
70
  class BaseExtension:
cuneus/core/logging.py CHANGED
@@ -4,22 +4,73 @@ Structured logging with structlog and request context.
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from contextvars import ContextVar
8
7
  import logging
9
8
  import time
10
9
  import uuid
11
- from typing import Any, Awaitable, Callable, MutableMapping
10
+ from typing import Any, Awaitable, Callable
12
11
 
13
12
  import structlog
14
13
  import svcs
15
14
  from fastapi import FastAPI, Request, Response
16
15
  from starlette.middleware.base import BaseHTTPMiddleware
17
16
  from starlette.middleware import Middleware
18
- from starlette.types import ASGIApp, Scope, Send, Receive
17
+ from starlette.types import ASGIApp
19
18
 
20
19
  from .extensions import BaseExtension
21
20
  from .settings import Settings
22
21
 
22
+ logger = structlog.stdlib.get_logger("cuneus")
23
+
24
+
25
+ def configure_structlog(settings: Settings | None = None) -> None:
26
+ log_settings = settings or Settings()
27
+
28
+ # Shared processors
29
+ shared_processors: list[structlog.types.Processor] = [
30
+ structlog.contextvars.merge_contextvars,
31
+ structlog.stdlib.add_log_level,
32
+ structlog.stdlib.add_logger_name,
33
+ structlog.stdlib.PositionalArgumentsFormatter(),
34
+ structlog.processors.TimeStamper(fmt="iso", utc=True),
35
+ structlog.processors.StackInfoRenderer(),
36
+ structlog.processors.UnicodeDecoder(),
37
+ ]
38
+
39
+ renderer: structlog.types.Processor = structlog.dev.ConsoleRenderer(colors=True)
40
+ if log_settings.log_json: # pragma: no cover
41
+ renderer = structlog.processors.JSONRenderer()
42
+
43
+ # Configure structlog
44
+ structlog.configure(
45
+ processors=shared_processors
46
+ + [
47
+ structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
48
+ ],
49
+ logger_factory=structlog.stdlib.LoggerFactory(),
50
+ cache_logger_on_first_use=True,
51
+ )
52
+
53
+ # Create formatter for stdlib
54
+ formatter = structlog.stdlib.ProcessorFormatter(
55
+ foreign_pre_chain=shared_processors,
56
+ processors=[
57
+ structlog.stdlib.ProcessorFormatter.remove_processors_meta,
58
+ renderer,
59
+ ],
60
+ )
61
+
62
+ # Configure root logger
63
+ handler = logging.StreamHandler()
64
+ handler.setFormatter(formatter)
65
+
66
+ root_logger = logging.getLogger()
67
+ root_logger.handlers.clear()
68
+ root_logger.addHandler(handler)
69
+ root_logger.setLevel(log_settings.log_level.upper())
70
+
71
+ # Quiet noisy loggers
72
+ logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
73
+
23
74
 
24
75
  class LoggingExtension(BaseExtension):
25
76
  """
@@ -27,69 +78,11 @@ class LoggingExtension(BaseExtension):
27
78
 
28
79
  Integrates with stdlib logging so uvicorn and other libraries
29
80
  also output through structlog.
30
-
31
- Usage:
32
- from qtip import build_app
33
- from qtip.middleware.logging import LoggingExtension, LoggingSettings
34
-
35
- app = build_app(
36
- settings,
37
- extensions=[LoggingExtension(settings)],
38
- )
39
81
  """
40
82
 
41
83
  def __init__(self, settings: Settings | None = None) -> None:
42
84
  self.settings = settings or Settings()
43
- self._configure_structlog()
44
-
45
- def _configure_structlog(self) -> None:
46
- settings = self.settings
47
-
48
- # Shared processors
49
- shared_processors: list[structlog.types.Processor] = [
50
- structlog.contextvars.merge_contextvars,
51
- structlog.stdlib.add_log_level,
52
- structlog.stdlib.add_logger_name,
53
- structlog.stdlib.PositionalArgumentsFormatter(),
54
- structlog.processors.TimeStamper(fmt="iso"),
55
- structlog.processors.StackInfoRenderer(),
56
- structlog.processors.UnicodeDecoder(),
57
- ]
58
-
59
- renderer: structlog.types.Processor = structlog.dev.ConsoleRenderer(colors=True)
60
- if settings.log_json:
61
- renderer = structlog.processors.JSONRenderer()
62
-
63
- # Configure structlog
64
- structlog.configure(
65
- processors=shared_processors
66
- + [
67
- structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
68
- ],
69
- logger_factory=structlog.stdlib.LoggerFactory(),
70
- cache_logger_on_first_use=True,
71
- )
72
-
73
- # Create formatter for stdlib
74
- formatter = structlog.stdlib.ProcessorFormatter(
75
- foreign_pre_chain=shared_processors,
76
- processors=[
77
- structlog.stdlib.ProcessorFormatter.remove_processors_meta,
78
- renderer,
79
- ],
80
- )
81
-
82
- # Configure root logger
83
- handler = logging.StreamHandler()
84
- handler.setFormatter(formatter)
85
-
86
- root_logger = logging.getLogger()
87
- root_logger.handlers.clear()
88
- root_logger.addHandler(handler)
89
- root_logger.setLevel(settings.log_level.upper())
90
-
91
- # Quiet noisy loggers
92
- logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
85
+ configure_structlog(settings)
93
86
 
94
87
  async def startup(self, registry: svcs.Registry, app: FastAPI) -> dict[str, Any]:
95
88
  # app.add_middleware(RequestLoggingMiddleware)
@@ -120,18 +113,24 @@ class LoggingMiddleware(BaseHTTPMiddleware):
120
113
  async def dispatch(
121
114
  self, request: Request, call_next: Callable[..., Awaitable[Response]]
122
115
  ) -> Response:
116
+ path = request.url.path
117
+ # Exclude health routes as these are just noise
118
+ # TODO(rmyers): make this configurable
119
+ if path.startswith("/health"):
120
+ return await call_next(request)
121
+
123
122
  request_id = request.headers.get(self.header_name) or str(uuid.uuid4())[:8]
124
123
 
125
124
  structlog.contextvars.clear_contextvars()
126
125
  structlog.contextvars.bind_contextvars(
127
126
  request_id=request_id,
128
127
  method=request.method,
129
- path=request.url.path,
128
+ path=path,
130
129
  )
131
130
 
132
131
  request.state.request_id = request_id
133
132
 
134
- log = structlog.get_logger()
133
+ log = structlog.stdlib.get_logger("cuneus")
135
134
  start_time = time.perf_counter()
136
135
 
137
136
  try:
@@ -151,71 +150,3 @@ class LoggingMiddleware(BaseHTTPMiddleware):
151
150
  raise
152
151
  finally:
153
152
  structlog.contextvars.clear_contextvars()
154
-
155
-
156
- # Used by httpx for request ID propagation
157
- request_id_ctx: ContextVar[str | None] = ContextVar("request_id", default=None)
158
-
159
-
160
- class RequestIDMiddleware:
161
- def __init__(self, app: ASGIApp, header_name: str = "X-Request-ID") -> None:
162
- self.app = app
163
- self.header_name = header_name
164
-
165
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
166
- if scope["type"] != "http":
167
- await self.app(scope, receive, send)
168
- return
169
-
170
- headers = dict(scope.get("headers", []))
171
- request_id = headers.get(
172
- self.header_name.lower().encode(), str(uuid.uuid4())[:8].encode()
173
- ).decode()
174
-
175
- if "state" not in scope:
176
- scope["state"] = {}
177
- scope["state"]["request_id"] = request_id
178
-
179
- # Set contextvar for use in HTTP clients
180
- token = request_id_ctx.set(request_id)
181
-
182
- async def send_with_request_id(message: MutableMapping[str, Any]) -> None:
183
- if message["type"] == "http.response.start":
184
- headers = list(message.get("headers", []))
185
- headers.append((self.header_name.encode(), request_id.encode()))
186
- message["headers"] = headers
187
- await send(message)
188
-
189
- try:
190
- await self.app(scope, receive, send_with_request_id)
191
- finally:
192
- request_id_ctx.reset(token)
193
-
194
-
195
- # === Public API ===
196
-
197
-
198
- def get_logger(**initial_context: Any) -> structlog.stdlib.BoundLogger:
199
- """
200
- Get a logger with optional initial context.
201
-
202
- Usage:
203
- log = get_logger()
204
- log.info("user logged in", user_id=123)
205
- """
206
- log = structlog.stdlib.get_logger()
207
- if initial_context:
208
- log = log.bind(**initial_context)
209
- return log
210
-
211
-
212
- def bind_contextvars(**context: Any) -> None:
213
- """
214
- Bind additional context that will appear in all subsequent logs.
215
- """
216
- structlog.contextvars.bind_contextvars(**context)
217
-
218
-
219
- def get_request_id(request: Request) -> str:
220
- """Get request ID from request state."""
221
- return getattr(request.state, "request_id", "-")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cuneus
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: ASGI application wrapper
5
5
  Project-URL: Homepage, https://github.com/rmyers/cuneus
6
6
  Project-URL: Documentation, https://github.com/rmyers/cuneus#readme
@@ -31,6 +31,7 @@ Requires-Dist: httpx>=0.27; extra == 'dev'
31
31
  Requires-Dist: mypy>=1.8; extra == 'dev'
32
32
  Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
33
33
  Requires-Dist: pytest-cov>=4.0; extra == 'dev'
34
+ Requires-Dist: pytest-mock; extra == 'dev'
34
35
  Requires-Dist: pytest>=8.0; extra == 'dev'
35
36
  Requires-Dist: redis>=5.0; extra == 'dev'
36
37
  Requires-Dist: ruff>=0.3; extra == 'dev'
@@ -0,0 +1,15 @@
1
+ cuneus/__init__.py,sha256=AJXN9dnXIWn0Eg6JxWOgOf0C386EqBNdHiQG2akTpKc,1295
2
+ cuneus/cli.py,sha256=rcJDqU28FMPSw7tkmKUz95ERKzgYv43Y2kKVZw24rLo,3864
3
+ cuneus/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ cuneus/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ cuneus/core/application.py,sha256=Xoqs3fREJ-KPzSilq4ua65JzKv6w5LgugGaMklDELdU,4376
6
+ cuneus/core/exceptions.py,sha256=gyHgVy4UC7gBjs5pXpCzIIyIVsV7K8xt-KZKi8eLJtM,5496
7
+ cuneus/core/extensions.py,sha256=wqN2rbaqhiTkfK_fA5AkFlefOAV2GJr6zTLf9Oi-n4c,2771
8
+ cuneus/core/logging.py,sha256=YXF_-QzcNqfzIl6H4niiW3yR5IBzh2VJxUosfBvb0SM,4583
9
+ cuneus/core/settings.py,sha256=PaYXQ_ubeSt3AFpxNNErii-h1_ehHYPrajFWRT42mTI,1703
10
+ cuneus/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ cuneus/ext/health.py,sha256=5dWVVEPFL1tWFBhQwZv8C-IvZRzg28V-4sk_g1jJ0vc,3854
12
+ cuneus-0.2.7.dist-info/METADATA,sha256=6xs5SVnhEMudG9taaPSruvbee6aPiaOixc9XndBD9lc,6837
13
+ cuneus-0.2.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
14
+ cuneus-0.2.7.dist-info/entry_points.txt,sha256=tzPgom-_UkpP_uLKv3V_XyoIsKg84FBAc9ddjYl0W0Y,43
15
+ cuneus-0.2.7.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- cuneus/__init__.py,sha256=JJ3nZ4757GU9KKuurxP1FfJSdSVrcO-xaorLFSvUJ5E,1211
2
- cuneus/cli.py,sha256=NHk_hI_KtXPQ0EXCCfottDwOFrSG6vcYSZPrzuzyZk8,3715
3
- cuneus/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- cuneus/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- cuneus/core/application.py,sha256=6yKbrkkSaWlt3UHiKo68Dc9B1EAcNXd6BxO5W8ZTZuc,4058
6
- cuneus/core/execptions.py,sha256=beQE3gD-14BUK4Se6yE2J2U92xgr0yarVmVeibkudxs,5753
7
- cuneus/core/extensions.py,sha256=qqBnAD_wN6wTTun7C2hfVqxHhA3WgScNSrgRTMsYa04,2515
8
- cuneus/core/logging.py,sha256=OlcWxBCLDqBORzTXZXKlMc_rGD8OkfOBfHgSwEpMCM4,6778
9
- cuneus/core/settings.py,sha256=PaYXQ_ubeSt3AFpxNNErii-h1_ehHYPrajFWRT42mTI,1703
10
- cuneus/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- cuneus/ext/health.py,sha256=5dWVVEPFL1tWFBhQwZv8C-IvZRzg28V-4sk_g1jJ0vc,3854
12
- cuneus-0.2.5.dist-info/METADATA,sha256=hIVlKfIT3uwqzSeoX8nXeEN6oCnfKcb5HaT-y3YETBQ,6794
13
- cuneus-0.2.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
14
- cuneus-0.2.5.dist-info/entry_points.txt,sha256=tzPgom-_UkpP_uLKv3V_XyoIsKg84FBAc9ddjYl0W0Y,43
15
- cuneus-0.2.5.dist-info/RECORD,,
File without changes