From e5c3000d3e8adc73afb1705b0d6f9b53fc6204d6 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Sat, 26 Oct 2024 04:42:57 +0200 Subject: [PATCH 1/2] OpenAPI: Make extension optional Install with: pip install 'responder[openapi]' --- .github/workflows/test.yaml | 31 +++++++++++++++++++++++++++---- README.md | 12 ++++++++++-- docs/source/tour.rst | 8 +++++++- responder/api.py | 3 ++- setup.py | 2 ++ tests/conftest.py | 10 ++++++++++ tests/test_responder.py | 12 ++++++------ 7 files changed, 64 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index acc7db31..22d11cda 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -12,8 +12,9 @@ concurrency: cancel-in-progress: true jobs: - test: - name: "Python ${{ matrix.python-version }} on ${{ matrix.os }}" + + test-full: + name: "Full: Python ${{ matrix.python-version }} on ${{ matrix.os }}" runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -65,11 +66,33 @@ jobs: - name: Install package and run software tests (Python 3.6) if: matrix.python-version == '3.6' run: | - pip install '.[graphql,develop,test]' + pip install '.[full,develop,test]' poe test - name: Install and completely validate package (Python >=3.6) if: matrix.python-version != '3.6' run: | - uv pip install '.[graphql,develop,test]' --system + uv pip install '.[full,develop,test]' --system + poe check + + + test-minimal: + name: "Minimal: Python ${{ matrix.python-version }} on ${{ matrix.os }}" + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest"] + python-version: ["3.13"] + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - uses: yezz123/setup-uv@v4 + + - name: Install and completely validate package + run: | + uv pip install '.[develop,test]' --system poe check diff --git a/README.md b/README.md index 4851e981..a5f3773a 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,19 @@ for more details on features available in Responder. Install the most recent stable release: - uv pip install --upgrade 'responder' + pip install --upgrade 'responder' + +Include support for all extensions and interfaces: + + pip install --upgrade 'responder[full]' + +Individual optional installation extras are: + + graphql, openapi Or, install directly from the repository: - uv pip install --upgrade 'responder @ git+https://github.com/kennethreitz/responder.git' + pip install 'responder[full] @ git+https://github.com/kennethreitz/responder.git' Responder supports **Python 3.6+**. diff --git a/docs/source/tour.rst b/docs/source/tour.rst index 256e7bf8..792ac927 100644 --- a/docs/source/tour.rst +++ b/docs/source/tour.rst @@ -34,6 +34,10 @@ Here, you can spawn off a background thread to run any function, out-of-request: GraphQL ------- +responder supports GraphQL:: + + pip install 'responder[graphql]' + Serve a GraphQL API:: @@ -58,7 +62,9 @@ You can make use of Responder's Request and Response objects in your GraphQL res OpenAPI Schema Support ---------------------- -Responder comes with built-in support for OpenAPI / marshmallow +Responder comes with built-in support for OpenAPI / marshmallow:: + + pip install 'responder[openapi]' New in Responder `1.4.0`:: diff --git a/responder/api.py b/responder/api.py index 4311c8f9..62beeb3d 100644 --- a/responder/api.py +++ b/responder/api.py @@ -13,7 +13,6 @@ from . import status_codes from .background import BackgroundQueue -from .ext.schema import OpenAPISchema as OpenAPISchema from .formats import get_formats from .routes import Router from .staticfiles import StaticFiles @@ -110,6 +109,8 @@ def __init__( self.add_middleware(SessionMiddleware, secret_key=self.secret_key) if openapi or docs_route: + from .ext.schema import OpenAPISchema + self.openapi = OpenAPISchema( app=self, title=title, diff --git a/setup.py b/setup.py index 10755132..17f5f902 100644 --- a/setup.py +++ b/setup.py @@ -132,7 +132,9 @@ def run(self): "sphinx-design-elements", "sphinxext.opengraph", ], + "full": ["responder[graphql,openapi]"], "graphql": ["graphene"], + "openapi": ["apispec>=1.0.0b1"], "release": ["build", "twine"], "test": ["flask", "mypy", "pytest", "pytest-cov", "pytest-mock"], }, diff --git a/tests/conftest.py b/tests/conftest.py index 74addee8..953a352b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -53,3 +53,13 @@ def template_path(tmpdir): template_file = tmpdir.mkdir("static").join(template_name) template_file.write("{{ var }}") return template_file + + +@pytest.fixture +def needs_openapi() -> None: + try: + import apispec + + _ = apispec.APISpec + except ImportError as ex: + raise pytest.skip("apispec package not installed") from ex diff --git a/tests/test_responder.py b/tests/test_responder.py index c125da2b..730a9f9c 100644 --- a/tests/test_responder.py +++ b/tests/test_responder.py @@ -322,11 +322,11 @@ def route(req, resp): assert yaml.safe_load(r.content) == dump -def test_schema_generation_explicit(): +def test_schema_generation_explicit(needs_openapi): import marshmallow import responder - from responder.ext.schema import OpenAPISchema as OpenAPISchema + from responder.ext.schema import OpenAPISchema api = responder.API() @@ -357,7 +357,7 @@ def route(req, resp): assert dump["openapi"] == "3.0.2" -def test_schema_generation(): +def test_schema_generation(needs_openapi): from marshmallow import Schema, fields import responder @@ -389,11 +389,11 @@ def route(req, resp): assert dump["openapi"] == "3.0.2" -def test_documentation_explicit(): +def test_documentation_explicit(needs_openapi): import marshmallow import responder - from responder.ext.schema import OpenAPISchema as OpenAPISchema + from responder.ext.schema import OpenAPISchema description = "This is a sample server for a pet store." terms_of_service = "http://example.com/terms/" @@ -443,7 +443,7 @@ def route(req, resp): assert "html" in r.text -def test_documentation(): +def test_documentation(needs_openapi): from marshmallow import Schema, fields import responder From 5078202707209c3fb636e9f0abe71abf2898a99b Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Sat, 26 Oct 2024 05:12:13 +0200 Subject: [PATCH 2/2] OpenAPI: Refactor module to `responder.ext.openapi` It has been `responder.ext.schema` before. --- README.md | 3 ++- docs/source/tour.rst | 19 ++++++++++++------- responder/api.py | 8 +++++++- responder/ext/{schema => openapi}/__init__.py | 0 .../{schema => openapi}/docs/elements.html | 0 .../ext/{schema => openapi}/docs/rapidoc.html | 0 .../ext/{schema => openapi}/docs/redoc.html | 0 .../{schema => openapi}/docs/swagger_ui.html | 0 setup.py | 2 +- tests/test_responder.py | 4 ++-- 10 files changed, 24 insertions(+), 12 deletions(-) rename responder/ext/{schema => openapi}/__init__.py (100%) rename responder/ext/{schema => openapi}/docs/elements.html (100%) rename responder/ext/{schema => openapi}/docs/rapidoc.html (100%) rename responder/ext/{schema => openapi}/docs/redoc.html (100%) rename responder/ext/{schema => openapi}/docs/swagger_ui.html (100%) diff --git a/README.md b/README.md index a5f3773a..d7565e32 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,8 @@ Include support for all extensions and interfaces: Individual optional installation extras are: - graphql, openapi +- graphql: Adds GraphQL support via Graphene +- openapi: Adds OpenAPI/Swagger interface support Or, install directly from the repository: diff --git a/docs/source/tour.rst b/docs/source/tour.rst index 792ac927..2548ff66 100644 --- a/docs/source/tour.rst +++ b/docs/source/tour.rst @@ -66,10 +66,16 @@ Responder comes with built-in support for OpenAPI / marshmallow:: pip install 'responder[openapi]' -New in Responder `1.4.0`:: +.. note:: + + If you're upgrading from a previous version, note that the OpenAPI module + has been renamed from ``responder.ext.schema`` to ``responder.ext.openapi``. + Update your imports accordingly. + +New in Responder 1.4.0:: import responder - from responder.ext.schema import Schema as OpenAPISchema + from responder.ext.openapi import OpenAPISchema from marshmallow import Schema, fields contact = { @@ -200,12 +206,11 @@ Responder can automatically supply API Documentation for you. Using the example The new and recommended way:: - ... - from responder.ext.schema import Schema - ... + from responder.ext.openapi import OpenAPISchema + api = responder.API() - schema = Schema( + schema = OpenAPISchema( app=api, title="Web Service", version="1.0", @@ -220,7 +225,7 @@ The new and recommended way:: ) -The old way :: +The old way:: api = responder.API( title="Web Service", diff --git a/responder/api.py b/responder/api.py index 62beeb3d..81bb831b 100644 --- a/responder/api.py +++ b/responder/api.py @@ -109,7 +109,13 @@ def __init__( self.add_middleware(SessionMiddleware, secret_key=self.secret_key) if openapi or docs_route: - from .ext.schema import OpenAPISchema + try: + from .ext.openapi import OpenAPISchema + except ImportError as ex: + raise ImportError( + "The dependencies for the OpenAPI extension are not installed. " + "Install them using: pip install 'responder[openapi]'" + ) from ex self.openapi = OpenAPISchema( app=self, diff --git a/responder/ext/schema/__init__.py b/responder/ext/openapi/__init__.py similarity index 100% rename from responder/ext/schema/__init__.py rename to responder/ext/openapi/__init__.py diff --git a/responder/ext/schema/docs/elements.html b/responder/ext/openapi/docs/elements.html similarity index 100% rename from responder/ext/schema/docs/elements.html rename to responder/ext/openapi/docs/elements.html diff --git a/responder/ext/schema/docs/rapidoc.html b/responder/ext/openapi/docs/rapidoc.html similarity index 100% rename from responder/ext/schema/docs/rapidoc.html rename to responder/ext/openapi/docs/rapidoc.html diff --git a/responder/ext/schema/docs/redoc.html b/responder/ext/openapi/docs/redoc.html similarity index 100% rename from responder/ext/schema/docs/redoc.html rename to responder/ext/openapi/docs/redoc.html diff --git a/responder/ext/schema/docs/swagger_ui.html b/responder/ext/openapi/docs/swagger_ui.html similarity index 100% rename from responder/ext/schema/docs/swagger_ui.html rename to responder/ext/openapi/docs/swagger_ui.html diff --git a/setup.py b/setup.py index 17f5f902..c9add646 100644 --- a/setup.py +++ b/setup.py @@ -134,7 +134,7 @@ def run(self): ], "full": ["responder[graphql,openapi]"], "graphql": ["graphene"], - "openapi": ["apispec>=1.0.0b1"], + "openapi": ["apispec>=1.0.0"], "release": ["build", "twine"], "test": ["flask", "mypy", "pytest", "pytest-cov", "pytest-mock"], }, diff --git a/tests/test_responder.py b/tests/test_responder.py index 730a9f9c..0ce99319 100644 --- a/tests/test_responder.py +++ b/tests/test_responder.py @@ -326,7 +326,7 @@ def test_schema_generation_explicit(needs_openapi): import marshmallow import responder - from responder.ext.schema import OpenAPISchema + from responder.ext.openapi import OpenAPISchema api = responder.API() @@ -393,7 +393,7 @@ def test_documentation_explicit(needs_openapi): import marshmallow import responder - from responder.ext.schema import OpenAPISchema + from responder.ext.openapi import OpenAPISchema description = "This is a sample server for a pet store." terms_of_service = "http://example.com/terms/"