cuneus 0.2.5__tar.gz → 0.2.6__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.
cuneus-0.2.6/Makefile ADDED
@@ -0,0 +1,68 @@
1
+ BASE_DIR := $(shell git rev-parse --show-toplevel 2>/dev/null || pwd)
2
+ PROG_NAME := $(shell basename $(BASE_DIR))
3
+ PYPROJECT := $(shell find . -name 'pyproject.toml')
4
+ SHELL := /bin/bash
5
+ UV_LOCK := uv.lock
6
+ UV ?= uv
7
+ VENV := .venv
8
+ MARKER := $(VENV)/.installed
9
+
10
+ ################################################################
11
+ #%
12
+ #% Usage:
13
+ #% make <command>
14
+ #%
15
+ #% Getting Started:
16
+ #% make setup
17
+ #%
18
+ #% Run the tests locally:
19
+ #% make test
20
+ #%
21
+ #% Available Commands:
22
+ help: ## Help is on the way
23
+ @echo " Tools for building, running, and testing $(PROG_NAME)"
24
+ @grep '^#%' $(MAKEFILE_LIST) | sed -e 's/#%//'
25
+ @grep '^[a-zA-Z]' $(MAKEFILE_LIST) | awk -F ':.*?## ' 'NF==2 {printf " %-20s%s\n", $$1, $$2}' | sort
26
+
27
+ # export env vars in order to be used in commands
28
+ export PYTHONPATH ?= ./
29
+
30
+ # uv creates the venv automatically, but this tracks if sync has been run
31
+ $(MARKER): $(PYPROJECT) $(UV_LOCK)
32
+ $(UV) sync --all-extras
33
+ touch $(MARKER)
34
+
35
+ .PHONY: install
36
+ setup: $(MARKER) ## Install dependencies for the project
37
+
38
+ .PHONY: update
39
+ update: ## Update and lock dependencies
40
+ $(UV) lock
41
+ $(UV) sync
42
+ touch $(MARKER)
43
+
44
+ .PHONY: clean
45
+ clean: ## Remove virtual environment
46
+ rm -rf $(VENV)
47
+
48
+ .PHONY: deps
49
+ deps: $(MARKER) ## Run any dependencies for local dev and test
50
+ @echo "Starting deps..."
51
+
52
+ .PHONY: dev
53
+ dev: deps ## Run the application in dev mode
54
+ $(UV) run cuneus dev
55
+
56
+ .PHONY: prod
57
+ prod: deps ## Run the application in prod mode
58
+ $(UV) run cuneus prod
59
+
60
+ .PHONY: test
61
+ test: deps ## Run pytests
62
+ $(UV) run pytest
63
+
64
+ .PHONY: test-failed
65
+ test-failed: deps ## Re-Run pytest on tests that failed
66
+ $(UV) run pytest --lf
67
+
68
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cuneus
3
- Version: 0.2.5
3
+ Version: 0.2.6
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cuneus"
3
- version = "0.2.5"
3
+ version = "0.2.6"
4
4
  description = "ASGI application wrapper"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Robert Myers", email = "robert@julython.org" }]
@@ -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
 
@@ -11,6 +11,7 @@ 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
@@ -21,6 +22,7 @@ from .logging import LoggingExtension
21
22
  from .extensions import Extension, HasCLI, 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
 
