Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update the FastAPI template for current practices
Browse files Browse the repository at this point in the history
Update for current FastAPI recommendations and warnings:

- Use a lifespan function instead of hooks
- Update to Pydantic v2
- Use Annotated for injected dependencies in route functions

Use Pydantic's support for environment variable prefixes to use
the same prefix for all environment variables used for
configuration, instead of using SAFIR_ for the default options.

Update the Makefile for current best practices:

- Add a make help default target
- Upgrade pip before updating dependencies
- Run pre-commit autoupdate during make update-deps
- Add --allow-unsafe to pip-compile because setuptools is normally
  needed (and we've not had any problems with it in the past)

Remove the dependency update CI job. We've found this is more
annoying than helpful; it's easier to update using make update at
the start of a development cycle.

Switch to Ruff for both linting and reformatting; remove the Black,
isort, and flake8 configurations; and update the Ruff configuration
with more exclusions.

Update the default and minimum version of Python to 3.12.
rra committed Feb 1, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 6fcc926 commit ddba4f1
Showing 22 changed files with 226 additions and 228 deletions.
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-yaml
- id: check-toml
- id: check-yaml
- id: trailing-whitespace

- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
additional_dependencies: [toml]

- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black

- repo: https://github.com/pycqa/flake8
rev: 6.0.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.14
hooks:
- id: flake8
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
2 changes: 1 addition & 1 deletion project_templates/fastapi_safir_app/example/Dockerfile
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@
# - Runs a non-root user.
# - Sets up the entrypoint and port.

FROM python:3.11.1-slim-bullseye as base-image
FROM python:3.12.1-slim-bullseye as base-image

# Update system packages
COPY scripts/install-base-packages.sh .
56 changes: 40 additions & 16 deletions project_templates/fastapi_safir_app/example/Makefile
Original file line number Diff line number Diff line change
@@ -1,27 +1,51 @@
.PHONY: update-deps
update-deps:
pip install --upgrade pip-tools pip setuptools
pip-compile --upgrade --resolver=backtracking --build-isolation --generate-hashes --output-file requirements/main.txt requirements/main.in
pip-compile --upgrade --resolver=backtracking --build-isolation --generate-hashes --output-file requirements/dev.txt requirements/dev.in

# Useful for testing against a Git version of Safir.
.PHONY: update-deps-no-hashes
update-deps-no-hashes:
pip install --upgrade pip-tools pip setuptools
pip-compile --upgrade --resolver=backtracking --build-isolation --allow-unsafe --output-file requirements/main.txt requirements/main.in
pip-compile --upgrade --resolver=backtracking --build-isolation --allow-unsafe --output-file requirements/dev.txt requirements/dev.in
.PHONY: help
help:
@echo "Make targets for Gafaelfawr"
@echo "make init - Set up dev environment"
@echo "make run - Start a local development instance"
@echo "make update - Update pinned dependencies and run make init"
@echo "make update-deps - Update pinned dependencies"
@echo "make update-deps-no-hashes - Pin dependencies without hashes"

.PHONY: init
init:
pip install --upgrade pip
pip install --upgrade pre-commit tox
pip install --editable .
pip install --upgrade -r requirements/main.txt -r requirements/dev.txt
rm -rf .tox
pip install --upgrade pre-commit tox
pre-commit install

.PHONY: update
update: update-deps init

.PHONY: run
run:
tox run -e run

.PHONY: update
update: update-deps init

# The dependencies need --allow-unsafe because kubernetes-asyncio and
# (transitively) pre-commit depends on setuptools, which is normally not
# allowed to appear in a hashed dependency file.
.PHONY: update-deps
update-deps:
pip install --upgrade pip
pip install --upgrade pre-commit
pre-commit autoupdate
pip install --upgrade pip-tools pip setuptools
pip-compile --upgrade --resolver=backtracking --build-isolation \
--allow-unsafe --generate-hashes \
--output-file requirements/main.txt requirements/main.in
pip-compile --upgrade --resolver=backtracking --build-isolation \
--allow-unsafe --generate-hashes \
--output-file requirements/dev.txt requirements/dev.in

# Useful for testing against a Git version of Safir.
.PHONY: update-deps-no-hashes
update-deps-no-hashes:
pip install --upgrade pip-tools pip setuptools
pip-compile --upgrade --resolver=backtracking --build-isolation \
--allow-unsafe \
--output-file requirements/main.txt requirements/main.in
pip-compile --upgrade --resolver=backtracking --build-isolation \
--allow-unsafe \
--output-file requirements/dev.txt requirements/dev.in
73 changes: 31 additions & 42 deletions project_templates/fastapi_safir_app/example/pyproject.toml
Original file line number Diff line number Diff line change
@@ -11,13 +11,13 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Intended Audience :: Developers",
"Natural Language :: English",
"Operating System :: POSIX",
"Typing :: Typed",
]
requires-python = ">=3.11"
requires-python = ">=3.12"
# Use requirements/main.in for runtime dependencies instead.
dependencies = []
dynamic = ["version"]
@@ -35,24 +35,6 @@ build-backend = "setuptools.build_meta"

