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..d7565e32 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,20 @@ 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: Adds GraphQL support via Graphene +- openapi: Adds OpenAPI/Swagger interface support 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..2548ff66 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,12 +62,20 @@ 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]' + +.. note:: -New in Responder `1.4.0`:: + 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 = { @@ -194,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", @@ -214,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 4311c8f9..81bb831b 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,14 @@ def __init__( self.add_middleware(SessionMiddleware, secret_key=self.secret_key) if openapi or docs_route: + 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, title=title, 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 10755132..c9add646 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.0"], "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..0ce99319 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.openapi 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.openapi 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