Skip to content

Commit

Permalink
feat(init): add init app
Browse files Browse the repository at this point in the history
  • Loading branch information
wangxin688 committed Jan 14, 2024
1 parent 249e90b commit ade87cc
Show file tree
Hide file tree
Showing 15 changed files with 422 additions and 44 deletions.
2 changes: 1 addition & 1 deletion pre-commit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ set -ex
rye run black src
rye run black tests
rye run ruff src --fix
rye run ruff src --fix
rye run ruff tests --fix
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ dev-dependencies = [
"pytest-cov>=4.1.0",
"pytest-asyncio>=0.23.3",
"black>=23.12.1",
"mypy>=1.8.0",
]

[tool.hatch.metadata]
Expand All @@ -52,14 +53,15 @@ target-version = "py311"

[tool.ruff.lint]
select = ["ALL"]
ignore = ["D", "G002", "DTZ003", "ANN401", "ANN101", "ANN102", "EM101", "PD901", "COM812", "ISC001", "FBT001"]
ignore = ["D", "G002", "DTZ003", "ANN401", "ANN101", "ANN102", "EM101", "PD901", "COM812", "ISC001", "FBT"]
fixable = ["ALL"]


[tool.ruff.extend-per-file-ignores]
"env.py" = ["INP001", "I001", "ERA001"]
"tests/*.py" = ["S101"]
"models.py" = ["A003"]
"mixins.py" = ["A003"]
"exception_handlers.py" = ["ARG001"]

[tool.ruff.flake8-bugbear]
Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
alembic==1.13.1
annotated-types==0.6.0
anyio==4.2.0
babel==2.14.0
black==23.12.1
certifi==2023.11.17
cfgv==3.4.0
Expand All @@ -27,6 +26,7 @@ idna==3.6
iniconfig==2.0.0
mako==1.3.0
markupsafe==2.1.3
mypy==1.8.0
mypy-extensions==1.0.0
nodeenv==1.8.0
numpy==1.26.3
Expand Down
1 change: 0 additions & 1 deletion requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
alembic==1.13.1
annotated-types==0.6.0
anyio==4.2.0
babel==2.14.0
certifi==2023.11.17
click==8.1.7
fastapi==0.108.0
Expand Down
13 changes: 7 additions & 6 deletions src/_types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from enum import Enum
from typing import Annotated, Generic, Literal, ParamSpec, TypeVar
from uuid import UUID
from typing import Annotated, Generic, Literal, ParamSpec, TypeAlias, TypeVar

import pydantic
from fastapi import Query
Expand All @@ -12,6 +11,8 @@
T = TypeVar("T")
P = ParamSpec("P")

Order: TypeAlias = Literal["descend", "ascend"]

StrList = Annotated[str | list[str], BeforeValidator(items_to_list)]
IntList = Annotated[int | list[int], BeforeValidator(items_to_list)]
MacAddress = Annotated[str, BeforeValidator(mac_address_validator)]
Expand Down Expand Up @@ -53,17 +54,17 @@ class QueryParams(BaseModel):
limit: int | None = Query(default=20, ge=0, le=1000, description="Number of results to return per request.")
offset: int | None = Query(default=0, ge=0, description="The initial index from which return the results.")
q: str | None = Query(default=None, description="Search for results.")
id: list[UUID] | None = Field(Query(default=[], description="request object unique ID"))
id: list[int] | None = Field(Query(default=[], description="request object unique ID"))
order_by: str | None = Query(default=None, description="Which field to use when order the results")
order: Literal["descend", "ascend"] | None = Query(default="ascend", description="Order by dscend or ascend")
order: Order | None = Query(default="ascend", description="Order by dscend or ascend")


class BatchDelete(BaseModel):
ids: list[UUID]
ids: list[int]


class BatchUpdate(BaseModel):
ids: list[UUID]
ids: list[int]


class I18nField(BaseModel):
Expand Down
20 changes: 9 additions & 11 deletions src/auth/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from datetime import datetime
from typing import ClassVar
from uuid import UUID

from sqlalchemy import DateTime, ForeignKey
from sqlalchemy.dialects.postgresql import JSON
Expand All @@ -13,22 +12,21 @@

class RolePermission(Base):
__tablename__ = "role_permission"
__multi_tenant__: ClassVar = False
role_id: Mapped[UUID] = mapped_column(ForeignKey("role.id"), primary_key=True)
permission_id: Mapped[UUID] = mapped_column(ForeignKey("permission.id"), primary_key=True)
role_id: Mapped[int] = mapped_column(ForeignKey("role.id"), primary_key=True)
permission_id: Mapped[int] = mapped_column(ForeignKey("permission.id"), primary_key=True)


class Role(Base):
__tablename__ = "role"
__search_fields__: ClassVar = {"name"}
id: Mapped[_types.uuid_pk]
id: Mapped[_types.int_pk]
name: Mapped[str]
permission: Mapped[list["Permission"]] = relationship(secondary=RolePermission, backref="role")


