Middleware for Starlette that allows you to store and access the context data of a request. Can be used with logging so logs automatically use request headers such as x-request-id or x-correlation-id.

Overview

Build Status Python PyPI version codecov Docs Downloads Language grade: Python

starlette context

Middleware for Starlette that allows you to store and access the context data of a request. Can be used with logging so logs automatically use request headers such as x-request-id or x-correlation-id.

Resources:

Installation

$ pip install starlette-context

Requirements

Python 3.7+

Dependencies

  • starlette

All other dependencies from requirements-dev.txt are only needed to run tests or examples. Test/dev env is dockerized if you want to try them yourself.

Example

import uvicorn

from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.requests import Request
from starlette.responses import JSONResponse

from starlette_context import context, plugins
from starlette_context.middleware import RawContextMiddleware

middleware = [
    Middleware(
        RawContextMiddleware,
        plugins=(
            plugins.RequestIdPlugin(),
            plugins.CorrelationIdPlugin()
        )
    )
]

app = Starlette(middleware=middleware)


@app.route("/")
async def index(request: Request):
    return JSONResponse(context.data)


uvicorn.run(app, host="0.0.0.0")

In this example the response contains a json with

{
  "X-Correlation-ID":"5ca2f0b43115461bad07ccae5976a990",
  "X-Request-ID":"21f8d52208ec44948d152dc49a713fdd"
}

Context can be updated and accessed at anytime if it's created in the middleware.

Contribution

See the guide on read the docs.

