Add a unique constraint for the repo url and source graph repo id

This commit is contained in:
Vladyslav Fedoriuk 2023-08-15 14:30:44 +02:00
parent cca18fbd33
commit 102de55b12
8 changed files with 68 additions and 25 deletions

View File

@ -19,7 +19,7 @@ from collections.abc import AsyncGenerator
from pathlib import PurePath from pathlib import PurePath
from typing import Final from typing import Final
from sqlalchemy import BigInteger, ForeignKey, String, Text from sqlalchemy import BigInteger, ForeignKey, MetaData, String, Text, UniqueConstraint
from sqlalchemy.ext.asyncio import ( from sqlalchemy.ext.asyncio import (
AsyncAttrs, AsyncAttrs,
AsyncEngine, AsyncEngine,
@ -27,7 +27,12 @@ from sqlalchemy.ext.asyncio import (
async_sessionmaker, async_sessionmaker,
create_async_engine, create_async_engine,
) )
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship from sqlalchemy.orm import (
Mapped,
declarative_base,
mapped_column,
relationship,
)
DB_PATH: Final[PurePath] = PurePath(__file__).parent.parent / "db.sqlite3" DB_PATH: Final[PurePath] = PurePath(__file__).parent.parent / "db.sqlite3"
@ -39,6 +44,16 @@ async_session_maker: Final[async_sessionmaker[AsyncSession]] = async_sessionmake
engine, expire_on_commit=False, autoflush=False, autocommit=False engine, expire_on_commit=False, autoflush=False, autocommit=False
) )
metadata = MetaData(
naming_convention={
"ix": "ix_%(table_name)s_%(column_0_N_name)s ",
"uq": "uq_%(table_name)s_%(column_0_N_name)s ",
"ck": "ck_%(table_name)s_%(constraint_name)s ",
"fk": "fk_%(table_name)s_%(column_0_N_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s",
}
)
async def get_async_session() -> AsyncGenerator[AsyncSession, None]: async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
"""Get an async session.""" """Get an async session."""
@ -46,10 +61,7 @@ async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
yield session yield session
class Base(AsyncAttrs, DeclarativeBase): Base = declarative_base(metadata=metadata, cls=AsyncAttrs)
"""Declarative base for database models."""
pass
class Repo(Base): class Repo(Base):
@ -66,6 +78,7 @@ class Repo(Base):
dependencies: Mapped[list["Dependency"]] = relationship( dependencies: Mapped[list["Dependency"]] = relationship(
"Dependency", secondary="repo_dependency", back_populates="repos" "Dependency", secondary="repo_dependency", back_populates="repos"
) )
__table_args__ = (UniqueConstraint("url", "source_graph_repo_id"),)
class Dependency(Base): class Dependency(Base):

View File