class Permission(Base):
__tablename__ = "permission"
id: Mapped[_types.uuid_pk]
id: Mapped[_types.int_pk]
name: Mapped[str]
url: Mapped[str]
method: Mapped[str]
Expand All @@ -38,24 +36,24 @@ class Permission(Base):
class Group(Base):
__tablename__ = "group"
__search_fields__: ClassVar = {"name"}
id: Mapped[_types.uuid_pk]
id: Mapped[_types.int_pk]
name: Mapped[str]
role_id: Mapped[UUID] = mapped_column(ForeignKey(Role.id, ondelete="CASCADE"))
role_id: Mapped[int] = mapped_column(ForeignKey(Role.id, ondelete="CASCADE"))
role: Mapped["Role"] = relationship(backref="group", passive_deletes=True)


class User(Base):
__tablename__ = "user"
__search_fields__: ClassVar = {"email", "name", "phone"}
id: Mapped[_types.uuid_pk]
id: Mapped[_types.int_pk]
name: Mapped[str]
email: Mapped[str | None] = mapped_column(unique=True)
phone: Mapped[str | None] = mapped_column(unique=True)
password: Mapped[str]
avatar: Mapped[str | None]
last_login: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
group_id: Mapped[UUID] = mapped_column(ForeignKey(Group.id, ondelete="CASCADE"))
group_id: Mapped[int] = mapped_column(ForeignKey(Group.id, ondelete="CASCADE"))
group: Mapped["Group"] = relationship(backref="user", passive_deletes=True)
role_id: Mapped[UUID] = mapped_column(ForeignKey(Role.id, ondelete="CASCADE"))
role_id: Mapped[int] = mapped_column(ForeignKey(Role.id, ondelete="CASCADE"))
role: Mapped["Role"] = relationship(backref="user", passive_deletes=True)
auth_info: Mapped[dict] = mapped_column(MutableDict.as_mutable(JSON))
2 changes: 1 addition & 1 deletion src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=f"{PROJECT_DIR}/.env", case_sensitive=True, extra="allow")


settings = Settings()
settings = Settings() # type: ignore # noqa: PGH003
1 change: 0 additions & 1 deletion src/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from uuid import UUID

request_id_ctx = ContextVar[str | None] = ContextVar("x-request-id", default=None)
tenant_ctx = ContextVar[UUID | None] = ContextVar("x-tenant-id", default=None)
auth_user_ctx = ContextVar[UUID | None] = ContextVar("x-auth-user", default=None)
locale_ctx = ContextVar[UUID | None] = ContextVar("Accept-Language", default="en")
orm_diff_ctx = ContextVar[dict | None] = ContextVar("x-orm-diff", default=None)
Expand Down
9 changes: 9 additions & 0 deletions src/db/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from src.auth.models import Group, Permission, Role, RolePermission, User # noqa: F401
from src.db.base import Base


def orm_by_table_name(table_name: str) -> type[Base] | None:
for m in Base.registry.mappers:
if getattr(m.class_, "__tablename__", None) == table_name:
return m.class_
return None
4 changes: 2 additions & 2 deletions src/db/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from datetime import date, datetime
from typing import Annotated, Any

from sqlalchemy import CHAR, Boolean, Date, DateTime, Dialect, String, func, type_coerce
from sqlalchemy import CHAR, Boolean, Date, DateTime, Dialect, Integer, String, func, type_coerce
from sqlalchemy.dialects.postgresql import BYTEA, UUID
from sqlalchemy.orm import mapped_column
from sqlalchemy.sql import expression
Expand Down Expand Up @@ -63,7 +63,7 @@ def process_result_value(self, value: Any, dialect: Dialect) -> UUID | None: #
return value


uuid_pk = Annotated[uuid.UUID, mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)]
int_pk = Annotated[int, mapped_column(Integer, primary_key=True)]
bool_true = Annotated[bool, mapped_column(Boolean, server_default=expression.true())]
bool_false = Annotated[bool, mapped_column(Boolean, server_default=expression.false())]
datetime_required = Annotated[datetime, mapped_column(DateTime(timezone=True))]
Expand Down
20 changes: 9 additions & 11 deletions src/db/base.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
from typing import ClassVar
from typing import Any, ClassVar
from uuid import UUID

from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, declared_attr, mapped_column
from fastapi.encoders import jsonable_encoder
from sqlalchemy.orm import DeclarativeBase

from src.context import tenant_ctx
from src.db._types import GUID


class Base(DeclarativeBase):
__multi_tenant__: bool = True
__visible_name__: ClassVar = {}
__search_fields__: ClassVar = set()
type_annotation_map: ClassVar = {UUID: GUID}

@declared_attr
@classmethod
def tenant_id(cls) -> Mapped[UUID]:
if not cls.__multi_tenant__:
return None
return mapped_column(UUID, ForeignKey("tenant.id", ondelete="CASCADE"), index=True, default=tenant_ctx.get)
def dict(self, exclude: set[str] | None = None, native_dict: bool = False) -> dict[str, Any]:
"""Return dict representation of model."""
if not native_dict:
return jsonable_encoder(self, exclude=exclude)
return {c.name: getattr(self, c.name) for c in self.__table__.columns if c.name not in exclude}
Loading

0 comments on commit ade87cc

Please sign in to comment.