From c5114e8138d285670e126ffe98cb971e4c6c46de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sondre=20Lilleb=C3=B8=20Gundersen?= Date: Sun, 15 May 2022 21:17:48 +0200 Subject: [PATCH 1/8] refactor: Rename validator uuid validator --- asgi_correlation_id/middleware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/asgi_correlation_id/middleware.py b/asgi_correlation_id/middleware.py index 8d60ed5..d0b06c7 100644 --- a/asgi_correlation_id/middleware.py +++ b/asgi_correlation_id/middleware.py @@ -14,7 +14,7 @@ logger = logging.getLogger('asgi_correlation_id') -def is_valid_uuid(uuid_: str) -> bool: +def is_valid_uuid4(uuid_: str) -> bool: """ Check whether a string is a valid v4 uuid. """ @@ -42,7 +42,7 @@ async def __call__(self, scope: 'Scope', receive: 'Receive', send: 'Send') -> No if not header_value: id_value = uuid4().hex - elif self.validate_header_as_uuid and not is_valid_uuid(header_value): + elif self.validate_header_as_uuid and not is_valid_uuid4(header_value): logger.warning('Generating new UUID, since header value \'%s\' is invalid', header_value) id_value = uuid4().hex else: From 922e708a23549acd1e6bb10472836e770d832799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sondre=20Lilleb=C3=B8=20Gundersen?= Date: Sun, 15 May 2022 21:31:13 +0200 Subject: [PATCH 2/8] feat: Rework request ID generation and validation Add concept of `generator`, `validator`, and `transformer` to make request ID generation, validation, and cleaning fully configurable. --- asgi_correlation_id/middleware.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/asgi_correlation_id/middleware.py b/asgi_correlation_id/middleware.py index d0b06c7..8bfe46c 100644 --- a/asgi_correlation_id/middleware.py +++ b/asgi_correlation_id/middleware.py @@ -1,6 +1,6 @@ import logging -from dataclasses import dataclass -from typing import TYPE_CHECKING +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Callable, Optional from uuid import UUID, uuid4 from starlette.datastructures import Headers, MutableHeaders @@ -28,7 +28,15 @@ def is_valid_uuid4(uuid_: str) -> bool: class CorrelationIdMiddleware: app: 'ASGIApp' header_name: str = 'X-Request-ID' - validate_header_as_uuid: bool = True + + # ID-generating callable + generator: Callable[[], str] = field(default=lambda: uuid4().hex) + + # ID validator + validator: Optional[Callable[[str], bool]] = field(default=is_valid_uuid4) + + # ID transformer - can be used to clean/mutate IDs + transformer: Optional[Callable[[str], str]] = field(default=lambda a: a) async def __call__(self, scope: 'Scope', receive: 'Receive', send: 'Send') -> None: """ @@ -38,16 +46,24 @@ async def __call__(self, scope: 'Scope', receive: 'Receive', send: 'Send') -> No await self.app(scope, receive, send) return + # Try to load request ID from the request headers header_value = Headers(scope=scope).get(self.header_name.lower()) if not header_value: - id_value = uuid4().hex - elif self.validate_header_as_uuid and not is_valid_uuid4(header_value): - logger.warning('Generating new UUID, since header value \'%s\' is invalid', header_value) - id_value = uuid4().hex + # Generate request ID if none was found + id_value = self.generator() + elif self.validator and not self.validator(header_value): + # Also generate a request ID if one was found, but it was deemed invalid + logger.warning('Generating new request ID, since header value \'%s\' is invalid', header_value) + id_value = self.generator() else: + # Otherwise, use the found request ID id_value = header_value + # Clean/change the ID if needed + if self.transformer: + id_value = self.transformer(id_value) + correlation_id.set(id_value) self.sentry_extension(id_value) From db2a00bba3f8892e2e5d6a53edc3a1e22da18b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sondre=20Lilleb=C3=B8=20Gundersen?= Date: Sun, 15 May 2022 21:47:23 +0200 Subject: [PATCH 3/8] tests: Add and refactor tests for new class variables --- tests/conftest.py | 10 ++- tests/test_extension_celery.py | 4 +- tests/test_middleware.py | 147 ++++++++++++++++++++------------- 3 files changed, 100 insertions(+), 61 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f06ff07..f6aba84 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -43,8 +43,14 @@ def _configure_logging(): } dictConfig(LOGGING) +TRANSFORMER_VALUE = 'some-id' -app = FastAPI(middleware=[Middleware(CorrelationIdMiddleware)]) +default_app = FastAPI(middleware=[Middleware(CorrelationIdMiddleware)]) +no_validator_or_transformer_app = FastAPI( + middleware=[Middleware(CorrelationIdMiddleware, validator=None, transformer=None)] +) +transformer_app = FastAPI(middleware=[Middleware(CorrelationIdMiddleware, transformer=lambda a: a * 2)]) +generator_app = FastAPI(middleware=[Middleware(CorrelationIdMiddleware, generator=lambda: TRANSFORMER_VALUE)]) @pytest.fixture(scope='session', autouse=True) @@ -56,5 +62,5 @@ def event_loop(): @pytest.fixture(scope='module') async def client() -> AsyncClient: - async with AsyncClient(app=app, base_url='http://test') as client: + async with AsyncClient(app=default_app, base_url='http://test') as client: yield client diff --git a/tests/test_extension_celery.py b/tests/test_extension_celery.py index e52da5d..dad8e04 100644 --- a/tests/test_extension_celery.py +++ b/tests/test_extension_celery.py @@ -5,7 +5,7 @@ from celery import shared_task from asgi_correlation_id.extensions.celery import load_celery_current_and_parent_ids, load_correlation_ids -from tests.conftest import app +from tests.conftest import default_app logger = logging.getLogger('asgi_correlation_id') @@ -40,7 +40,7 @@ async def test_endpoint_to_worker_to_worker(client, caplog, celery_session_app, - The current ID of the first worker to be added as the parent ID of the second worker """ - @app.get('/celery-test', status_code=200) + @default_app.get('/celery-test', status_code=200) async def test_view() -> dict: logger.debug('Test view') task1.delay().get(timeout=10) diff --git a/tests/test_middleware.py b/tests/test_middleware.py index 61326df..2784d97 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -4,53 +4,52 @@ import pytest from fastapi import Response +from httpx import AsyncClient from starlette.testclient import TestClient -from tests.conftest import app +from tests.conftest import default_app, generator_app, no_validator_or_transformer_app, transformer_app, \ + TRANSFORMER_VALUE if TYPE_CHECKING: from starlette.websockets import WebSocket logger = logging.getLogger('asgi_correlation_id') -pytestmark = pytest.mark.asyncio - - -@app.get('/test', status_code=200) -async def test_view() -> dict: - logger.debug('Test view') - return {'test': 'test'} +apps = [default_app, no_validator_or_transformer_app, transformer_app, generator_app] - -@app.websocket_route('/ws') -async def websocket(websocket: 'WebSocket'): - await websocket.accept() - await websocket.send_json({'msg': 'Hello WebSocket'}) - await websocket.close() +pytestmark = pytest.mark.asyncio -async def test_returned_response_headers(client): +@pytest.mark.parametrize('app', [default_app, no_validator_or_transformer_app, generator_app]) +async def test_returned_response_headers(app): """ We expect: - our request id header to be returned back to us - the request id header name to be returned in access-control-expose-headers """ - # Check we get the right headers back - correlation_id = uuid4().hex - response = await client.get('test', headers={'X-Request-ID': correlation_id}) - assert response.headers['access-control-expose-headers'] == 'X-Request-ID' - assert response.headers['X-Request-ID'] == correlation_id - # And do it one more time, jic - second_correlation_id = uuid4().hex - second_response = await client.get('test', headers={'X-Request-ID': second_correlation_id}) - assert second_response.headers['access-control-expose-headers'] == 'X-Request-ID' - assert second_response.headers['X-Request-ID'] == second_correlation_id + @app.get('/test', status_code=200) + async def test_view() -> dict: + logger.debug('Test view') + return {'test': 'test'} + + async with AsyncClient(app=app, base_url='http://test') as client: + # Check we get the right headers back + correlation_id = uuid4().hex + response = await client.get('test', headers={'X-Request-ID': correlation_id}) + assert response.headers['access-control-expose-headers'] == 'X-Request-ID' + assert response.headers['X-Request-ID'] == correlation_id - # Then try without specifying a request id - third_response = await client.get('test') - assert third_response.headers['access-control-expose-headers'] == 'X-Request-ID' - assert third_response.headers['X-Request-ID'] not in [correlation_id, second_correlation_id] + # And do it one more time, jic + second_correlation_id = uuid4().hex + second_response = await client.get('test', headers={'X-Request-ID': second_correlation_id}) + assert second_response.headers['access-control-expose-headers'] == 'X-Request-ID' + assert second_response.headers['X-Request-ID'] == second_correlation_id + + # Then try without specifying a request id + third_response = await client.get('test') + assert third_response.headers['access-control-expose-headers'] == 'X-Request-ID' + assert third_response.headers['X-Request-ID'] not in [correlation_id, second_correlation_id] bad_uuids = [ @@ -62,55 +61,89 @@ async def test_returned_response_headers(client): @pytest.mark.parametrize('value', bad_uuids) -async def test_non_uuid_header(client, caplog, value): +@pytest.mark.parametrize('app', [default_app, transformer_app, generator_app]) +async def test_non_uuid_header(client, caplog, value, app): """ We expect the middleware to ignore our request ID and log a warning when the request ID we pass doesn't correspond to the uuid4 format. """ - response = await client.get('test', headers={'X-Request-ID': value}) - assert response.headers['X-Request-ID'] != value - assert caplog.messages[0] == f"Generating new UUID, since header value '{value}' is invalid" + @app.get('/test', status_code=200) + async def test_view() -> dict: + logger.debug('Test view') + return {'test': 'test'} + + async with AsyncClient(app=app, base_url='http://test') as client: + response = await client.get('test', headers={'X-Request-ID': value}) + assert response.headers['X-Request-ID'] != value + assert caplog.messages[0] == f"Generating new request ID, since header value '{value}' is invalid" -async def test_websocket_request(caplog): +@pytest.mark.parametrize('app', apps) +async def test_websocket_request(caplog, app): """ We expect websocket requests to not be handled. - This test could use improvement. """ - client = TestClient(app) - with client.websocket_connect('/ws') as websocket: - websocket.receive_json() - assert caplog.messages == [] + @app.websocket_route('/ws') + async def websocket(websocket: 'WebSocket'): + await websocket.accept() + await websocket.send_json({'msg': 'Hello WebSocket'}) + await websocket.close() -@app.get('/access-control-expose-headers') -async def access_control_view() -> Response: - return Response(status_code=204, headers={'Access-Control-Expose-Headers': 'test1, test2'}) + client = TestClient(app) + with client.websocket_connect('/ws') as ws: + ws.receive_json() + assert caplog.messages == [] -async def test_access_control_expose_headers(client, caplog): +@pytest.mark.parametrize('app', apps) +async def test_access_control_expose_headers(caplog, app): """ The middleware should add the correlation ID header name to exposed headers. - The middleware should not overwrite other values, but should append to it. """ - response = await client.get('access-control-expose-headers') - assert response.headers['Access-Control-Expose-Headers'] == 'test1, test2, X-Request-ID' + @app.get('/access-control-expose-headers') + async def access_control_view() -> Response: + return Response(status_code=204, headers={'Access-Control-Expose-Headers': 'test1, test2'}) + async with AsyncClient(app=app, base_url='http://test') as client: + response = await client.get('access-control-expose-headers') + assert response.headers['Access-Control-Expose-Headers'] == 'test1, test2, X-Request-ID' -@app.get('/multiple_headers_same_name') -async def multiple_headers_response() -> Response: - response = Response(status_code=204) - response.set_cookie('access_token_cookie', 'test-access-token') - response.set_cookie('refresh_token_cookie', 'test-refresh-token') - return response - -async def test_multiple_headers_same_name(client, caplog): +@pytest.mark.parametrize('app', apps) +async def test_multiple_headers_same_name(caplog, app): """ The middleware should not change the headers that were set in the response and return all of them as it is. """ - response = await client.get('multiple_headers_same_name') - assert response.headers['set-cookie'].find('access_token_cookie') != -1 - assert response.headers['set-cookie'].find('refresh_token_cookie') != -1 + @app.get('/multiple_headers_same_name') + async def multiple_headers_response() -> Response: + response = Response(status_code=204) + response.set_cookie('access_token_cookie', 'test-access-token') + response.set_cookie('refresh_token_cookie', 'test-refresh-token') + return response + + async with AsyncClient(app=app, base_url='http://test') as client: + response = await client.get('multiple_headers_same_name') + assert response.headers['set-cookie'].find('access_token_cookie') != -1 + assert response.headers['set-cookie'].find('refresh_token_cookie') != -1 + + +async def test_no_validator(): + async with AsyncClient(app=no_validator_or_transformer_app, base_url='http://test') as client: + response = await client.get('test', headers={'X-Request-ID': 'bad-uuid'}) + assert response.headers['X-Request-ID'] == 'bad-uuid' + + +async def test_custom_transformer(): + cid = uuid4().hex + async with AsyncClient(app=transformer_app, base_url='http://test') as client: + response = await client.get('test', headers={'X-Request-ID': cid}) + assert response.headers['X-Request-ID'] == cid * 2 + + +async def test_custom_generator(): + async with AsyncClient(app=generator_app, base_url='http://test') as client: + response = await client.get('test', headers={'X-Request-ID': 'bad-uuid'}) + assert response.headers['X-Request-ID'] == TRANSFORMER_VALUE From d2886db48135c918a11d07efdc8257e5f89634b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sondre=20Lilleb=C3=B8=20Gundersen?= Date: Sun, 15 May 2022 21:53:12 +0200 Subject: [PATCH 4/8] docs: Add section on middleware configuration --- README.md | 66 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c71ef2a..336e1ac 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # ASGI Correlation ID middleware -Middleware for loading or generating correlation IDs for each incoming request. Correlation IDs can be added to your +Middleware for reading or generating correlation IDs for each incoming request. Correlation IDs can then be added to your logs, making it simple to retrieve all logs generated from a single HTTP request. When the middleware detects a correlation ID HTTP header in an incoming request, the ID is stored. If no header is @@ -64,21 +64,7 @@ app.add_middleware(CorrelationIdMiddleware) or any other way your framework allows. -For [Starlette](https://github.com/encode/starlette) apps, just substitute `FastAPI` with `Starlette` in the example -above. - -The middleware only has two settings, and can be configured like this: - -```python -app.add_middleware( - CorrelationIdMiddleware, - # The HTTP header key to read IDs from. - header_name='X-Request-ID', - # Enforce UUID formatting to limit chance of collisions - # - Invalid header values are discarded, and an ID is generated in its place - validate_header_as_uuid=True -) -``` +For [Starlette](https://github.com/encode/starlette) apps, just substitute `FastAPI` with `Starlette` in all examples. ## Configure logging @@ -154,6 +140,54 @@ LOGGING = { If you're using a json log-formatter, just add `correlation-id: %(correlation_id)s` to your list of properties. +## Middleware configuration + +The middleware can be configured in a few ways, but there are no required arguments. + +```python +app.add_middleware( + CorrelationIdMiddleware, + header_name='X-Request-ID', + generator=lambda: uuid4().hex, + validator=is_valid_uuid4, + transformer=lambda a: a, +) +``` + +Configurable middleware arguments include: + +**header_name** + +- Type: `str` +- Default: `X-Request-ID` +- Description: The header name decides which HTTP header value to read correlation IDs from. `X-Request-ID` and + `X-Correlation-ID` are common choices. + +**generator** + +- Type: `Callable[[], str]` +- Default: `lambda: uuid4().hex` +- Description: The generator function is responsible for generating new correlation IDs when no ID is received from an + incoming request's headers. We use UUIDs by default, but if you prefer, you could use libraries + like [nanoid](https://github.com/puyuan/py-nanoid) or your own custom function. + +**validator** + +- Type: `Callable[[str], bool]` +- Default: `is_valid_uuid` ( + found [here](https://github.com/snok/asgi-correlation-id/blob/main/asgi_correlation_id/validators.py)) +- Description: The validator function is used when reading incoming HTTP header values. By default, we discard non-UUID + formatted header values, to enforce correlation ID uniqueness. If you prefer to allow any header value, you can set + this setting to `None`, or pass your own validator. + +**transformer** + +- Type: `Callable[[str], str]` +- Default: `lambda a: a` +- Description: Most users won't need a transformer, and by default we do nothing. + The argument was added for cases where users might want to alter incoming or generated ID values in some way. It + provides a mechanism for transforming an incoming ID in a way you see fit. See the middleware code for more context. + ## Exception handling By default, the `X-Correlation-ID` and `Access-Control-Expose-Headers` response headers will be included in all From 70486286285924e658909fee897ffd3e959f3e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sondre=20Lilleb=C3=B8=20Gundersen?= Date: Sun, 15 May 2022 21:53:35 +0200 Subject: [PATCH 5/8] chore: Update pre-commit hooks and black preview flag --- .pre-commit-config.yaml | 4 ++-- pyproject.toml | 2 +- tests/conftest.py | 1 + tests/test_middleware.py | 12 ++++++++++-- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8f19879..6eb9761 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,13 +40,13 @@ repos: ] - repo: https://github.com/sirosen/check-jsonschema - rev: 0.14.3 + rev: 0.15.0 hooks: - id: check-github-actions - id: check-github-workflows - repo: https://github.com/asottile/pyupgrade - rev: v2.32.0 + rev: v2.32.1 hooks: - id: pyupgrade args: [ "--py36-plus" ] diff --git a/pyproject.toml b/pyproject.toml index ab98e61..a568a25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ build-backend = "poetry.masonry.api" quiet = true line-length = 120 skip-string-normalization = true -experimental-string-processing = true +preview = true [tool.isort] profile = "black" diff --git a/tests/conftest.py b/tests/conftest.py index f6aba84..6e80d7e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -43,6 +43,7 @@ def _configure_logging(): } dictConfig(LOGGING) + TRANSFORMER_VALUE = 'some-id' default_app = FastAPI(middleware=[Middleware(CorrelationIdMiddleware)]) diff --git a/tests/test_middleware.py b/tests/test_middleware.py index 2784d97..7762c5f 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -7,8 +7,13 @@ from httpx import AsyncClient from starlette.testclient import TestClient -from tests.conftest import default_app, generator_app, no_validator_or_transformer_app, transformer_app, \ - TRANSFORMER_VALUE +from tests.conftest import ( + TRANSFORMER_VALUE, + default_app, + generator_app, + no_validator_or_transformer_app, + transformer_app, +) if TYPE_CHECKING: from starlette.websockets import WebSocket @@ -67,6 +72,7 @@ async def test_non_uuid_header(client, caplog, value, app): We expect the middleware to ignore our request ID and log a warning when the request ID we pass doesn't correspond to the uuid4 format. """ + @app.get('/test', status_code=200) async def test_view() -> dict: logger.debug('Test view') @@ -103,6 +109,7 @@ async def test_access_control_expose_headers(caplog, app): The middleware should add the correlation ID header name to exposed headers. The middleware should not overwrite other values, but should append to it. """ + @app.get('/access-control-expose-headers') async def access_control_view() -> Response: return Response(status_code=204, headers={'Access-Control-Expose-Headers': 'test1, test2'}) @@ -117,6 +124,7 @@ async def test_multiple_headers_same_name(caplog, app): """ The middleware should not change the headers that were set in the response and return all of them as it is. """ + @app.get('/multiple_headers_same_name') async def multiple_headers_response() -> Response: response = Response(status_code=204) From 1883b31b0b115b2e6706c03f9bf94fadfeebca7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sondre=20Lilleb=C3=B8=20Gundersen?= Date: Mon, 16 May 2022 09:26:21 +0200 Subject: [PATCH 6/8] fix: Remove logger argument --- asgi_correlation_id/middleware.py | 5 ++++- tests/test_middleware.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/asgi_correlation_id/middleware.py b/asgi_correlation_id/middleware.py index 8bfe46c..c8ba187 100644 --- a/asgi_correlation_id/middleware.py +++ b/asgi_correlation_id/middleware.py @@ -24,6 +24,9 @@ def is_valid_uuid4(uuid_: str) -> bool: return False +FAILED_VALIDATION_MESSAGE = 'Generated new request ID (%s), since request header value failed validation' + + @dataclass class CorrelationIdMiddleware: app: 'ASGIApp' @@ -54,8 +57,8 @@ async def __call__(self, scope: 'Scope', receive: 'Receive', send: 'Send') -> No id_value = self.generator() elif self.validator and not self.validator(header_value): # Also generate a request ID if one was found, but it was deemed invalid - logger.warning('Generating new request ID, since header value \'%s\' is invalid', header_value) id_value = self.generator() + logger.warning(FAILED_VALIDATION_MESSAGE, header_value) else: # Otherwise, use the found request ID id_value = header_value diff --git a/tests/test_middleware.py b/tests/test_middleware.py index 7762c5f..99e979e 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -7,6 +7,7 @@ from httpx import AsyncClient from starlette.testclient import TestClient +from asgi_correlation_id.middleware import FAILED_VALIDATION_MESSAGE from tests.conftest import ( TRANSFORMER_VALUE, default_app, @@ -81,7 +82,7 @@ async def test_view() -> dict: async with AsyncClient(app=app, base_url='http://test') as client: response = await client.get('test', headers={'X-Request-ID': value}) assert response.headers['X-Request-ID'] != value - assert caplog.messages[0] == f"Generating new request ID, since header value '{value}' is invalid" + assert caplog.messages[0] == FAILED_VALIDATION_MESSAGE.replace('%s', value) @pytest.mark.parametrize('app', apps) From 8b30e44caf8e946990192925df7b09dc6308412a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sondre=20Lilleb=C3=B8=20Gundersen?= Date: Mon, 16 May 2022 09:41:28 +0200 Subject: [PATCH 7/8] chore: Update pytest-asyncio mode --- setup.cfg | 1 + tests/conftest.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 8c294fb..2043d0b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,6 @@ [tool:pytest] testpaths = tests +asyncio_mode = auto [flake8] max-line-length = 120 diff --git a/tests/conftest.py b/tests/conftest.py index 6e80d7e..281dbeb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ from logging.config import dictConfig import pytest +import pytest_asyncio from fastapi import FastAPI from httpx import AsyncClient from starlette.middleware import Middleware @@ -61,7 +62,7 @@ def event_loop(): loop.close() -@pytest.fixture(scope='module') +@pytest_asyncio.fixture(scope='module') async def client() -> AsyncClient: async with AsyncClient(app=default_app, base_url='http://test') as client: yield client From 24b553020f67b36417cfb5545beed40b1f79dfce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sondre=20Lilleb=C3=B8=20Gundersen?= Date: Mon, 16 May 2022 09:42:01 +0200 Subject: [PATCH 8/8] chore: Bump version from 2.0.0 to 3.0.0a1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a568a25..a6c8fe0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "asgi-correlation-id" -version = "2.0.0" +version = "3.0.0a1" description = "Middleware correlating project logs to individual requests" authors = ["Sondre Lillebø Gundersen "] maintainers = ["Jonas Krüger Svensson "]