Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

wrapture

wrapture-instrumentation

Instrumentation for common Python packages, applied through wrapture.

wrapture attaches bindings to arbitrary Python call sites without modifying the code being observed, and its config layer can switch on packaged instrumentation for a third-party package by name. This project is the collection of that packaged instrumentation: one wrapture.Instrumentation class per target package, each registered under the bare target name, so that tracing a framework is one config entry and no code.

Status: beta, ahead of 1.0.0. Developed against wrapture's beta series, with pre-releases published to PyPI, and until 1.0.0 is final a plain pip install wrapture-instrumentation picks up the latest pre-release automatically, so there is no need to pin a specific version. The table below lists what is covered, from web frameworks and the servers that carry them to HTTP clients, databases and template engines.

Installation

$ pip install wrapture-instrumentation

Installing it brings wrapture and nothing else. No target package is a dependency: the instrumentation for a package you do not have is inert, and wrapture checks the installed version of each target against the range the instrumentation supports at apply time.

Using it

An [[instrument]] entry in wrapture.toml names a target:

[[instrument]]
name = "flask"

[[sink]]
type = "printer"

and the runner applies it before the application starts, so the patches are in place before the framework is imported:

$ python -m wrapture -m myapp

The same config works through autowrapt injection (AUTOWRAPT_BOOTSTRAP=wrapture python myapp.py); through manual setup, a few lines in the application's own startup applying the config where wrapping the launch from outside is awkward (an embedded interpreter, gunicorn and other multi-process managers); and, in a test, through wrapture.instrumentation("flask") scoping the instrumentation to a block. The ad-hoc tracing guide covers the config file itself.

To see what is installed, what each instrumentation supports in the current environment, and what aspects and settings it takes:

$ python -m wrapture.tools instrumentation --verbose

and to generate the [[instrument]] entries to paste into a config, every one disabled, every setting commented out at its default and every aspect as a commented-out sub-table:

$ python -m wrapture.tools instrumentation --toml

Provided instrumentation