[tool.setuptools_scm]

[tool.black]
line-length = 79
target-version = ["py311"]
exclude = '''
/(
\.eggs
| \.git
| \.mypy_cache
| \.tox
| \.venv
| _build
| build
| dist
)/
'''
# Use single-quoted strings so TOML treats the string like a Python r-string
# Multi-line strings are implicitly treated by black as regular expressions

[tool.coverage.run]
parallel = true
branch = true
@@ -75,12 +57,6 @@ exclude_lines = [
"if TYPE_CHECKING:",
]

[tool.isort]
profile = "black"
line_length = 79
known_first_party = ["example", "tests"]
skip = ["docs/conf.py"]

[tool.mypy]
disallow_untyped_defs = true
disallow_incomplete_defs = true
@@ -138,51 +114,70 @@ ignore = [
"D104", # don't see the point of documenting every package
"D105", # our style doesn't require docstrings for magic methods
"D106", # Pydantic uses a nested Config class that doesn't warrant docs
"D205", # our documentation style allows a folded first line
"EM101", # justification (duplicate string in traceback) is silly
"EM102", # justification (duplicate string in traceback) is silly
"FBT003", # positional booleans are normal for Pydantic field defaults
"FIX002", # point of a TODO comment is that we're not ready to fix it
"G004", # forbidding logging f-strings is appealing, but not our style
"RET505", # disagree that omitting else always makes code more readable
"PLR0911", # often many returns is clearer and simpler style
"PLR0913", # factory pattern uses constructors with many arguments
"PLR2004", # too aggressive about magic values
"PLW0603", # yes global is discouraged but if needed, it's needed
"S105", # good idea but too many false positives on non-passwords
"S106", # good idea but too many false positives on non-passwords
"S107", # good idea but too many false positives on non-passwords
"S603", # not going to manually mark every subprocess call as reviewed
"S607", # using PATH is not a security vulnerability
"SIM102", # sometimes the formatting of nested if statements is clearer
"SIM117", # sometimes nested with contexts are clearer
"TCH001", # we decided to not maintain separate TYPE_CHECKING blocks
"TCH002", # we decided to not maintain separate TYPE_CHECKING blocks
"TCH003", # we decided to not maintain separate TYPE_CHECKING blocks
"TID252", # if we're going to use relative imports, use them always
"TRY003", # good general advice but lint is way too aggressive
"TRY301", # sometimes raising exceptions inside try is the best flow

# The following settings should be disabled when using ruff format
# per https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
"W191",
"E111",
"E114",
"E117",
"D206",
"D300",
"Q000",
"Q001",
"Q002",
"Q003",
"COM812",
"COM819",
"ISC001",
"ISC002",
]
select = ["ALL"]
target-version = "py311"
target-version = "py312"

[tool.ruff.per-file-ignores]
"src/example/handlers/**" = [
"D103", # FastAPI handlers should not have docstrings
]
"tests/**" = [
"C901", # tests are allowed to be complex, sometimes that's convenient
"D101", # tests don't need docstrings
"D103", # tests don't need docstrings
"PLR0915", # tests are allowed to be long, sometimes that's convenient
"PT012", # way too aggressive about limiting pytest.raises blocks
"S101", # tests should use assert
"S106", # tests are allowed to hard-code dummy passwords
"SLF001", # tests are allowed to access private members
]

