diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml index 457e3e8a..ce05943e 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml @@ -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 %} ] diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/tests/conftest.py b/fastapi_template/template/{{cookiecutter.project_name}}/tests/conftest.py index b0f40272..073f131f 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/tests/conftest.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/tests/conftest.py @@ -68,7 +68,7 @@ {%- elif cookiecutter.orm == "beanie" %} import beanie -from motor.motor_asyncio import AsyncIOMotorClient +from pymongo import AsyncMongoClient {%- endif %} @@ -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], diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifespan.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifespan.py index 75ee3998..e0839669 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifespan.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifespan.py @@ -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], diff --git a/scripts/version_bumper.py b/scripts/version_bumper.py index 312c45d1..85c5a5b9 100644 --- a/scripts/version_bumper.py +++ b/scripts/version_bumper.py @@ -1,13 +1,11 @@ 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.+)\s*(?P\>\=.+,\<.+)\s*\"") -RAW_VERSION_RE = re.compile(r'(?P.*)\s*=\s*\"(?P[\^\~\>\=\<\!]?[\d\.\-\w]+)\"') -EXPANDED_VER_RE = re.compile( - r'(?P.*)\s*=\s*\{(.*)version\s*=\s*\"(?P[\^\~\>\=\<\!]?[\d\.\-\w]+)\"(.*)\}' -) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() @@ -15,74 +13,48 @@ def parse_args() -> argparse.Namespace: "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()