Target Supported versions Records Aspects
aiohttp.client aiohttp 3.10+ (3.x) Every outbound request through a ClientSession (the verb helpers, session.request, a streamed async with) as one external leaf on _request carrying method, URL, host, port, path, query and status (an error status is a status, not an exception); a followed redirect one event resolved inside it; the trace identity propagated hop by hop; the query and any URL credentials masked, the body never recorded. requests (primary) with propagate
aiohttp.web aiohttp 3.10+ (3.x) Every request an aiohttp server handles as one server-categorised request boundary carrying method, path, scheme, peer and redacted query, annotated with the matched route pattern (a sub-application's prefix folded in, exported as http.route) and status (an HTTPException is its status, not a failure), joining the trace a traceparent header carries; every registered handler function observed as its route is built and recorded beneath the boundary, labelled by the route's name. requests (primary) with ignore_paths, join; handlers
django Django 4.2+ (below 7.0) Every request as one tree through the recording WSGI or ASGI middleware on the handler's own __call__, annotated with the matched route pattern and view name (the pattern exported as http.route); every view observed at URL resolution and labelled by its URL name, function, class-based and async views alike; ORM queries and transaction ends as database leaves at the cursor seam carrying system, operation and database, bound parameters never recorded and the SQL text only behind a setting; DTL renders as template events beneath their views, contexts never captured; unhandled failures noted on the request beside the 500 (an Http404 is its status, not a failure). requests (primary) with ignore_paths; views; queries with statement; templates; exceptions
fastapi fastapi 0.110+ (below 1.0) Every request as one tree through the recording ASGI middleware on the application's own __call__, annotated with the matched route pattern (router prefixes folded in) and name, the pattern exported as http.route; every endpoint function observed as its APIRoute is built, labelled by the route's name, dependency injection and response models undisturbed; validation failures record as their 422, unhandled failures on the request event beside the 500. requests (primary) with ignore_paths; views
flask Flask 3.x Every request as one tree, annotated with route and endpoint; every view observed and labelled by endpoint; template renders beneath their views; handled and unhandled failures noted on the request. requests (primary) with ignore_paths; views; lifecycle; handlers; templates; and handled_errors on the entry
grpc grpcio 1.76+ (1.x) Every RPC a channel makes, all four call shapes, as one external leaf through an injected client interceptor, carrying the rpc system, service and method, host, port and status code (an error code is a status, not an exception; a streamed response records the call, its consumption deliberately not tracked); every RPC a server handles as one server boundary through an injected server interceptor spanning the handler's run (a streaming handler's whole body), joining the trace the metadata carries; an abort is its code with the boundary clean, an escaped exception the failure it is; payloads and metadata never recorded. client with propagate; server with join
http.client Python 3.12+ (standard library) The wire phases of each exchange (connect where the socket really opens, the request line with its query masked, headers and body out by size, the response wait with its status) as plain events. A debugging aid: beneath an instrumented higher-level client nothing records until that client is switched to leaf = false. requests (primary)
httpx httpx 0.27+ (below 1.0) Every request through the module-level helpers, a Client or an AsyncClient, sync and async alike, as one external leaf on send carrying method, URL, host, port, path, query and status (an error status is a status, not an exception); a followed redirect one event resolved inside it; the trace identity propagated hop by hop; the query and any URL credentials masked, the body never recorded. requests (primary) with propagate
jinja2 Jinja2 3.x Every render traced in all its forms (sync, async, streamed), annotated with the template name; the loading and compile pipeline beneath it; context and output kept out of capture. renders (primary); loading
requests requests 2.31+ (2.x) Every request through the module-level helpers, Session.request or Session.send itself as one external leaf carrying method, URL, host, port, path, query and status (an error status is a status, not an exception); a redirect one leaf named by the URL asked for; the trace identity propagated hop by hop; the query and any URL credentials masked, the body never recorded. requests (primary) with propagate
sqlalchemy SQLAlchemy 1.4+ (below 3.0) Every statement an engine executes, Core and ORM, sync and async alike, as one database leaf at the dialect seam every driver sits behind, carrying the system, the SQL's leading keyword as the operation and the database; the connections the pool really opens and the transaction boundaries recorded the same way, the pool's own reset rollbacks excluded; bound parameters and credentials never recorded, the SQL text only behind a setting. statements (primary) with statement; connections
sqlite3 Python 3.12+ (standard library) Every query and transaction boundary as one database leaf, through recording proxies around the connections connect hands out, carrying the system and the SQL's leading keyword as the operation; bound parameters never recorded, the SQL text only behind a setting. statements (primary) with statement; connections
starlette starlette 0.47+ (below 2.0) Every request as one tree through the recording ASGI middleware on the application's own __call__, annotated with the matched route pattern and name (the pattern is what the OpenTelemetry export names the span by, as http.route); every endpoint function observed as its route is built, labelled by the route's name, sync and async alike; unhandled failures on the request event beside the 500. requests (primary) with ignore_paths; views
urllib.request Python 3.12+ (standard library) Every request through urllib.request as one external leaf carrying method, URL, host, port, path, query and status; the trace identity propagated in its headers; the query recorded with secrets masked, the body and response kept out of capture. requests (primary) with propagate
urllib3 urllib3 1.26+ (below 3.0) Every request through a pool manager, a connection pool or the module-level helper as one external leaf on urlopen (both doors sharing one depth count, so a redirect, a retry and the manager's delegation to a pool fold into it) carrying method, URL, host, port, path, query and status (an error status is a status, not an exception); the trace identity propagated in the headers; the query and any URL credentials masked, the body never recorded. Beneath the requests leaf it stays silent; standalone it records its own. requests (primary) with propagate
uvicorn uvicorn 0.30+ (below 1.0) Every application the server loads wrapped in the recording ASGI middleware at uvicorn's own seam, inside its proxy-headers middleware: one request tree per request, named by the application, with method, path, redacted query, status and streaming shape, joining the trace a traceparent header carries; an application's own recording middleware still records one boundary per request. requests (primary) with ignore_paths
werkzeug.serving werkzeug 3.x Every application handed to werkzeug's development server (Flask's app.run() included) wrapped in the recording WSGI middleware as the server is built: one request tree per request with method, path, redacted query and status, joining the trace a traceparent header carries; a framework's own recording middleware still records one boundary per request. requests (primary) with ignore_paths
wsgiref.simple_server Python 3.12+ (standard library) Every application the server is handed wrapped in the recording WSGI middleware at the server's own seam: one request tree per request with method, path, redacted query and status, joining the trace a traceparent header carries; an application already recording (a framework's own middleware) still records one boundary per request. requests (primary) with ignore_paths
xmlrpc.client Python 3.12+ (standard library) Every remote call through a ServerProxy as one external leaf carrying the RPC system and method name, URL, host, port, path and status (a Fault is a 200, a ProtocolError its code); the trace identity propagated in its headers; credentials, arguments, results and bodies kept out of capture. requests (primary) with propagate
xmlrpc.server Python 3.12+ (standard library) Every XML-RPC POST a SimpleXMLRPCServer handles as one server-categorised request boundary carrying method, path, client and status, joining the distributed trace an arriving traceparent header carries; each dispatched procedure beneath it with the method name as operation, multicall sub-calls nested; params and results reduced to counts and types. requests (primary) with join; methods

The entry point name is the config's name; the table summarizes each instrumentation, and the linked per-target README is its full user documentation: what records, what the events carry, the settings, and what is deliberately not traced. Settings, further choke points and wider version ranges are being added target by target.

Companion packages