@ -21,7 +21,7 @@ async def create_or_update_repos_from_source_graph_repos_data(
""" """
insert_statement = sqlalchemy.dialects.sqlite.insert(database.Repo) insert_statement = sqlalchemy.dialects.sqlite.insert(database.Repo)
update_statement = insert_statement.on_conflict_do_update( update_statement = insert_statement.on_conflict_do_update(
index_elements=[database.Repo.url], index_elements=[database.Repo.url, database.Repo.source_graph_repo_id],
set_={ set_={
"url": insert_statement.excluded.url, "url": insert_statement.excluded.url,
"description": insert_statement.excluded.description, "description": insert_statement.excluded.description,

Binary file not shown.

View File

@ -1,15 +1,15 @@
"""Create Repo, Dependency and RepoDependency tables """Set up the database
Revision ID: 0232d84a5aea Revision ID: 90eb9d1f9267
Revises: Revises:
Create Date: 2023-08-02 22:14:12.910175 Create Date: 2023-08-15 14:13:30.562069
""" """
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = "0232d84a5aea" revision = "90eb9d1f9267"
down_revision = None down_revision = None
branch_labels = None branch_labels = None
depends_on = None depends_on = None
@ -21,8 +21,8 @@ def upgrade() -> None:
"dependency", "dependency",
sa.Column("id", sa.Integer(), nullable=False), sa.Column("id", sa.Integer(), nullable=False),
sa.Column("name", sa.String(length=255), nullable=False), sa.Column("name", sa.String(length=255), nullable=False),
sa.PrimaryKeyConstraint("id"), sa.PrimaryKeyConstraint("id", name=op.f("pk_dependency")),
sa.UniqueConstraint("name"), sa.UniqueConstraint("name", name=op.f("uq_dependency_name ")),
) )
op.create_table( op.create_table(
"repo", "repo",
@ -31,19 +31,36 @@ def upgrade() -> None:
sa.Column("description", sa.Text(), nullable=False), sa.Column("description", sa.Text(), nullable=False),
sa.Column("stars", sa.BigInteger(), nullable=False), sa.Column("stars", sa.BigInteger(), nullable=False),
sa.Column("source_graph_repo_id", sa.BigInteger(), nullable=True), sa.Column("source_graph_repo_id", sa.BigInteger(), nullable=True),
sa.PrimaryKeyConstraint("id"), sa.PrimaryKeyConstraint("id", name=op.f("pk_repo")),
sa.UniqueConstraint("source_graph_repo_id"), sa.UniqueConstraint(
sa.UniqueConstraint("url"), "source_graph_repo_id", name=op.f("uq_repo_source_graph_repo_id ")
),
sa.UniqueConstraint(
"url",
"source_graph_repo_id",
name=op.f("uq_repo_url_source_graph_repo_id "),
),
sa.UniqueConstraint("url", name=op.f("uq_repo_url ")),
) )
op.create_table( op.create_table(
"repo_dependency", "repo_dependency",
sa.Column("repo_id", sa.Integer(), nullable=False), sa.Column("repo_id", sa.Integer(), nullable=False),
sa.Column("dependency_id", sa.Integer(), nullable=False), sa.Column("dependency_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint( sa.ForeignKeyConstraint(
["dependency_id"], ["dependency.id"], ondelete="CASCADE" ["dependency_id"],
["dependency.id"],
name=op.f("fk_repo_dependency_dependency_id_dependency"),
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["repo_id"],
["repo.id"],
name=op.f("fk_repo_dependency_repo_id_repo"),
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint(
"repo_id", "dependency_id", name=op.f("pk_repo_dependency")
), ),
sa.ForeignKeyConstraint(["repo_id"], ["repo.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("repo_id", "dependency_id"),
) )
# ### end Alembic commands ### # ### end Alembic commands ###

View File

@ -16,7 +16,7 @@ dependencies = [
"alembic", "alembic",
"fastapi[all]", "fastapi[all]",
"httpx-sse", "httpx-sse",
"sqlalchemy[asyncio]", "sqlalchemy[asyncio,mypy]",
"stamina", "stamina",
"third-party-imports", "third-party-imports",
"typer[all]", "typer[all]",
@ -114,7 +114,8 @@ convention = "numpy"
[tool.mypy] [tool.mypy]
plugins = [ plugins = [
"pydantic.mypy" "pydantic.mypy",
"sqlalchemy.ext.mypy.plugin",
] ]
strict = true strict = true
exclude = [ exclude = [

View File

@ -66,6 +66,10 @@ markupsafe==2.1.3
# mako # mako
mdurl==0.1.2 mdurl==0.1.2
# via markdown-it-py # via markdown-it-py
mypy==1.4.1
# via sqlalchemy
mypy-extensions==1.0.0
# via mypy
orjson==3.9.2 orjson==3.9.2
# via fastapi # via fastapi
pydantic==2.1.1 pydantic==2.1.1
@ -100,7 +104,7 @@ sniffio==1.3.0
# anyio # anyio
# httpcore # httpcore
# httpx # httpx
sqlalchemy[asyncio]==2.0.19 sqlalchemy[asyncio,mypy]==2.0.19
# via # via
# alembic # alembic
# awesome-fastapi-projects (pyproject.toml) # awesome-fastapi-projects (pyproject.toml)
@ -118,6 +122,7 @@ typing-extensions==4.7.1
# via # via
# alembic # alembic
# fastapi # fastapi
# mypy
# pydantic # pydantic
# pydantic-core # pydantic-core
# sqlalchemy # sqlalchemy

View File

@ -95,7 +95,9 @@ matplotlib-inline==0.1.6
mdurl==0.1.2 mdurl==0.1.2
# via markdown-it-py # via markdown-it-py
mypy==1.4.1 mypy==1.4.1
# via awesome-fastapi-projects (pyproject.toml) # via
# awesome-fastapi-projects (pyproject.toml)
# sqlalchemy
mypy-extensions==1.0.0 mypy-extensions==1.0.0
# via # via
# black # black
@ -176,7 +178,7 @@ sniffio==1.3.0
# anyio # anyio
# httpcore # httpcore
# httpx # httpx
sqlalchemy[asyncio]==2.0.19 sqlalchemy[asyncio,mypy]==2.0.19
# via # via
# alembic # alembic
# awesome-fastapi-projects (pyproject.toml) # awesome-fastapi-projects (pyproject.toml)

View File

@ -75,6 +75,10 @@ markupsafe==2.1.3
# mako # mako
mdurl==0.1.2 mdurl==0.1.2
# via markdown-it-py # via markdown-it-py
mypy==1.4.1
# via sqlalchemy
mypy-extensions==1.0.0
# via mypy
orjson==3.9.2 orjson==3.9.2
# via fastapi # via fastapi
packaging==23.1 packaging==23.1
@ -133,7 +137,7 @@ sniffio==1.3.0
# anyio # anyio
# httpcore # httpcore
# httpx # httpx
sqlalchemy[asyncio]==2.0.19 sqlalchemy[asyncio,mypy]==2.0.19
# via # via
# alembic # alembic
# awesome-fastapi-projects (pyproject.toml) # awesome-fastapi-projects (pyproject.toml)
@ -151,6 +155,7 @@ typing-extensions==4.7.1
# via # via
# alembic # alembic
# fastapi # fastapi
# mypy
# polyfactory # polyfactory
# pydantic # pydantic
# pydantic-core # pydantic-core