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 +68 -0
- {cuneus-0.2.5 → cuneus-0.2.6}/PKG-INFO +1 -1
- {cuneus-0.2.5 → cuneus-0.2.6}/pyproject.toml +1 -1
- {cuneus-0.2.5 → cuneus-0.2.6}/src/cuneus/cli.py +4 -0
- {cuneus-0.2.5 → cuneus-0.2.6}/src/cuneus/core/application.py +4 -0
- {cuneus-0.2.5 → cuneus-0.2.6}/src/cuneus/core/logging.py +61 -52
- {cuneus-0.2.5 → cuneus-0.2.6}/tests/test_integration.py +1 -1
- {cuneus-0.2.5 → cuneus-0.2.6}/uv.lock +1 -1
- {cuneus-0.2.5 → cuneus-0.2.6}/.gitignore +0 -0
- {cuneus-0.2.5 → cuneus-0.2.6}/.python-version +0 -0
- {cuneus-0.2.5 → cuneus-0.2.6}/README.md +0 -0
- {cuneus-0.2.5 → cuneus-0.2.6}/src/cuneus/__init__.py +0 -0
- {cuneus-0.2.5 → cuneus-0.2.6}/src/cuneus/core/__init__.py +0 -0
- {cuneus-0.2.5 → cuneus-0.2.6}/src/cuneus/core/execptions.py +0 -0
- {cuneus-0.2.5 → cuneus-0.2.6}/src/cuneus/core/extensions.py +0 -0
- {cuneus-0.2.5 → cuneus-0.2.6}/src/cuneus/core/settings.py +0 -0
- {cuneus-0.2.5 → cuneus-0.2.6}/src/cuneus/ext/__init__.py +0 -0
- {cuneus-0.2.5 → cuneus-0.2.6}/src/cuneus/ext/health.py +0 -0
- {cuneus-0.2.5 → cuneus-0.2.6}/src/cuneus/py.typed +0 -0
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
|
+
|
|
@@ -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
|
-
|
|
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=
|
|
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:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|