This package deliberately covers only the standard library and third-party packages that can be exercised in-process, with no separate backend product or service needed to test against. Instrumentation for targets that do need one is provided as separate self-contained packages, one per product or service, each carrying its own test arrangements (a fake or a real backend to speak to) and its own release cadence. They install beside this package and are enabled the same way, by the bare target name in an [[instrument]] entry:

Package Targets Covers
wrapture-instrumentation-aws botocore The AWS SDK (boto3 and botocore): every AWS API call as one event named service/operation and categorised per service (DynamoDB a datastore, SQS, SNS and Kinesis messaging, Lambda and Step Functions tasks, S3 and the rest external), carrying service, operation, region, endpoint, the resource addressed and the response's status, request id and retry count; payloads never recorded.
wrapture-instrumentation-postgresql psycopg, psycopg2, asyncpg The PostgreSQL client libraries, one target per driver: every query as one database leaf however it was issued (the execute and fetch families, streamed queries, prepared statements, server-side cursors, COPY), the connection being opened, and each transaction boundary, sync and async alike; each event carrying the system, operation, and the database, host and port reached. Bound parameters are never recorded and the SQL text only with the statement setting on. Tested against a real PostgreSQL server in a container.
wrapture-instrumentation-mysql pymysql, MySQLdb, aiomysql The MySQL client libraries (PyMySQL, mysqlclient and aiomysql), one target per driver, named as the driver is imported and configured: every query as one database leaf however it was issued (execute, executemany, callproc, through every cursor class), the connection being opened, and each transaction boundary, sync and async alike; each event carrying the system, operation, and the database, host and port reached. Bound parameters are never recorded and the SQL text only with the statement setting on. Tested against a real MySQL server in a container.

Packages for the other caches and brokers that need a real server to test against (Redis and its driver packages) are coming.

Adding a target

Each target lives under src/wrapture_instrumentation/ in a role directory named for its category, as <category>/<target>: framework/flask, external/requests, database/sqlite3. The target's own name is its module path with dots as underscores (external/urllib_request for urllib.request, server/xmlrpc_server for xmlrpc.server), and the category says what kind of thing the target is and, with it, which part of wrapture the instrumentation mostly uses:

  • framework/: web frameworks, and their extensions as compound names (framework/flask_restful).

  • external/: outbound HTTP and RPC clients and service SDKs.

  • database/: DB-API drivers and SQL toolkits.

  • datastore/: other stores and caches.

  • task/: task queues. messaging/: brokers and their clients.

  • rpc/: RPC frameworks whose one package covers both the client and the server side.

  • server/: servers handling inbound requests, WSGI and ASGI servers included.

  • template/: template engines.

A new category is added when a target fits none of these. The role directories are the collection form of the layout: a package instrumenting a single target skips them and uses the flat <category>_<target> name (external_requests), the same words joined by an underscore instead of a directory. Either way the layout is internal; the entry point name, and so the name a config uses, is always the bare target.

The subpackage's __init__.py holds one wrapture.Instrumentation subclass, with one @wrapture.instrumentation_hook method per trigger module, and imports only wrapture; everything that touches the target lives in sibling submodules named for what they patch (app.py for flask.app), themselves importing only wrapture at top level. The class is registered in pyproject.toml under [project.entry-points."wrapture.instrumentation"], and gets its own test suite under tests/<category>/<target>/, mirroring the source layout. Each subpackage also carries a README.md, its user documentation, rendered by GitHub when browsing the directory and linked from the table above; the module docstrings stay the implementation commentary. The instrumentation packages page of the wrapture documentation is the full contract; TESTING.md here covers the tests.

License

BSD 2-Clause. See LICENSE.

Release files for wrapture-instrumentation 1.0.0b3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wrapture-instrumentation 1.0.0b3
File Size Uploaded
wrapture_instrumentation-1.0.0b3.tar.gz 235.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for wrapture-instrumentation 1.0.0b3
File Interpreter ABI Platform
wrapture_instrumentation-1.0.0b3-py3-none-any.whl Python 3 none any Details

Total release size: 404.9 kB

Release files / wrapture_instrumentation-1.0.0b3.tar.gz

Download URL wrapture_instrumentation-1.0.0b3.tar.gz
Size 235.0 kB
Tags Source
SHA-256 checksum
How to use checksums
86e1db40aea9ffb381c5dd9170fd2b9c60380fac28d7723a08b96149ba72cb60
BLAKE2b-256 checksum
How to use checksums
32bc511c7e83c21733e557afdb0051df540f002c25ed0beeecad8d1333e47d27
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.

Transparency log

Release files / wrapture_instrumentation-1.0.0b3-py3-none-any.whl

Download URL wrapture_instrumentation-1.0.0b3-py3-none-any.whl
Size 169.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
42abd5aff8fc0a7b0ebbe9baf1d9c347e9dc32d9a14c2643d4c86e6a26af4bef
BLAKE2b-256 checksum
How to use checksums
b63326a05568dba5e55702a9132bb1479b00fe028cb747eac70064fcc5e458ed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page