diff --git a/copier.yml b/copier.yml index 01458c4f1..2682ff8b1 100644 --- a/copier.yml +++ b/copier.yml @@ -391,6 +391,16 @@ _tasks: echo "pyrightconfig.json not found" fi when: "{{ is_circuit_python_driver }}" + - command: | + echo "Checking .config/pyrefly.toml..." + if [ -f .config/pyrefly.toml ]; then + if ! grep -q 'firmware/src' .config/pyrefly.toml; then + sed -i 's#search-path = \["../backend/src", "../backend"\]#search-path = ["../backend/src", "../backend", "../backend/src/backend_api/firmware/src"]#' .config/pyrefly.toml + fi + else + echo ".config/pyrefly.toml not found" + fi + when: "{{ is_circuit_python_driver }}" - command: | echo "Checking for .coveragerc in repo root config..." if [ -f .config/.coveragerc ]; then diff --git a/template/.config/vacuum-ignore.yaml.jinja b/template/.config/vacuum-ignore.yaml.jinja index 408adce98..66af2c07a 100644 --- a/template/.config/vacuum-ignore.yaml.jinja +++ b/template/.config/vacuum-ignore.yaml.jinja @@ -7,7 +7,27 @@ owasp-define-error-validation: - "$.paths['/api/shutdown'].get.responses" -{% endraw %}{% if backend_uses_graphql %}{% raw %}# /api/graphql's 200 response body is arbitrary JSON shaped by the caller's GraphQL query/GraphiQL page; +{% endraw %}{% if is_circuit_python_driver %}{% raw %}# A generated driver only ever manages a single physical device connection at a time, so its +# resource paths are intentionally singular ("driver", not "drivers") rather than REST-collection plural. +resources-plural: + - "$.paths['/api/driver/data-from-serial-port']" + - "$.paths['/api/driver/device-info-from-serial-port']" + - "$.paths['/api/driver/device-connection']" + - "$.paths['/api/driver/send-raw-command']" + - "$.paths['/api/driver/measurement-types']" + - "$.paths['/api/driver/{sensor_id}']" + - "$.paths['/api/driver/{sensor_id}/name']" + - "$.paths['/api/driver/{sensor_id}/reset']" + - "$.paths['/api/driver/{sensor_id}/clear-memory']" + - "$.paths['/api/driver/{sensor_id}/measure/export']" + +# no-request-body flags DELETE with a body as non-RESTful. DELETE /device-connection removes a +# device by mDNS service name with no resource ID in the path, so the service name must travel +# in the body. The rule stays enabled for future endpoints; only this one is exempted. +no-request-body: + - "$.paths['/api/driver/device-connection'].delete" + +{% endraw %}{% endif %}{% raw %}{% endraw %}{% if backend_uses_graphql %}{% raw %}# /api/graphql's 200 response body is arbitrary JSON shaped by the caller's GraphQL query/GraphiQL page; # there is no single representative example to attach without misleadingly implying a fixed response shape. response-body-example: - "$.paths['/api/graphql'].get.responses['200']" diff --git a/template/{% if has_backend %}backend{% endif %}/src/backend_api/app_def.py.jinja b/template/{% if has_backend %}backend{% endif %}/src/backend_api/app_def.py.jinja index b10fb5611..1db88f387 100644 --- a/template/{% if has_backend %}backend{% endif %}/src/backend_api/app_def.py.jinja +++ b/template/{% if has_backend %}backend{% endif %}/src/backend_api/app_def.py.jinja @@ -16,14 +16,14 @@ from fastapi.staticfiles import StaticFiles from fastapi_offline import FastAPIOffline from pydantic import Field{% endraw %}{% if has_circuit_python_backend_template_been_instantiated %}{% raw %} from zeroconf.asyncio import AsyncZeroconf{% endraw %}{% endif %}{% raw %} -{% endraw %}{% if has_circuit_python_backend_template_been_instantiated %}{% raw %} + +from .camel_case_model import CamelCaseModel{% endraw %}{% if has_circuit_python_backend_template_been_instantiated %}{% raw %} from .common.bridges import router as bridges_router from .common.mdns import SimpleBrowser from .common.mdns import register_driver from .common.mdns import router as mdns_router from .common.rfc_servers_jinja import get_servers_container from .driver_routes import router as driver_router{% endraw %}{% endif %}{% raw %} -from .camel_case_model import CamelCaseModel from .entrypoint.parser import get_version from .fast_api_exception_handlers import register_exception_handlers{% endraw %}{% if backend_uses_graphql %}{% raw %} from .graphql.schema import schema{% endraw %}{% endif %}{% raw %} @@ -75,7 +75,6 @@ async def lifespan(app: FastAPI): logger.info("Shutting down FastAPI application") {% endraw %}{% endif %}{% raw %} - API_DESCRIPTION = {% endraw %}{{ backend_rest_api_description | tojson }}{% raw %} OPENAPI_APP_SPECIFIC_TAGS: list[dict[str, str]] = [ @@ -86,6 +85,10 @@ OPENAPI_TAGS = [ {"name": "system", "description": "Server health and lifecycle operations."},{% endraw %}{% if backend_uses_graphql %}{% raw %} {"name": "graphql", "description": "GraphQL endpoint"},{% endraw %}{% endif %}{% if has_circuit_python_backend_template_been_instantiated %}{% raw %} {"name": "debug", "description": "Debug and diagnostic operations."}, + { + "name": "driver", + "description": "Operations for reading measurements from and controlling a connected device driver.", + }, {"name": "mDNS", "description": "mDNS service discovery operations."},{% endraw %}{% endif %}{% raw %} *OPENAPI_APP_SPECIFIC_TAGS, ] diff --git a/template/{% if has_backend %}backend{% endif %}/src/backend_api/fast_api_exception_handlers.py b/template/{% if has_backend %}backend{% endif %}/src/backend_api/fast_api_exception_handlers.py index 5d8479532..03b9ec4f9 100644 --- a/template/{% if has_backend %}backend{% endif %}/src/backend_api/fast_api_exception_handlers.py +++ b/template/{% if has_backend %}backend{% endif %}/src/backend_api/fast_api_exception_handlers.py @@ -203,7 +203,9 @@ def custom_openapi(app: FastAPI) -> dict[str, Any]: }, } for schema_name, schema_metadata in generated_schema_metadata.items(): - if schema_name in comps: + if ( + schema_name in comps + ): # pragma: no branch # every app has at least one validating route (even just the health check), so HTTPValidationError/ValidationError are always already in comps by the time this runs for key, value in schema_metadata.items(): _ = comps[schema_name].setdefault(key, value)