Comments
  • Allow custom validation response

    Allow custom validation response

    This PR fixes 2 issues:

    • A user sending invalid data in a UUID-based plugin should not result in a server error, but a client error with a 400 status code.
    • Middlewares in Starlette should not raise Exceptions, they should abort the request cycle and send a response instead.

    The exact error is customizable from user code, but provides a default 400 response with empty body. Implemented on both ContextMiddleware and RawContextMiddleware.

    enhancement 
    opened by hhamana 11
  • Testing code that relies on context vars without a full test client / app

    Testing code that relies on context vars without a full test client / app

    Sometimes it can be very useful to write tests for functions that rely on some data in the request context, but without using a full Starlette test client, as tests that are based on test clients are much less "lean" and do a lot of things beyond just running the code unit under test.

    I think it would be very useful to document / add some helpers that enable a context manager that mocks a request / response cycle / middleware / etc. so it can be used in testing.

    For now, I have done this in my tests (I'm using pytest fixtures, this is a very simplified example):

    code under test

    from starlette_context import context
    
    def get_session_id():
        """Get the current session ID"""
        return context['session_id']
    

    in conftest.py:

    import pytest
    from starlette_context import _request_scope_context_storage, context
    
    
    @pytest.fixture()
    def request_context():
        token = _request_scope_context_storage.set({})
        try:
            yield context
        finally:
            _request_scope_context_storage.reset(token)
    

    in tests:

    from myapp.session_utils import get_session_id 
    
    
    def test_some_func_that_relies_on_context(request_context):
        request_context['session_id'] = 'foobar'
        assert get_session_id() == 'foobar'
    

    Obviously, this is a bit hackish and it would be nice if there would be an official / documented way of doing this without accessing module internals.

    question 
    opened by shevron 8
  • Are there plans to add type hints or library stubs for MyPy?

    Are there plans to add type hints or library stubs for MyPy?

    I'm using your library right now in a FastAPI app, and since everything's heavily typed in FastAPI (and Pydantic), I have MyPy enabled and it's using quite a strict configuration. Right now, it's complaining about the imports from starlette_context:

    Code:

    from starlette_context import context
    from starlette_context.plugins import Plugin, RequestIdPlugin
    

    Error:

    Skipping analyzing 'starlette_context': found module but no type hints or library stubs  [import]
    Skipping analyzing 'starlette_context.plugins': found module but no type hints or library stubs  [import]
    See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
    

    I can always just suppress the error with # type: ignore but I was wondering if you were planning on adding type hints or stub packages/files, as recommended by MyPy here: https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-type-hints-for-third-party-library. I think that would be a better approach.

    In case there's no plan for this yet, what would you say to a PR to add stub files? Basically, I think just adding the appropriate .pyi files next to each .py file would satisfy MyPy.

    invalid wontfix 
    opened by ginomempin 8
  • uninitialized context access raises LookupError

    uninitialized context access raises LookupError

    Here is a testcase:

    >>> from starlette_context import context
    >>> context
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/home/dmig/.pyenv/versions/3.8.2/lib/python3.8/collections/__init__.py", line 1014, in __repr__
        def __repr__(self): return repr(self.data)
      File "/home/dmig/.pyenv/versions/marty-services-3.8.2/lib/python3.8/site-packages/starlette_context/ctx.py", line 26, in data
        return _request_scope_context_storage.get()
    LookupError: <ContextVar name='starlette_context' at 0x7f3b3be81c20>
    
    enhancement invalid question 
    opened by dmig-alarstudios 8
  • ContextDoesNotExistError on FastAPI Custom Exception Handler

    ContextDoesNotExistError on FastAPI Custom Exception Handler

    Hi, i'm using Depend FastAPI mechaninsm to access Context on request-response cycle:

    async def my_context_dependency(
        x_plt_session_id: str = Header(None),
        x_plt_correlation_id: str = Header(None),
        x_plt_user_id: str = Header(None),
        x_plt_event_id: str = Header(None),
        x_plt_solution_user: str = Header(None),
    ) -> Any:
        # When used a Depends(), this fucntion get the `X-Client_ID` header,
        # which will be documented as a required header by FastAPI.
        # use `x_client_id: str = Header(None)` for an optional header.
    
        data = {
            "session-id": x_plt_session_id,
            "correlation-id": x_plt_correlation_id,
            "user-id": x_plt_user_id,
            "event-id": x_plt_event_id,
            "solution-user": x_plt_solution_user,
        }
        with request_cycle_context(data):
            # yield allows it to pass along to the rest of the request
            yield
    
    app = FastAPI(
            dependencies=[Depends(my_context_dependency)],
            title="Virtual Entity API",
            description="This is a very fancy project, with auto docs for the API and everything",
            version="0.0.1",
            openapi_url="/openapi.json"
        )
    

    I'm also using custom exception handler in FastAPI:

    from starlette_context import context
    
    @app.exception_handler(AWSException)
    async def unicorn_exception_handler(
        request: Request, exc: AWSException
    ) -> JSONResponse:
    
       user_id = context.get("user-id", default=None)
        
        print(user_id )
    
        return JSONResponse(
            status_code=400,
            content={"message": exc.message},
        )
    

    But when i try to access context inside the custom exception handler i receive :

    starlette_context.errors.ContextDoesNotExistError: You didn't use the required middleware or you're trying to access context object outside of the request-response cycle.

    Any hints ?

    enhancement invalid 
    opened by mancioshell 7
  • `starlette_context` context manager

    `starlette_context` context manager

    This creates and exposes a context manager that instantiates and resets the Token.

    This isn't much actual code, it may look like it only slightly improves the code reuse of the token initialization/reset code, but exposing it while abstracting the internal details allows 2 more important use cases:

    • within unit tests, creating a mock environment to test a portion using the context out of a request-response cycle (helping for #46).
    • leveraging FastAPI framework Depends() to allow required/optional headers to be visibly documented, plus use the full scope of FastAPI features (such as easy access to the request's body within a Depends system, (as #50 shown might be a use case).

    This also introduces the first explicit support of FastAPI, which, while limited, may attract more attention to the library.

    documentation enhancement 
    opened by hhamana 6
  • rewrite middleware to pure ASGI

    rewrite middleware to pure ASGI

    Since there are issues in Starlette caused by BaseHTTPMiddleware class, this package becomes a source of these issues in any project using it:

    • https://github.com/encode/starlette/issues/919
    • https://github.com/encode/starlette/issues/1012

    The simple solution would be to rewrite the middleware to pure ASGI.

    enhancement 
    opened by dmig-alarstudios 5
  • PluginUUIDBase's force_new_uuid option seems broken

    PluginUUIDBase's force_new_uuid option seems broken

    When using PluginUUIDBase's force_new_uuid option and setting it to True I'm always getting the same uuid. Which is the opposite of what I expect.

    It looks like when self.value is None a new uuid is generated, and this works fine when for the first request. But subsequent request seem to be using the same id.

    I suspect the code that needs to be fixed is missing a self.value = None before trying anything else here: https://github.com/tomwojcik/starlette-context/blob/2f80262bbf4c00fb501c22ac04a5c1c408187fe6/starlette_context/plugins/plugin_uuid.py#L34

    bug 
    opened by wapiflapi 5
  • Support for CorrelationIdPlugin and CorrelationIdPlugin still executing when application encounters an exception

    Support for CorrelationIdPlugin and CorrelationIdPlugin still executing when application encounters an exception

    Issue

    When a route raises an exception and so returns a 500 Internal Server Error, x-request-id and x-correlation-id are not set.

    The culprit seems to be https://github.com/tomwojcik/starlette-context/blob/79aa8ffe5b50db2263e2073fc5d252bf442f150c/starlette_context/middleware.py#L46-L52

    This is similar to this comment - https://github.com/tiangolo/fastapi/issues/397#issuecomment-543136587

    Expectation I'd expect those headers to be set for all responses. One benefit of correlation ids is when there are errors, we can use the ids to track down the issue.

    Steps to reproduce Sample. The "/" endpoint returns the x-request-id and x-correlation-id headers. The "/error" does not.

    from starlette.applications import Starlette
    from starlette.middleware import Middleware
    from starlette.requests import Request
    from starlette.responses import JSONResponse
    
    import uvicorn
    from starlette_context import context, plugins
    from starlette_context.middleware import ContextMiddleware
    
    middleware = [
        Middleware(
            ContextMiddleware,
            plugins=(plugins.RequestIdPlugin(), plugins.CorrelationIdPlugin()),
        )
    ]
    
    app = Starlette(debug=True, middleware=middleware)
    
    
    @app.route("/")
    async def index(request: Request):
        return JSONResponse(context.data)
    
    @app.route("/error")
    async def index(request: Request):
        raise RuntimeError()
        return JSONResponse(context.data)
    
    uvicorn.run(app, host="0.0.0.0")
    
    documentation question 
    opened by derekbekoe 5
  • Can't write new plugin - plugin is not iterable

    Can't write new plugin - plugin is not iterable

    Hi!

    I am quite new to Starlette and FastAPI, and I might have holes in my knowledge, however I was not able to write a new plugin.

    from fastapi import FastAPI
    from fastapi.middleware import Middleware
    
    from starlette_context.header_keys import HeaderKeys
    from starlette_context.plugins import Plugin
    
    class LangPlugin(Plugin):
        print('****')
    
    middleware = [
        Middleware(
            RawContextMiddleware,
            plugins(
                LangPlugin()
            )
        )
    ]
    
    app = FastAPI(middleware=middleware)
    

    And it says:

      File ".../starlette_context/middleware/raw_middleware.py", line 17, in __init__
        if not all([isinstance(plugin, Plugin) for plugin in self.plugins]):
    TypeError: 'LangPlugin' object is not iterable
    

    How is my plugin not instance of Plugin, I wonder. Or I guess this is the problem.

    To put you into context, I would like to get from here an optional string param, (/path-to-api-endpoint/?lang=en) to access the request language from everywhere. Or maybe you have a more straightforward solution to my problem, what I did not recognize?

    invalid question 
    opened by dddenes 4
  • Add request-id to logs without using LoggerAdapter

    Add request-id to logs without using LoggerAdapter

    Thank you for this middleware!

    I want to use it for adding request IDs in the logs of third-party libraries that I do not have control over. Since most libraries might use a logger instead of LoggerAdapter, I am wondering if there is a way to log request IDs without the use LoggerAdapter(like in the example given in this repo).

    documentation 
    opened by dev-99 4
  • WIP: Test where asgi app is spammed with requests and the context (response headers) is always correct

    WIP: Test where asgi app is spammed with requests and the context (response headers) is always correct

    @hhamana I tested your PR. It works fine. I'm worried about regressions where context is not cleared though.

    I'd like to add a test where the client is spamming asgi app with requests. In the meantime, half of them fails. If we can check if response headers are correct after that, it should prove there are no regressions in real world apps with huge traffic.

    The only thing left is to figure out why await asyncio.sleep is not working.

    help wanted 
    opened by tomwojcik 0
  • Properly use pyproject.toml

    Properly use pyproject.toml

    The pyproject.toml we use is currently only for black config. While that's fine and all, pyproject.toml is originally intended to define the build system, and replace setup.cfg, which itself is already supposed to be a safer alternative to the setup.py As build system, using the status quo default setuptools is fine for us. But we have to be explicit about it. Latest updates to pip issue a DeprecatedWarning to systems still using setup.py or setup.cfg to define project metadata.

    recommended reading:

    • https://snarky.ca/what-the-heck-is-pyproject-toml/
    • https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html
    dependencies github_actions 
    opened by hhamana 2
  • drop error_response, add error detail and support for serialization

    drop error_response, add error detail and support for serialization

    Closes https://github.com/tomwojcik/starlette-context/issues/58

    bug enhancement 
    opened by tomwojcik 6
  • Improved logging

    Improved logging

    Not sure if I was doing something wrong but it seems like the library could provide more useful logging. For instance I was passing non valid UUID's as the X-Request-ID and in this case all the logging I saw on my server was:

    127.0.0.1:51990 - "POST /graphql/ HTTP/1.1" 400
    

    Some descriptive log message would help.

    bug enhancement 
    opened by KBoehme 4
  • Consider removing `RawContextMiddleware` and keeping only `ContextMiddleware` after resolving memory usage issue

    Consider removing `RawContextMiddleware` and keeping only `ContextMiddleware` after resolving memory usage issue

    I like ContextMiddleware because it's very simple to understand and expand, even for minds that are not familiar with async Python.

    Some time ago it was advised https://github.com/tomwojcik/starlette-context/issues/18 to add RawContextMiddleware and Starlette maintainers were discouraging the use of the built-in middleware. I was surprised to see 3 ❤️ reactions under this issue so I think a few people had problems with it.

    Fast-forward almost a year, the issue https://github.com/encode/starlette/issues/1012#issuecomment-866622798 with memory usage has been closed.

    If there's no point in keeping both, I'd be happy to remove the more complicated one but I will wait for some confirmations from the community. If there's a case when it makes sense to use RawContextMiddleware, then I'd keep both.

    What's your opinion about that? @hhamana @dmig-alarstudios

    question 
    opened by tomwojcik 8
Releases(v0.3.5)
  • v0.3.5(Nov 26, 2022)

  • v0.3.4(Jun 22, 2022)

    • add request_cycle_context. It’s a context manager that allows for easier testing and cleaner code (Thanks @hhamana) https://github.com/tomwojcik/starlette-context/issues/46
    • fix for accessing context during logging, outside of the request-response cycle. Technically it should raise an exception, but it makes sense to include the context by default (in logs) and if it’s not available, some logs are better than no logs. Now it will show context data if context is available, with a fallback to an empty dict (instead of raising an exc) https://github.com/tomwojcik/starlette-context/issues/65
    • add ContextMiddleware deprecation warning
    • **context context unpacking seems to be working now
    Source code(tar.gz)
    Source code(zip)
  • v0.3.3(Jun 28, 2021)

    • add support for custom error responses if error occurred in plugin / middleware -> fix for 500 (Thanks @hhamana)
    • better (custom) exceptions with a base StarletteContextError (Thanks @hhamana)
    Source code(tar.gz)
    Source code(zip)
  • v0.3.2(Apr 22, 2021)

    • ContextDoesNotExistError is raised when context object can't be accessed. Previously it was RuntimeError. For backwards compatibility, it inherits from RuntimeError so it shouldn't result in any regressions.
    • Added py.typed file so your mypy should never complain
    Source code(tar.gz)
    Source code(zip)
  • v0.3.1(Oct 17, 2020)

  • v0.3.0(Oct 10, 2020)

    • add RawContextMiddleware for Streaming and File responses
    • add flake8, isort, mypy
    • small refactor of the base plugin, moved directories and removed one redundant method (potentially breaking changes)
    Source code(tar.gz)
    Source code(zip)
  • v0.2.3(Jul 27, 2020)

    • add docs on read the docs
    • fix bug with force_new_uuid=True returning the same uuid constantly
    • due to ^ a lot of tests had to be refactored as well
    Source code(tar.gz)
    Source code(zip)
  • 0.2.2(Apr 26, 2020)

    • for correlation id and request id plugins, add support for enforcing the generation of a new value
    • for ^ plugins add support for validating uuid. It's a default behavior so will break things for people who don't use uuid4 there. If you don't want this validation, you need to pass validate=False to the plugin
    • thanks to @VukW you can now check if context is available
    Source code(tar.gz)
    Source code(zip)
  • 0.2.1(Apr 18, 2020)

    • dropped with_plugins from the middleware as Starlette has it's own way of doing this
    • due to ^ this change some tests are simplified
    • if context is not available no LookupError will be raised, instead there will be RuntimeError, because this error might mean one of two things: user either didn't use ContextMiddleware or is trying to access context object outside of request-response cycle
    Source code(tar.gz)
    Source code(zip)
  • 0.2.0(Feb 21, 2020)

    • changed parent of context object. More or less the API is the same but due to this change the implementation itself is way more simple and now it's possible to use .items() or keys() like in a normal dict, out of the box. Still, unpacking **kwargs is not supported and I don't think it ever will be. I tried to inherit from the builtin dict but nothing good came out of this. Now you access context as dict using context.data, not context.dict()
    • there was an issue related to not having awaitable plugins. Now both middleware and plugins are fully async compatible. It's a breaking change as it forces to use await, hence new minor version
    Source code(tar.gz)
    Source code(zip)
  • 0.1.6(Jan 2, 2020)

  • 0.1.5(Jan 1, 2020)

  • 0.1.4(Dec 31, 2019)

  • 0.1.3(Dec 30, 2019)

  • 0.1.2(Dec 30, 2019)

  • 0.1.1(Dec 28, 2019)

  • 0.1(Dec 27, 2019)

Owner
Tomasz Wójcik
There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors.
Tomasz Wójcik
Hook Slinger acts as a simple service that lets you send, retry, and manage event-triggered POST requests, aka webhooks

Hook Slinger acts as a simple service that lets you send, retry, and manage event-triggered POST requests, aka webhooks. It provides a fully self-contained docker image that is easy to orchestrate, m

Redowan Delowar 96 Jan 02, 2023
🔀⏳ Easy throttling with asyncio support

Throttler Zero-dependency Python package for easy throttling with asyncio support. 📝 Table of Contents 🎒 Install 🛠 Usage Examples Throttler and Thr

Ramzan Bekbulatov 80 Dec 07, 2022
Learn to deploy a FastAPI application into production DigitalOcean App Platform

Learn to deploy a FastAPI application into production DigitalOcean App Platform. This is a microservice for our Try Django 3.2 project. The goal is to extract any and all text from images using a tec

Coding For Entrepreneurs 59 Nov 29, 2022
A FastAPI Framework for things like Database, Redis, Logging, JWT Authentication and Rate Limits

A FastAPI Framework for things like Database, Redis, Logging, JWT Authentication and Rate Limits Install You can install this Library with: pip instal

Tert0 33 Nov 28, 2022
Mixer -- Is a fixtures replacement. Supported Django, Flask, SqlAlchemy and custom python objects.

The Mixer is a helper to generate instances of Django or SQLAlchemy models. It's useful for testing and fixture replacement. Fast and convenient test-

Kirill Klenov 871 Dec 25, 2022
Flask + marshmallow for beautiful APIs

Flask-Marshmallow Flask + marshmallow for beautiful APIs Flask-Marshmallow is a thin integration layer for Flask (a Python web framework) and marshmal

marshmallow-code 768 Dec 22, 2022
Fastapi performans monitoring

Fastapi-performans-monitoring This project is a simple performance monitoring for FastAPI. License This project is licensed under the terms of the MIT

bilal alpaslan 11 Dec 31, 2022
Formatting of dates and times in Flask templates using moment.js.

Flask-Moment This extension enhances Jinja2 templates with formatting of dates and times using moment.js. Quick Start Step 1: Initialize the extension

Miguel Grinberg 358 Nov 28, 2022
REST API with FastAPI and PostgreSQL

REST API with FastAPI and PostgreSQL To have the same data in db: create table CLIENT_DATA (id SERIAL PRIMARY KEY, fullname VARCHAR(50) NOT NULL,email

Luis Quiñones Requelme 1 Nov 11, 2021
Flask-vs-FastAPI - Understanding Flask vs FastAPI Web Framework. A comparison of two different RestAPI frameworks.

Flask-vs-FastAPI Understanding Flask vs FastAPI Web Framework. A comparison of two different RestAPI frameworks. IntroductionIn Flask is a popular mic

Mithlesh Navlakhe 1 Jan 01, 2022
Cache-house - Caching tool for python, working with Redis single instance and Redis cluster mode

Caching tool for python, working with Redis single instance and Redis cluster mo

Tural 14 Jan 06, 2022
LuSyringe is a documentation injection tool for your classes when using Fast API

LuSyringe LuSyringe is a documentation injection tool for your classes when using Fast API Benefits The main benefit is being able to separate your bu

Enzo Ferrari 2 Sep 06, 2021
A FastAPI Plug-In to support authentication authorization using the Microsoft Authentication Library (MSAL)

FastAPI/MSAL - MSAL (Microsoft Authentication Library) plugin for FastAPI FastAPI - https://github.com/tiangolo/fastapi FastAPI is a modern, fast (hig

Dudi Levy 15 Jul 20, 2022
Utils for fastapi based services.

Installation pip install fastapi-serviceutils Usage For more details and usage see: readthedocs Development Getting started After cloning the repo

Simon Kallfass 31 Nov 25, 2022
Docker image with Uvicorn managed by Gunicorn for high-performance FastAPI web applications in Python 3.6 and above with performance auto-tuning. Optionally with Alpine Linux.

Supported tags and respective Dockerfile links python3.8, latest (Dockerfile) python3.7, (Dockerfile) python3.6 (Dockerfile) python3.8-slim (Dockerfil

Sebastián Ramírez 2.1k Dec 31, 2022
FastAPI application and service structure for a more maintainable codebase

Abstracting FastAPI Services See this article for more information: https://camillovisini.com/article/abstracting-fastapi-services/ Poetry poetry inst

Camillo Visini 309 Jan 04, 2023
Keycloak integration for Python FastAPI

FastAPI Keycloak Integration Documentation Introduction Welcome to fastapi-keycloak. This projects goal is to ease the integration of Keycloak (OpenID

Code Specialist 113 Dec 31, 2022
flask extension for integration with the awesome pydantic package

flask extension for integration with the awesome pydantic package

249 Jan 06, 2023
Cookiecutter API for creating Custom Skills for Azure Search using Python and Docker

cookiecutter-spacy-fastapi Python cookiecutter API for quick deployments of spaCy models with FastAPI Azure Search The API interface is compatible wit

Microsoft 379 Jan 03, 2023
An extension library for FastAPI framework

FastLab An extension library for FastAPI framework Features Logging Models Utils Routers Installation use pip to install the package: pip install fast

Tezign Lab 10 Jul 11, 2022