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
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
Cookiecutter template for FastAPI projects using: Machine Learning, Poetry, Azure Pipelines and Pytests

cookiecutter-fastapi In order to create a template to FastAPI projects. 🚀 Important To use this project you don't need fork it. Just run cookiecutter

Arthur Henrique 225 Dec 28, 2022
FastAPI + PeeWee = <3

FastAPIwee FastAPI + PeeWee = 3 Using Python = 3.6 🐍 Installation pip install FastAPIwee 🎉 Documentation Documentation can be found here: https://

16 Aug 30, 2022
Boilerplate code for quick docker implementation of REST API with JWT Authentication using FastAPI, PostgreSQL and PgAdmin ⭐

FRDP Boilerplate code for quick docker implementation of REST API with JWT Authentication using FastAPI, PostgreSQL and PgAdmin ⛏ . Getting Started Fe

BnademOverflow 53 Dec 29, 2022
Stac-fastapi built on Tile38 and Redis to support caching

stac-fastapi-caching Stac-fastapi built on Tile38 to support caching. This code is built on top of stac-fastapi-elasticsearch 0.1.0 with pyle38, a Pyt

Jonathan Healy 4 Apr 11, 2022
This code generator creates FastAPI app from an openapi file.

fastapi-code-generator This code generator creates FastAPI app from an openapi file. This project is an experimental phase. fastapi-code-generator use

Koudai Aono 632 Jan 05, 2023
api versioning for fastapi web applications

fastapi-versioning api versioning for fastapi web applications Installation pip install fastapi-versioning Examples from fastapi import FastAPI from f

Dean Way 472 Jan 02, 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
FastAPI Auth Starter Project

This is a template for FastAPI that comes with authentication preconfigured.

Oluwaseyifunmi Oyefeso 6 Nov 13, 2022
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
Light, Flexible and Extensible ASGI API framework

Starlite Starlite is a light and flexible ASGI API framework. Using Starlette and pydantic as foundations. Check out the Starlite documentation 📚 Cor

1.5k Jan 04, 2023
Drop-in MessagePack support for ASGI applications and frameworks

msgpack-asgi msgpack-asgi allows you to add automatic MessagePack content negotiation to ASGI applications (Starlette, FastAPI, Quart, etc.), with a s

Florimond Manca 128 Jan 02, 2023
API using python and Fastapi framework

Welcome 👋 CFCApi is a API DEVELOPMENT PROJECT UNDER CODE FOR COMMUNITY ! Project Walkthrough 🚀 CFCApi run on Python using FASTapi Framework Docs The

Abhishek kushwaha 7 Jan 02, 2023
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
Fast, simple API for Apple firmwares.

Loyal Fast, Simple API for fetching Apple Firmwares. The API server is closed due to some reasons. Wait for v2 releases. Features Fetching Signed IPSW

11 Oct 28, 2022
Ready-to-use and customizable users management for FastAPI

FastAPI Users Ready-to-use and customizable users management for FastAPI Documentation: https://fastapi-users.github.io/fastapi-users/ Source Code: ht

FastAPI Users 2.3k Dec 30, 2022
京东图片点击验证码识别

京东图片验证码识别 本项目是@yqchilde 大佬的 JDMemberCloseAccount 识别图形验证码(#45)思路验证,若你也有思路可以提交Issue和PR也可以在 @yqchilde 的 TG群 找到我 声明 本脚本只是为了学习研究使用 本脚本除了采集处理验证码图片没有其他任何功能,也

AntonVanke 37 Dec 22, 2022
FastAPI with async for generating QR codes and bolt11 for Lightning Addresses

sendsats An API for getting QR codes and Bolt11 Invoices from Lightning Addresses. Share anywhere; as a link for tips on a twitter profile, or via mes

Bitkarrot 12 Jan 07, 2023
Dead-simple mailer micro-service for static websites

Mailer Dead-simple mailer micro-service for static websites A free and open-source software alternative to contact form services such as FormSpree, to

Romain Clement 42 Dec 21, 2022
A Sample App to Demonstrate React Native and FastAPI Integration

React Native - Service Integration with FastAPI Backend. A Sample App to Demonstrate React Native and FastAPI Integration UI Based on NativeBase toolk

YongKi Kim 4 Nov 17, 2022