Common utilities for Camptocamp ASGI applications
Project description
Camptocamp ASGI Utils
This package provides a set of utilities to help you build ASGI applications with Python.
Stack
Stack that we consider that the project uses:
Environment variables
See: https://github.com/camptocamp/c2casgiutils/blob/master/c2casgiutils/config.py
C2C__REDIS__URL
Optional, default value: None
Redis connection URL
C2C__REDIS__OPTIONS
Optional, default value: None
Redis connection options, e.g. 'socket_timeout=5,ssl=True'.
C2C__REDIS__SENTINELS
Optional, default value: None
Redis Sentinels
C2C__REDIS__SERVICENAME
Optional, default value: None
Redis service name for Sentinel
C2C__REDIS__DB
Optional, default value: 0
Redis database number
C2C__PROMETHEUS__PREFIX
Optional, default value: c2casgiutils_
Prefix for Prometheus metrics
C2C__PROMETHEUS__PORT
Optional, default value: 9000
Port for Prometheus metrics
C2C__SENTRY__DSN
Optional, default value: None
Sentry DSN
C2C__SENTRY__DEBUG
Optional, default value: False
Enable Sentry debug mode
C2C__SENTRY__RELEASE
Optional, default value: None
Sentry release version
C2C__SENTRY__ENVIRONMENT
Optional, default value: production
Sentry environment
C2C__SENTRY__DIST
Optional, default value: None
Sentry distribution
C2C__SENTRY__SAMPLE_RATE
Optional, default value: 1.0
Sample rate for error events
C2C__SENTRY__IGNORE_ERRORS
Optional, default value: []
List of exception class names to ignore
C2C__SENTRY__MAX_BREADCRUMBS
Optional, default value: 100
Maximum number of breadcrumbs to capture
C2C__SENTRY__ATTACH_STACKTRACE
Optional, default value: False
Attach stack trace to all messages
C2C__SENTRY__SEND_DEFAULT_PII
Optional, default value: None
Send default PII
C2C__SENTRY__EVENT_SCRUBBER
Optional, default value: None
Event scrubber for sensitive information
C2C__SENTRY__INCLUDE_SOURCE_CONTEXT
Optional, default value: True
Include source context in events
C2C__SENTRY__INCLUDE_LOCAL_VARIABLES
Optional, default value: True
Include local variables in events
C2C__SENTRY__ADD_FULL_STACK
Optional, default value: False
Add full stack trace to events
C2C__SENTRY__MAX_STACK_FRAMES
Optional, default value: 100
Maximum number of stack frames to capture
C2C__SENTRY__SERVER_NAME
Optional, default value: None
Server name for Sentry events
C2C__SENTRY__PROJECT_ROOT
Optional, default value: <working_directory>
Root directory of the project
C2C__SENTRY__IN_APP_INCLUDE
Optional, default value: []
List of module prefixes that are in the app
C2C__SENTRY__IN_APP_EXCLUDE
Optional, default value: []
List of module prefixes that are not in the app
C2C__SENTRY__MAX_REQUEST_BODY_SIZE
Optional, default value: medium
Maximum request body size to capture
C2C__SENTRY__MAX_VALUE_LENGTH
Optional, default value: 1024
Maximum length of values in event payloads
C2C__SENTRY__CA_CERTS
Optional, default value: None
Path to alternative CA bundle file in PEM format
C2C__SENTRY__SEND_CLIENT_REPORTS
Optional, default value: True
Send client reports to Sentry
C2C__AUTH__COOKIE_AGE
Optional, default value: 604800
Authentication cookie age in seconds (default: 7 days)
C2C__AUTH__COOKIE
Optional, default value: c2c-auth
Authentication cookie name
C2C__AUTH__JWT__SECRET
Optional, default value: None
JWT secret key
C2C__AUTH__JWT__ALGORITHM
Optional, default value: HS256
JWT algorithm (default: HS256)
C2C__AUTH__JWT__COOKIE__SAME_SITE
Optional, default value: strict
SameSite attribute for JWT cookie (default: strict)
Possible values
lax, strict, none
C2C__AUTH__JWT__COOKIE__SECURE
Optional, default value: True
Whether the JWT cookie should be secure (default: True)
C2C__AUTH__JWT__COOKIE__PATH
Optional, default value: None
Path for the JWT cookie (default: the path to the index page)
C2C__AUTH__SECRET
Optional, default value: None
Secret key for trivial authentication (not secure)
C2C__AUTH__GITHUB__REPOSITORY
Optional, default value: None
GitHub repository for authentication
C2C__AUTH__GITHUB__ACCESS_TYPE
Optional, default value: pull
GitHub access type
C2C__AUTH__GITHUB__AUTHORIZE_URL
Optional, default value: https://github.com/login/oauth/authorize
GitHub OAuth authorization URL
C2C__AUTH__GITHUB__TOKEN_URL
Optional, default value: https://github.com/login/oauth/access_token
GitHub OAuth token URL
C2C__AUTH__GITHUB__USER_URL
Optional, default value: https://api.github.com/user
GitHub user API URL
C2C__AUTH__GITHUB__REPO_URL
Optional, default value: https://api.github.com/repos
GitHub repository API URL
C2C__AUTH__GITHUB__CLIENT_ID
Optional, default value: None
GitHub OAuth client ID
C2C__AUTH__GITHUB__CLIENT_SECRET
Optional, default value: None
GitHub OAuth client secret
C2C__AUTH__GITHUB__SCOPE
Optional, default value: repo
GitHub OAuth scope
C2C__AUTH__GITHUB__PROXY_URL
Optional, default value: None
GitHub proxy URL
C2C__AUTH__GITHUB__STATE_COOKIE
Optional, default value: c2c-state
GitHub state cookie name
C2C__AUTH__GITHUB__STATE_COOKIE_AGE
Optional, default value: 600
GitHub state cookie age in seconds (default: 10 minutes)
C2C__AUTH__TEST__USERNAME
Optional, default value: None
Test username
C2C__TOOLS__LOGGING__REDIS_PREFIX
Optional, default value: c2c_logging_level_
Redis prefix for logging settings
C2C__TOOLS__LOGGING__APPLICATION_MODULE
Optional, default value: c2casgiutils
Application module name for logging
Installation
pip install c2casgiutils[all]
Add in your application:
import c2casgiutils
from c2casgiutils import broadcast
from c2casgiutils import config
from prometheus_client import start_http_server
from prometheus_fastapi_instrumentator import Instrumentator
from contextlib import asynccontextmanager
@asynccontextmanager
async def _lifespan(main_app: FastAPI) -> None:
"""Handle application lifespan events."""
_LOGGER.info("Starting the application")
await c2casgiutils.startup(main_app)
yield
app = FastAPI(title="My fastapi_app application", lifespan=_lifespan)
app.mount('/c2c', c2casgiutils.app)
# For security headers (and compression)
# Add TrustedHostMiddleware (should be first)
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=["*"], # Configure with specific hosts in production
)
# Add HTTPSRedirectMiddleware
if os.environ.get("HTTP", "False").lower() not in ["true", "1"]:
app.add_middleware(HTTPSRedirectMiddleware)
# Add GZipMiddleware
app.add_middleware(GZipMiddleware, minimum_size=1000)
# Set all CORS origins enabled
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.add_middleware(
headers.ArmorHeaderMiddleware,
headers_config={
"http": {"headers": {"Strict-Transport-Security": None} if http else {}},
}
)
# Get Prometheus HTTP server port from environment variable 9000 by default
start_http_server(config.settings.prometheus.port)
instrumentator = Instrumentator(should_instrument_requests_inprogress=True)
instrumentator.instrument(app)
Broadcasting
To use the broadcasting you should do something like this:
import c2casgiutils
class BroadcastResponse(BaseModel):
"""Response from broadcast endpoint."""
result: list[dict[str, Any]] | None = None
echo_handler: Callable[[], Awaitable[list[BroadcastResponse] | None]] = None # type: ignore[assignment]
# Create a handler that will receive broadcasts
async def echo_handler_() -> dict[str, Any]:
"""Echo handler for broadcast messages."""
return {"message": "Broadcast echo"}
# Subscribe the handler to a channel on module import
@asynccontextmanager
async def _lifespan(main_app: FastAPI) -> None:
"""Handle application lifespan events."""
_LOGGER.info("Starting the application")
await c2casgiutils.startup(main_app)
# Register the echo handler
global echo_handler # pylint: disable=global-statement
echo_handler = await broadcast.decorate(echo_handler_, expect_answers=True)
yield
Then you can use the echo_handler function you will have the response of all the registered applications.
Health checks
The health_checks module provides a flexible system for checking the health of various components of your application. Health checks are exposed through a REST API endpoint at /c2c/health_checks and are also integrated with Prometheus metrics.
Basic Usage
To initialize health checks in your application:
from c2casgiutils import health_checks
# Add Redis health check
health_checks.FACTORY.add(health_checks.Redis(tags=["liveness", "redis", "all"]))
# Add SQLAlchemy database connection check
from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession
health_checks.FACTORY.add(health_checks.SQLAlchemy(Session=your_async_sessionmaker, tags=["database", "all"]))
# Add Alembic migration version check
health_checks.FACTORY.add(health_checks.Alembic(
Session=your_async_sessionmaker,
config_file="alembic.ini",
tags=["migrations", "database", "all"]
))
Available Health Checks
The package provides several built-in health check implementations:
- Redis: Checks connectivity to Redis by pinging both master and slave instances
- SQLAlchemy: Verifies database connectivity by executing a simple query
- Alembic: Ensures the database schema is up-to-date with the latest migrations
Custom Health Checks
You can create custom health checks by extending the Check base class:
from c2casgiutils.health_checks import Check, Result
class MyCustomCheck(Check):
async def check(self) -> Result:
# Your check logic here
try:
# Perform your check...
return Result(status_code=200, payload={"message": "Everything is fine!"})
except Exception as e:
return Result(status_code=500, payload={"error": str(e)})
# Add your custom check
health_checks.FACTORY.add(MyCustomCheck(tags=["custom", "all"]))
Filtering Health Checks
Health checks can be filtered using tags or names:
- Tags: Add relevant tags when creating a check to categorize it
- API Filtering: Use query parameters to filter checks when calling the API:
/c2c/health_checks?tags=database,critical- Run only checks with "database" or "critical" tags/c2c/health_checks?name=Redis- Run only the Redis check
Prometheus Integration
Health check results are automatically exported to Prometheus metrics via the health_checks_failure gauge, allowing you to monitor and alert on health check failures.
Middleware
Headers Middleware
The ArmorHeaderMiddleware provides automatic security headers configuration for your ASGI application. It allows you to configure headers based on request netloc (host:port) and path patterns.
Basic Usage
from c2casgiutils.headers import ArmorHeaderMiddleware
# Use default security headers
app.add_middleware(ArmorHeaderMiddleware)
# Or with custom configuration
app.add_middleware(ArmorHeaderMiddleware, headers_config=your_custom_config)
Default Security Headers
The middleware comes with sensible security defaults including:
- Content-Security-Policy: Restricts resource loading to prevent XSS attacks
- X-Frame-Options: Prevents clickjacking by denying iframe embedding
- Strict-Transport-Security: Forces HTTPS connections (disabled for localhost)
- X-Content-Type-Options: Prevents MIME-type sniffing
- Referrer-Policy: Controls referrer information sent with requests
- Permissions-Policy: Restricts access to browser features like geolocation
- X-DNS-Prefetch-Control: Disables DNS prefetching
- Expect-CT: Certificate Transparency enforcement
- Origin-Agent-Cluster: Isolates origin agent clusters
- Cross-Origin policies: CORP, COOP, COEP for cross-origin protection
Custom Configuration
You can configure headers based on request patterns:
from c2casgiutils.headers import ArmorHeaderMiddleware
custom_config = {
"api_endpoints": {
"path_match": r"^/api/.*", # Regex pattern for paths
"headers": {
"Access-Control-Allow-Origin": "*",
"X-Custom-Header": "api-value"
},
"order": 1 # Processing order
},
"admin_section": {
"netloc_match": r"^admin\..*", # Regex for host matching
"path_match": r"^/admin/.*",
"headers": {
"X-Robots-Tag": "noindex, nofollow"
},
"status_code": 200, # Only apply for specific status code
"order": 2
},
"success_responses": {
"headers": {
"Cache-Control": ["public", "max-age=3600"]
},
"status_code": (200, 299), # Apply for a range of status codes (200-299)
"order": 3
},
"api_methods": {
"path_match": r"^/api/.*",
"methods": ["GET", "HEAD"], # Only apply for specific HTTP methods
"headers": {
"Cache-Control": ["public", "max-age=3600"]
},
"order": 4
},
"remove_header": {
"headers": {
"Server": None # Remove header by setting to None
}
}
}
app.add_middleware(ArmorHeaderMiddleware, headers_config=custom_config)
Header Value Types
Headers support multiple value types:
headers = {
# String value
"X-Custom": "value",
# List (joined with "; ")
"Cache-Control": ["no-cache", "no-store", "must-revalidate"],
# Dictionary (for complex headers like CSP)
"Content-Security-Policy": {
"default-src": ["'self'"],
"script-src": ["'self'", "https://cdn.example.com"],
"style-src": ["'self'", "'unsafe-inline'"]
},
# List (joined with ", ") for Permissions-Policy
"Permissions-Policy": ["geolocation=()", "microphone=()"],
# Remove header
"Unwanted-Header": None
}
Special Localhost Handling
The middleware automatically disables Strict-Transport-Security for localhost to facilitate development.
Status Code Configuration
You can apply headers conditionally based on response status codes:
- Apply to a single status code:
"status_code": 200 - Apply to a range of status codes:
"status_code": (200, 299)(for all 2xx success responses)
This feature is useful for adding caching headers only to successful responses, or special headers for specific error codes.
HTTP Method Filtering
You can configure headers to be applied only for specific HTTP methods:
{
"api_post_endpoints": {
"path_match": r"^/api/.*",
"methods": ["POST", "PUT", "PATCH"], # Only apply for these methods
"headers": {
"Cache-Control": "no-store"
}
},
"api_get_endpoints": {
"path_match": r"^/api/.*",
"methods": ["GET", "HEAD"], # Only apply for GET and HEAD requests
"headers": {
"Cache-Control": ["public", "max-age=3600"]
}
}
}
This allows for fine-grained control over which headers are applied based on the request method, useful for implementing different caching strategies for read vs. write operations.
Content-Security-Policy and security considerations
With the default CSP your html application will not work, to make it working without impacting the security Of the other pages you should add in the headers_config something like this:
{
"my_page": {
"path_match": r"^your-path/?",
"headers": {
"Content-Security-Policy": {
"default-src": ["'self'"],
"script-src-elem": ["'self'", ...],
"style-src-elem": ["'self'", ...],
}
},
"order": 1
}
}
And do the same for other headers.
Cache-Control Header
The Cache-Control header can be configured to control caching behavior for different endpoints. You can specify it as a string, list, or dictionary:
{
"api_endpoints": {
"path_match": r"^/api/.*",
"headers": {
"Cache-Control": ["public", "max-age=3600"] # Cache for 1 hour
},
"order": 1
}
}
By default the middleware will not set any Cache-Control header, so you should explicitly configure it to enable caching.
Authentication
The package also provides authentication utilities for GitHub-based authentication and API key validation. See the auth.py module for detailed configuration options.
Prometheus Metrics
To enable Prometheus metrics in your FastAPI application, you can use the prometheus_fastapi_instrumentator package. Here's how to set it up:
from c2casgiutils import config
from prometheus_client import start_http_server
from prometheus_fastapi_instrumentator import Instrumentator
# Get Prometheus HTTP server port from environment variable 9000 by default
start_http_server(config.settings.prometheus.port)
instrumentator = Instrumentator(should_instrument_requests_inprogress=True)
instrumentator.instrument(app)
Sentry Integration
To enable error tracking with Sentry in your application:
import os
import sentry_sdk
# Initialize Sentry if the URL is provided
if config.settings.sentry.dsn or 'SENTRY_DSN' in os.environ:
_LOGGER.info("Sentry is enabled with URL: %s", config.settings.sentry.url or os.environ.get("SENTRY_DSN"))
sentry_sdk.init(**config.settings.sentry.model_dump())
Sentry will automatically capture exceptions and errors in your FastAPI application. For more advanced usage, refer to the Sentry Python SDK documentation and FastAPI integration guide.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file c2casgiutils-0.6.0.tar.gz.
File metadata
- Download URL: c2casgiutils-0.6.0.tar.gz
- Upload date:
- Size: 37.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1089530c74e1a066af184204462845454a319a2db0c28e4a4dffc868e7019fd4
|
|
| MD5 |
25479302200b7d8c12c374980435c49c
|
|
| BLAKE2b-256 |
ba19e04e38224bb6f053484241d00affb739171bcc28b6e97c6ede789520c406
|
File details
Details for the file c2casgiutils-0.6.0-py3-none-any.whl.
File metadata
- Download URL: c2casgiutils-0.6.0-py3-none-any.whl
- Upload date:
- Size: 38.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0122d3f1c7350064e470b897aea3c949ad0bcd6c0a16bdd966b4370bf0141835
|
|
| MD5 |
d611e9db482860577c26c1a8d4999659
|
|
| BLAKE2b-256 |
e8f9562ed8c59c0cad924c06520c61e8fe1cbbc4565b86b42e3156b94b913599
|