[tool.ruff.isort]
known-first-party = ["example", "tests"]
split-on-trailing-comma = false

[tool.ruff.flake8-bugbear]
extend-immutable-calls = [
"fastapi.Form",
"fastapi.Header",
"fastapi.Depends",
"fastapi.Path",
"fastapi.Query",
]

# These are too useful as attributes or methods to allow the conflict with the
# built-in to rule out their use.
[tool.ruff.flake8-builtins]
@@ -199,12 +194,6 @@ builtins-ignorelist = [
fixture-parentheses = false
mark-parentheses = false

[tool.ruff.pep8-naming]
classmethod-decorators = [
"pydantic.root_validator",
"pydantic.validator",
]

[tool.ruff.pydocstyle]
convention = "numpy"

Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ pydantic
pytest
pytest-asyncio
pytest-cov
ruff

# Documentation
scriv
Original file line number Diff line number Diff line change
@@ -13,4 +13,6 @@ starlette
uvicorn[standard]

# Other dependencies.
safir>=3.4.0
pydantic
pydantic-settings
safir>=5
Original file line number Diff line number Diff line change
@@ -2,39 +2,40 @@

from __future__ import annotations

from pydantic import BaseSettings, Field
from pydantic import Field
from pydantic_settings import BaseSettings
from safir.logging import LogLevel, Profile

__all__ = ["Configuration", "config"]
__all__ = ["Config", "config"]


class Configuration(BaseSettings):
class Config(BaseSettings):
"""Configuration for example."""

name: str = Field(
"example",
title="Name of application",
env="SAFIR_NAME",
validation_alias="EXAMPLE_NAME",
)

path_prefix: str = Field(
"/example",
title="URL prefix for application",
env="SAFIR_PATH_PREFIX",
validation_alias="EXAMPLE_PATH_PREFIX",
)

profile: Profile = Field(
Profile.development,
title="Application logging profile",
env="SAFIR_PROFILE",
validation_alias="EXAMPLE_PROFILE",
)

log_level: LogLevel = Field(
LogLevel.INFO,
title="Log level of the application's logger",
env="SAFIR_LOG_LEVEL",
validation_alias="EXAMPLE_LOG_LEVEL",
)


config = Configuration()
config = Config()
"""Configuration for example."""
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Handlers for the app's external root, ``/example/``."""

from typing import Annotated

from fastapi import APIRouter, Depends
from safir.dependencies.logger import logger_dependency
from safir.metadata import get_metadata
@@ -25,7 +27,7 @@
summary="Application metadata",
)
async def get_index(
logger: BoundLogger = Depends(logger_dependency),
logger: Annotated[BoundLogger, Depends(logger_dependency)],
) -> Index:
"""GET ``/example/`` (the app's external root).
19 changes: 14 additions & 5 deletions project_templates/fastapi_safir_app/example/src/example/main.py
Original file line number Diff line number Diff line change
@@ -7,6 +7,8 @@
called.
"""

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from importlib.metadata import metadata, version

from fastapi import FastAPI
@@ -21,6 +23,17 @@
__all__ = ["app", "config"]


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
"""Set up and tear down the application."""
# Any code here will be run when the application starts up.

yield

# Any code here will be run when the application shuts down.
await http_client_dependency.aclose()


configure_logging(
profile=config.profile,
log_level=config.log_level,
@@ -35,6 +48,7 @@
openapi_url=f"/{config.path_prefix}/openapi.json",
docs_url=f"/{config.path_prefix}/docs",
redoc_url=f"/{config.path_prefix}/redoc",
lifespan=lifespan,
)
"""The main FastAPI application for example."""

@@ -44,8 +58,3 @@

# Add middleware.
app.add_middleware(XForwardedMiddleware)


@app.on_event("shutdown")
async def shutdown_event() -> None:
await http_client_dependency.aclose()

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-yaml
- id: check-toml
- id: check-yaml
- id: trailing-whitespace

- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
additional_dependencies: [toml]

- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black

- repo: https://github.com/pycqa/flake8
rev: 6.0.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.14
hooks:
- id: flake8
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@
# - Runs a non-root user.
# - Sets up the entrypoint and port.

FROM python:3.11.1-slim-bullseye as base-image
FROM python:3.12.1-slim-bullseye as base-image

# Update system packages
COPY scripts/install-base-packages.sh .
56 changes: 40 additions & 16 deletions project_templates/fastapi_safir_app/{{cookiecutter.name}}/Makefile
Original file line number Diff line number Diff line change
@@ -1,27 +1,51 @@
.PHONY: update-deps
update-deps:
pip install --upgrade pip-tools pip setuptools
pip-compile --upgrade --resolver=backtracking --build-isolation --generate-hashes --output-file requirements/main.txt requirements/main.in
pip-compile --upgrade --resolver=backtracking --build-isolation --generate-hashes --output-file requirements/dev.txt requirements/dev.in

# Useful for testing against a Git version of Safir.
.PHONY: update-deps-no-hashes
update-deps-no-hashes:
pip install --upgrade pip-tools pip setuptools
pip-compile --upgrade --resolver=backtracking --build-isolation --allow-unsafe --output-file requirements/main.txt requirements/main.in
pip-compile --upgrade --resolver=backtracking --build-isolation --allow-unsafe --output-file requirements/dev.txt requirements/dev.in
.PHONY: help
help:
@echo "Make targets for Gafaelfawr"
@echo "make init - Set up dev environment"
@echo "make run - Start a local development instance"
@echo "make update - Update pinned dependencies and run make init"
@echo "make update-deps - Update pinned dependencies"
@echo "make update-deps-no-hashes - Pin dependencies without hashes"

.PHONY: init
init:
pip install --upgrade pip
pip install --upgrade pre-commit tox
pip install --editable .
pip install --upgrade -r requirements/main.txt -r requirements/dev.txt
rm -rf .tox
pip install --upgrade pre-commit tox
pre-commit install

.PHONY: update
update: update-deps init

.PHONY: run
run:
tox run -e run

.PHONY: update
update: update-deps init

# The dependencies need --allow-unsafe because kubernetes-asyncio and
# (transitively) pre-commit depends on setuptools, which is normally not
# allowed to appear in a hashed dependency file.
.PHONY: update-deps
update-deps:
pip install --upgrade pip
pip install --upgrade pre-commit
pre-commit autoupdate
pip install --upgrade pip-tools pip setuptools
pip-compile --upgrade --resolver=backtracking --build-isolation \
--allow-unsafe --generate-hashes \
--output-file requirements/main.txt requirements/main.in
pip-compile --upgrade --resolver=backtracking --build-isolation \
--allow-unsafe --generate-hashes \
--output-file requirements/dev.txt requirements/dev.in

# Useful for testing against a Git version of Safir.
.PHONY: update-deps-no-hashes
update-deps-no-hashes:
pip install --upgrade pip-tools pip setuptools
pip-compile --upgrade --resolver=backtracking --build-isolation \
--allow-unsafe \
--output-file requirements/main.txt requirements/main.in
pip-compile --upgrade --resolver=backtracking --build-isolation \
--allow-unsafe \
--output-file requirements/dev.txt requirements/dev.in
Original file line number Diff line number Diff line change
@@ -11,13 +11,13 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Intended Audience :: Developers",
"Natural Language :: English",
"Operating System :: POSIX",
"Typing :: Typed",
]
requires-python = ">=3.11"
requires-python = ">=3.12"
# Use requirements/main.in for runtime dependencies instead.
dependencies = []
dynamic = ["version"]
@@ -35,24 +35,6 @@ build-backend = "setuptools.build_meta"

[tool.setuptools_scm]

[tool.black]
line-length = 79
target-version = ["py311"]
exclude = '''
/(
\.eggs
| \.git
| \.mypy_cache
| \.tox
| \.venv
| _build
| build
| dist
)/
'''
# Use single-quoted strings so TOML treats the string like a Python r-string
# Multi-line strings are implicitly treated by black as regular expressions

[tool.coverage.run]
parallel = true
branch = true
@@ -75,12 +57,6 @@ exclude_lines = [
"if TYPE_CHECKING:",
]

[tool.isort]
profile = "black"
line_length = 79
known_first_party = ["{{cookiecutter.module_name}}", "tests"]
skip = ["docs/conf.py"]

[tool.mypy]
disallow_untyped_defs = true
disallow_incomplete_defs = true
@@ -138,51 +114,70 @@ ignore = [
"D104", # don't see the point of documenting every package
"D105", # our style doesn't require docstrings for magic methods
"D106", # Pydantic uses a nested Config class that doesn't warrant docs
"D205", # our documentation style allows a folded first line
"EM101", # justification (duplicate string in traceback) is silly
"EM102", # justification (duplicate string in traceback) is silly
"FBT003", # positional booleans are normal for Pydantic field defaults
"FIX002", # point of a TODO comment is that we're not ready to fix it
"G004", # forbidding logging f-strings is appealing, but not our style
"RET505", # disagree that omitting else always makes code more readable
"PLR0911", # often many returns is clearer and simpler style
"PLR0913", # factory pattern uses constructors with many arguments
"PLR2004", # too aggressive about magic values
"PLW0603", # yes global is discouraged but if needed, it's needed
"S105", # good idea but too many false positives on non-passwords
"S106", # good idea but too many false positives on non-passwords
"S107", # good idea but too many false positives on non-passwords
"S603", # not going to manually mark every subprocess call as reviewed
"S607", # using PATH is not a security vulnerability
"SIM102", # sometimes the formatting of nested if statements is clearer
"SIM117", # sometimes nested with contexts are clearer
"TCH001", # we decided to not maintain separate TYPE_CHECKING blocks
"TCH002", # we decided to not maintain separate TYPE_CHECKING blocks
"TCH003", # we decided to not maintain separate TYPE_CHECKING blocks
"TID252", # if we're going to use relative imports, use them always
"TRY003", # good general advice but lint is way too aggressive
"TRY301", # sometimes raising exceptions inside try is the best flow

# The following settings should be disabled when using ruff format
# per https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
"W191",
"E111",
"E114",
"E117",
"D206",
"D300",
"Q000",
"Q001",
"Q002",
"Q003",
"COM812",
"COM819",
"ISC001",
"ISC002",
]
select = ["ALL"]
target-version = "py311"
target-version = "py312"

[tool.ruff.per-file-ignores]
"src/{{cookiecutter.module_name}}/handlers/**" = [
"D103", # FastAPI handlers should not have docstrings
]
"tests/**" = [
"C901", # tests are allowed to be complex, sometimes that's convenient
"D101", # tests don't need docstrings
"D103", # tests don't need docstrings
"PLR0915", # tests are allowed to be long, sometimes that's convenient
"PT012", # way too aggressive about limiting pytest.raises blocks
"S101", # tests should use assert
"S106", # tests are allowed to hard-code dummy passwords
"SLF001", # tests are allowed to access private members
]

[tool.ruff.isort]
known-first-party = ["{{cookiecutter.module_name}}", "tests"]
split-on-trailing-comma = false

[tool.ruff.flake8-bugbear]
extend-immutable-calls = [
"fastapi.Form",
"fastapi.Header",
"fastapi.Depends",
"fastapi.Path",
"fastapi.Query",
]