@@ -128,8 +130,10 @@ def build_app(
128
130
 
129
131
  for ext in all_extensions:
130
132
  if isinstance(ext, HasMiddleware):
133
+ logger.debug(f"Loading middleware from {ext.__class__.__name__}")
131
134
  middleware.extend(ext.middleware())
132
135
  if isinstance(ext, HasCLI):
136
+ logger.debug(f"Adding cli commands from {ext.__class__.__name__}")
133
137
  ext.register_cli(app_cli)
134
138
 
135
139
  app = FastAPI(lifespan=lifespan, middleware=middleware, **fastapi_kwargs)
@@ -20,6 +20,58 @@ from starlette.types import ASGIApp, Scope, Send, Receive
20
20
  from .extensions import BaseExtension
21
21
  from .settings import Settings
22
22
 
23
+ logger = structlog.stdlib.get_logger("cuneus")
24
+
25
+
26
+ def configure_structlog(settings: Settings | None = None) -> None:
27
+ log_settings = settings or Settings()
28
+
29
+ # Shared processors
30
+ shared_processors: list[structlog.types.Processor] = [
31
+ structlog.contextvars.merge_contextvars,
32
+ structlog.stdlib.add_log_level,
33
+ structlog.stdlib.add_logger_name,
34
+ structlog.stdlib.PositionalArgumentsFormatter(),
35
+ structlog.processors.TimeStamper(fmt="iso", utc=True),
36
+ structlog.processors.StackInfoRenderer(),
37
+ structlog.processors.UnicodeDecoder(),
38
+ ]
39
+
40
+ renderer: structlog.types.Processor = structlog.dev.ConsoleRenderer(colors=True)
41
+ if log_settings.log_json:
42
+ renderer = structlog.processors.JSONRenderer()
43
+
44
+ # Configure structlog
45
+ structlog.configure(
46
+ processors=shared_processors
47
+ + [
48
+ structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
49
+ ],
50
+ logger_factory=structlog.stdlib.LoggerFactory(),
51
+ cache_logger_on_first_use=True,
52
+ )
53
+
54
+ # Create formatter for stdlib
55
+ formatter = structlog.stdlib.ProcessorFormatter(
56
+ foreign_pre_chain=shared_processors,
57
+ processors=[
58
+ structlog.stdlib.ProcessorFormatter.remove_processors_meta,
59
+ renderer,
60
+ ],
61
+ )
62
+
63
+ # Configure root logger
64
+ handler = logging.StreamHandler()
65
+ handler.setFormatter(formatter)
66
+
67
+ root_logger = logging.getLogger()
68
+ root_logger.handlers.clear()
69
+ root_logger.addHandler(handler)
70
+ root_logger.setLevel(log_settings.log_level.upper())
71
+
72
+ # Quiet noisy loggers
73
+ logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
74
+
23
75
 
24
76
  class LoggingExtension(BaseExtension):
25
77
  """
@@ -40,56 +92,7 @@ class LoggingExtension(BaseExtension):
40
92
 
41
93
  def __init__(self, settings: Settings | None = None) -> None:
42
94
  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)
95
+ configure_structlog(settings)
93
96
 
94
97
  async def startup(self, registry: svcs.Registry, app: FastAPI) -> dict[str, Any]:
95
98
  # app.add_middleware(RequestLoggingMiddleware)
@@ -120,18 +123,24 @@ class LoggingMiddleware(BaseHTTPMiddleware):
120
123
  async def dispatch(
121
124
  self, request: Request, call_next: Callable[..., Awaitable[Response]]
122
125
  ) -> Response:
126
+ path = request.url.path
127
+ # Exclude health routes as these are just noise
128
+ # TODO(rmyers): make this configurable
129
+ if path.startswith("/health"):
130
+ return await call_next(request)
131
+
123
132
  request_id = request.headers.get(self.header_name) or str(uuid.uuid4())[:8]
124
133
 
125
134
  structlog.contextvars.clear_contextvars()
126
135
  structlog.contextvars.bind_contextvars(
127
136
  request_id=request_id,
128
137
  method=request.method,
129
- path=request.url.path,
138
+ path=path,
130
139
  )
131
140
 
132
141
  request.state.request_id = request_id
133
142
 
134
- log = structlog.get_logger()
143
+ log = structlog.stdlib.get_logger("cuneus")
135
144
  start_time = time.perf_counter()
136
145
 
137
146
  try:
@@ -9,6 +9,6 @@ async def test_cuneus():
9
9
 
10
10
  with TestClient(app) as client:
11
11
  resp = client.get("/healthz")
12
- assert resp.status_code == 200
12
+ assert resp.status_code == 201
13
13
 
14
14
  assert resp.json()["status"] == health.HealthStatus.HEALTHY
@@ -249,7 +249,7 @@ toml = [
249
249
 
250
250
  [[package]]
251
251
  name = "cuneus"
252
- version = "0.2.4"
252
+ version = "0.2.5"
253
253
  source = { editable = "." }
254
254
  dependencies = [
255
255
  { name = "click" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes