Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 73 additions & 73 deletions fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,167 +7,167 @@ authors = []
maintainers = []
readme = "README.md"
dependencies = [
"fastapi>=0.115.6,<1",
"uvicorn[standard]>=0.34.0,<1",
"fastapi >=0.122.0,<1",
"uvicorn[standard] >=0.38.0,<1",
{%- if cookiecutter.gunicorn == "True" %}
"gunicorn>=23.0.0,<24",
"gunicorn >=23.0.0,<24",
{%- endif %}
{%- if cookiecutter.add_users == "True" %}
{%- if cookiecutter.orm == "sqlalchemy" %}
"fastapi-users>=14.0.0,<15",
"httpx-oauth>=0.16.1,<1",
"fastapi-users-db-sqlalchemy>=7,<8",
"fastapi-users >=15.0.1,<16",
"httpx-oauth >=0.16.1,<1",
"fastapi-users-db-sqlalchemy >=7.0.0,<8",
{%- endif %}
{%- endif %}
{%- if cookiecutter.orm == "ormar" %}
"pydantic>=2.5.3,<2.9.0",
"pydantic >=2.5.3,<2.9.0",
{%- else %}
"pydantic>=2.10.4,<3",
"pydantic >=2.12.5,<3",
{%- endif %}
"pydantic-settings>=2.7.0,<3",
"yarl>=1.18.3,<2",
"ujson>=5.10.0,<6",
"pydantic-settings >=2.12.0,<3",
"yarl >=1.22.0,<2",
"ujson >=5.11.0,<6",
{%- if cookiecutter.orm == "piccolo" %}
{%- if cookiecutter.db_info.name == "postgresql" %}
"piccolo[postgres]>=1.22.0,<2",
"piccolo[postgres] >=1.30.0,<2",
{%- elif cookiecutter.db_info.name == "sqlite" %}
"piccolo[sqlite]>=1.22.0,<2",
"piccolo[sqlite] >=1.30.0,<2",
{%- endif %}
{%- endif %}
{%- if cookiecutter.orm == "sqlalchemy" %}
"SQLAlchemy[asyncio]>=2.0.36,<3",
"SQLAlchemy[asyncio] >=2.0.44,<3",
{%- if cookiecutter.enable_migrations == "True" %}
"alembic>=1.14.0,<2",
"alembic >=1.17.2,<2",
{%- endif %}
{%- if cookiecutter.db_info.name == "postgresql" %}
"asyncpg>=0.31.0,<1",
"asyncpg >=0.31.0,<1",
{%- elif cookiecutter.db_info.name == "sqlite" %}
"aiosqlite>=0.20.0,<1",
"aiosqlite >=0.21.0,<1",
{%- elif cookiecutter.db_info.name == "mysql" %}
"aiomysql>=0.2.0,<1",
"mysqlclient>=2.2.6,<3",
"cryptography>=44.0.0,<45",
"aiomysql >=0.3.2,<1",
"mysqlclient >=2.2.7,<3",
"cryptography >=46.0.3,<47",
{%- endif %}
{%- endif %}
{%- if cookiecutter.orm == "tortoise" %}
"tortoise-orm>=0.23.0,<1",
"tortoise-orm >=0.25.1,<1",
{%- if cookiecutter.enable_migrations == "True" %}
"aerich>=0.8.0,<1",
"aerich >=0.9.2,<1",
{%- endif %}
{%- if cookiecutter.db_info.name == "postgresql" %}
"asyncpg>=0.30.0,<1",
"asyncpg >=0.31.0,<1",
{%- elif cookiecutter.db_info.name == "sqlite" %}
"aiosqlite>=0.20.0,<1",
"aiosqlite >=0.21.0,<1",
{%- elif cookiecutter.db_info.name == "mysql" %}
"aiomysql>=0.2.0,<1",
"mysqlclient>=2.2.6,<3",
"cryptography>=44.0.0,<45",
"aiomysql >=0.3.2,<1",
"mysqlclient >=2.2.7,<3",
"cryptography >=46.0.3,<47",
{%- endif %}
{%- endif %}
{%- if cookiecutter.orm == "ormar" %}
"ormar>=0.20.2,<1",
"ormar >=0.20.2,<1",
{%- if cookiecutter.enable_migrations == "True" %}
"alembic>=1.14.0,<2",
"alembic >=1.17.2,<2",
{%- endif %}
{%- if cookiecutter.db_info.name == "postgresql" %}
"asyncpg>=0.30.0,<1",
"psycopg2-binary>=2.9.10,<3",
"asyncpg >=0.31.0,<1",
"psycopg2-binary >=2.9.11,<3",
{%- elif cookiecutter.db_info.name == "sqlite" %}
"aiosqlite>=0.20.0,<1",
"aiosqlite >=0.21.0,<1",
{%- elif cookiecutter.db_info.name == "mysql" %}
"aiomysql>=0.2.0,<1",
"mysqlclient>=2.2.6,<3",
"aiomysql >=0.3.2,<1",
"mysqlclient >=2.2.7,<3",
{%- endif %}
{%- endif %}
{%- if cookiecutter.enable_redis == "True" %}
"redis[hiredis]>=5.2.1,<6",
"redis[hiredis] >=6,<7",
{%- endif %}
{%- if cookiecutter.self_hosted_swagger == 'True' %}
"aiofiles>=24.1.0,<25",
"aiofiles >=25.1.0,<26",
{%- endif %}
{%- if cookiecutter.orm == "psycopg" %}
"psycopg[binary,pool]>=3.2.3,<4",
"psycopg[binary,pool] >=3.2.13,<4",
{%- endif %}
"httptools>=0.6.4,<1",
"httptools >=0.7.1,<1",
{%- if cookiecutter.orm == "beanie" %}
"beanie>=1.28.0,<2",
"beanie >=2.0.1,<3",
{%- else %}
"pymongo>=4.10.1,<5",
"pymongo >=4.15.4,<5",
{%- endif %}
{%- if cookiecutter.api_type == "graphql" %}
"strawberry-graphql[fastapi]>=0.256.1,<1",
"strawberry-graphql[fastapi] >=0.287.0,<1",
{%- endif %}
{%- if cookiecutter.enable_rmq == "True" %}
"aio-pika>=9.5.4,<10",
"aio-pika >=9.5.8,<10",
{%- endif %}
{%- if cookiecutter.prometheus_enabled == "True" %}
"prometheus-client>=0.21.1,<1",
"prometheus-fastapi-instrumentator>=7,<8",
"prometheus-client >=0.23.1,<1",
"prometheus-fastapi-instrumentator >=7.1.0,<8",
{%- endif %}
{%- if cookiecutter.sentry_enabled == "True" %}
"sentry-sdk>=2.19.2,<3",
"sentry-sdk >=2.46.0,<3",
{%- endif %}
{%- if cookiecutter.otlp_enabled == "True" %}
"opentelemetry-api>=1.38.0,<2",
"opentelemetry-sdk>=1.38.0,<2",
"opentelemetry-exporter-otlp>=1.38.0,<2",
"opentelemetry-instrumentation>=0.59b0,<1",
"opentelemetry-instrumentation-fastapi>=0.59b0,<1",
"opentelemetry-api >=1.38.0,<2",
"opentelemetry-sdk >=1.38.0,<2",
"opentelemetry-exporter-otlp >=1.38.0,<2",
"opentelemetry-instrumentation >=0.59b0,<1",
"opentelemetry-instrumentation-fastapi >=0.59b0,<1",
{%- if cookiecutter.enable_redis == "True" %}
"opentelemetry-instrumentation-redis>=0.59b0,<1",
"opentelemetry-instrumentation-redis >=0.59b0,<1",
{%- endif %}
{%- if cookiecutter.db_info.name == "postgresql" and cookiecutter.orm in ["ormar", "tortoise"] %}
"opentelemetry-instrumentation-asyncpg>=0.59b0,<1",
"opentelemetry-instrumentation-asyncpg >=0.59b0,<1",
{%- endif %}
{%- if cookiecutter.orm == "sqlalchemy" %}
"opentelemetry-instrumentation-sqlalchemy>=0.59b0,<1",
"opentelemetry-instrumentation-sqlalchemy >=0.59b0,<1",
{%- endif %}
{%- if cookiecutter.enable_rmq == "True" %}
"opentelemetry-instrumentation-aio-pika>=0.59b0,<1",
"opentelemetry-instrumentation-aio-pika >=0.59b0,<1",
{%- endif %}
{%- endif %}
{%- if cookiecutter.enable_loguru == "True" %}
"loguru>=0.7.3,<1",
"loguru >=0.7.3,<1",
{%- endif %}
{%- if cookiecutter.enable_kafka == "True" %}
"aiokafka>=0.12.0,<1",
"aiokafka >=0.12.0,<1",
{%- endif %}
{%- if cookiecutter.enable_taskiq == "True" %}
"taskiq>=0.11.10,<1",
"taskiq-fastapi>=0.3.3,<1",
"taskiq >=0.12.0,<1",
"taskiq-fastapi >=0.3.6,<1",
{%- if cookiecutter.enable_redis == "True" %}
"taskiq-redis>=1.0.2,<2",
"taskiq-redis >=1.1.2,<2",
{%- endif %}

{%- if cookiecutter.enable_rmq == "True" %}
"taskiq-aio-pika>=0.4.1,<1",
"taskiq-aio-pika >=0.5.0,<1",
{%- endif %}

{%- if (cookiecutter.enable_rmq or cookiecutter.enable_rmq) != "True" %}
"pyzmq>=26.2.0,<27",
"pyzmq >=27.1.0,<28",
{%- endif %}
{%- endif %}
]

[dependency-groups]
dev = [
"pytest>=9,<10",
"ruff>=0.5.0,<1",
"mypy>=1.10.1,<2",
"pre-commit>=3.7.1,<4",
"pytest-cov>=5,<6",
"anyio>=4,<5",
"pytest-env>=1.1.3,<2",
"pytest >=9.0.1,<10",
"ruff >=0.14.6,<1",
"mypy >=1.19.0,<2",
"pre-commit >=4.5.0,<5",
"pytest-cov >=7.0.0,<8",
"anyio >=4.11.0,<5",
"pytest-env >=1.2.0,<2",
{%- if cookiecutter.enable_redis == "True" %}
"fakeredis>=2.23.3,<3",
"fakeredis >=2.32.1,<3",
{%- endif %}
{%- if cookiecutter.orm == "tortoise" %}
"asynctest>=0.13.0,<1",
"nest-asyncio>=1.6.0,<2",
"asynctest >=0.13.0,<1",
"nest-asyncio >=1.6.0,<2",
{%- endif %}
"httpx>=0.27.0,<1",
"httpx >=0.28.1,<1",
{%- if cookiecutter.enable_taskiq == "True" %}
"taskiq[reload]>=0,<1",
"taskiq[reload] >=0.12.0,<1",
{%- endif %}
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@

{%- elif cookiecutter.orm == "beanie" %}
import beanie
from motor.motor_asyncio import AsyncIOMotorClient
from pymongo import AsyncMongoClient

{%- endif %}

Expand Down Expand Up @@ -347,7 +347,7 @@ async def setup_db() -> AsyncGenerator[None, None]:

:yield: nothing.
"""
client = AsyncIOMotorClient(settings.db_url.human_repr()) # type: ignore
client = AsyncMongoClient(settings.db_url.human_repr()) # type: ignore
from {{cookiecutter.project_name}}.db.models import load_all_models
await beanie.init_beanie(
database=client[settings.db_base],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ def _setup_db(app: FastAPI) -> None: # pragma: no cover

{%- if cookiecutter.orm == "beanie" %}
import beanie
from motor.motor_asyncio import AsyncIOMotorClient
from pymongo import AsyncMongoClient
from {{cookiecutter.project_name}}.db.models import load_all_models
async def _setup_db(app: FastAPI) -> None:
client = AsyncIOMotorClient(str(settings.db_url)) # type: ignore
client = AsyncMongoClient(str(settings.db_url)) # type: ignore
app.state.db_client = client
await beanie.init_beanie(
database=client[settings.db_base],
Expand Down
102 changes: 37 additions & 65 deletions scripts/version_bumper.py
Original file line number Diff line number Diff line change
@@ -1,88 +1,60 @@
import argparse
from pathlib import Path
from typing import List, Optional
import re
import requests
from packaging.version import Version

DEP_RE = re.compile(r"\s*\"(?P<package>.+)\s*(?P<constraints>\>\=.+,\<.+)\s*\"")

RAW_VERSION_RE = re.compile(r'(?P<package>.*)\s*=\s*\"(?P<version>[\^\~\>\=\<\!]?[\d\.\-\w]+)\"')
EXPANDED_VER_RE = re.compile(
r'(?P<package>.*)\s*=\s*\{(.*)version\s*=\s*\"(?P<version>[\^\~\>\=\<\!]?[\d\.\-\w]+)\"(.*)\}'
)

def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument(
"file",
type=Path,
)
parser.add_argument(
"--section",
"-s",
type=str,
default="tool.poetry.dependencies",
)
return parser.parse_args()

def get_dependencies(path: Path, section: str) -> List[str]:
read_file = path.read_text()
recording = False
deps = []
for index, line in enumerate(read_file.splitlines(keepends=False)):
if line.startswith('[') and line.strip('[]') != section:
recording = False
continue
if line == f"[{section}]":
recording = True
continue
if line.startswith('python ='):
continue
if line.startswith('{%'):
continue
if recording:
deps.append((index, line))
return deps

def get_new_version(package_name: str) -> Optional[str]:
resp = requests.get(f'https://pypi.org/pypi/{package_name}/json')
if not resp.ok:
return None
def get_new_version(package_name: str) -> Version:
name = package_name.split("[")[0]
print("Fetching new version for", name)
resp = requests.get(f"https://pypi.org/pypi/{package_name}/json")
resp.raise_for_status()
rjson = resp.json()
return rjson['info']["version"]

return Version(rjson["info"]["version"])

def bump_version(dependency: str) -> str:
exp_match = EXPANDED_VER_RE.match(dependency)
raw_match = None
if exp_match:
package = exp_match.group("package").strip()
version = exp_match.group("version").lstrip("^=!~<>")
else:
raw_match = RAW_VERSION_RE.match(dependency)
if raw_match:
package = raw_match.group("package").strip()
version = raw_match.group("version").lstrip("^=!~<>")
if exp_match is None and raw_match is None:
return None

print(f"Checking {package}")
new_version = get_new_version(package)
if new_version is not None and version != new_version:
print(f"Found new version: {new_version}")
return dependency.replace(version, new_version)

return None

def main():
args = parse_args()
deps = get_dependencies(args.file, args.section)
lines = args.file.read_text().splitlines(keepends=False)
for i, dep in deps:
new_version = bump_version(dep)
if new_version:
lines[i] = new_version
args.file.write_text("\n".join(lines))


file: Path = args.file
lines = []
known_versions = {}
with file.open("r") as f:
for line in f:
match = DEP_RE.match(line)
if not match:
lines.append(line)
continue
full_package = match.group("package").strip()
package = full_package.strip().split("[")[0]
if package in known_versions:
print("Using known version for", package)
version = known_versions[package]
else:
version = get_new_version(package)
known_versions[package] = version
upper_version = version.major + 1
constraints = match.group("constraints").strip().split(";")
markers = None
if len(constraints) == 2:
markers = constraints[1]
new_constraints = f">={version},<{upper_version}"
if markers:
new_constraints += f"; {markers}"
lines.append(' "' + full_package + " " + new_constraints + '",\n')
with file.open("w") as f:
f.writelines(lines)

if __name__ == "__main__":
main()