# These are too useful as attributes or methods to allow the conflict with the
# built-in to rule out their use.
[tool.ruff.flake8-builtins]
@@ -199,12 +194,6 @@ builtins-ignorelist = [
fixture-parentheses = false
mark-parentheses = false

[tool.ruff.pep8-naming]
classmethod-decorators = [
"pydantic.root_validator",
"pydantic.validator",
]

[tool.ruff.pydocstyle]
convention = "numpy"

Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ pydantic
pytest
pytest-asyncio
pytest-cov
ruff

# Documentation
scriv
Original file line number Diff line number Diff line change
@@ -13,4 +13,6 @@ starlette
uvicorn[standard]

# Other dependencies.
safir>=3.4.0
pydantic
pydantic-settings
safir>=5
Original file line number Diff line number Diff line change
@@ -2,39 +2,40 @@

from __future__ import annotations

from pydantic import BaseSettings, Field
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
from safir.logging import LogLevel, Profile

__all__ = ["Configuration", "config"]
__all__ = ["Config", "config"]


class Configuration(BaseSettings):
class Config(BaseSettings):
"""Configuration for {{ cookiecutter.name }}."""

name: str = Field(
"{{ cookiecutter.name }}",
title="Name of application",
env="SAFIR_NAME",
title="Name of application"
)

path_prefix: str = Field(
"/{{ cookiecutter.name | lower }}",
title="URL prefix for application",
env="SAFIR_PATH_PREFIX",
title="URL prefix for application"
)

profile: Profile = Field(
Profile.development,
title="Application logging profile",
env="SAFIR_PROFILE",
title="Application logging profile"
)

log_level: LogLevel = Field(
LogLevel.INFO,
title="Log level of the application's logger",
env="SAFIR_LOG_LEVEL",
title="Log level of the application's logger"
)

model_config = SettingsConfigDict(
env_prefix="{{ cookiecutter.name | upper }}_", case_sensitive=False
)


config = Configuration()
config = Config()
"""Configuration for {{ cookiecutter.name }}."""
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Handlers for the app's external root, ``/{{ cookiecutter.name | lower }}/``."""

from typing import Annotated

from fastapi import APIRouter, Depends
from safir.dependencies.logger import logger_dependency
from safir.metadata import get_metadata
@@ -25,7 +27,7 @@
summary="Application metadata",
)
async def get_index(
logger: BoundLogger = Depends(logger_dependency),
logger: Annotated[BoundLogger, Depends(logger_dependency)],
) -> Index:
"""GET ``/{{ cookiecutter.name | lower }}/`` (the app's external root).
Original file line number Diff line number Diff line change
@@ -7,6 +7,8 @@
called.
"""

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from importlib.metadata import metadata, version

from fastapi import FastAPI
@@ -21,6 +23,17 @@
__all__ = ["app", "config"]


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
"""Set up and tear down the application."""
# Any code here will be run when the application starts up.

yield

# Any code here will be run when the application shuts down.
await http_client_dependency.aclose()


configure_logging(
profile=config.profile,
log_level=config.log_level,
@@ -35,6 +48,7 @@
openapi_url=f"/{config.path_prefix}/openapi.json",
docs_url=f"/{config.path_prefix}/docs",
redoc_url=f"/{config.path_prefix}/redoc",
lifespan=lifespan,
)
"""The main FastAPI application for {{ cookiecutter.name }}."""

@@ -44,8 +58,3 @@

# Add middleware.
app.add_middleware(XForwardedMiddleware)


@app.on_event("shutdown")
async def shutdown_event() -> None:
await http_client_dependency.aclose()
2 changes: 1 addition & 1 deletion project_templates/technote_md/testn-000/technote.toml
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ series_id = "TESTN"
canonical_url = "https://testn-000.lsst.io"
github_url = "https://github.com/lsst/testn-000"
github_default_branch = "main"
date_created = 2024-01-25T20:38:53Z
date_created = 2024-02-01T01:33:58Z
organization.name = "Vera C. Rubin Observatory"
organization.ror = "https://ror.org/048g3cy84"
license.id = "CC-BY-4.0"
2 changes: 1 addition & 1 deletion project_templates/technote_rst/testn-000/technote.toml
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ series_id = "TESTN"
canonical_url = "https://testn-000.lsst.io"
github_url = "https://github.com/lsst/testn-000"
github_default_branch = "main"
date_created = 2024-01-25T20:38:53Z
date_created = 2024-02-01T01:33:58Z
organization.name = "Vera C. Rubin Observatory"
organization.ror = "https://ror.org/048g3cy84"
license.id = "CC-BY-4.0"

0 comments on commit ddba4f1

Please sign in to comment.