Skip to content

Commit

Permalink
feat(new-arch): update to new arch
Browse files Browse the repository at this point in the history
  • Loading branch information
wangxin688 committed May 15, 2024
1 parent 86eb6b2 commit 7b4cc7a
Show file tree
Hide file tree
Showing 26 changed files with 202 additions and 143 deletions.
32 changes: 31 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ repos:
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-ast
- id: check-case-conflict
- id: mixed-line-ending
- repo: https://github.com/crate-ci/typos
rev: v1.16.22
hooks:
Expand All @@ -25,9 +28,36 @@ repos:
rev: v3.15.0
hooks:
- id: pyupgrade
args: ["--py311-plus"]
args: ["--py312-plus"]
files: ".*"

- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.341
hooks:
- id: pyright
exclude: "tests"
additional_dependencies:
[
fastapi,
faker,
pydantic>=2,
pytest,
sqlalchemy>=2,
]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
hooks:
- id: mypy
exclude: "tests"
additional_dependencies:
[
fastapi,
faker,
pydantic>=2,
pytest,
sqlalchemy>=2,
]

- repo: local
hooks:
- id: pytest-check
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,3 @@ Please notice that this project is still working in progress.
2. Enhance DTO base for better crud support.
3. Release beta version.
4. enhance docs.

33 changes: 28 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ packages = ["src"]
[tool.ruff]
line-length = 120
indent-width = 4
target-version = "py311"
target-version = "py312"

[tool.ruff.lint]
select = ["ALL"]
Expand All @@ -64,9 +64,9 @@ fixable = ["ALL"]
[tool.ruff.extend-per-file-ignores]
"env.py" = ["INP001", "I001", "ERA001"]
"tests/*.py" = ["S101"]
"exceptions.py" = ["ARG001"]
"*exceptions.py" = ["ARG001"]
"models.py" = ["RUF012"]
"api.py" = ["A002", "B008"]
"restapi.py" = ["A002", "B008"]
"deps.py" = ["B008"]
"src/internal/api.py" = ["ARG001"]
"src/auth/schemas.py" = ["N815"] # frontend menu
Expand All @@ -86,14 +86,12 @@ extend-immutable-calls=[
[tool.black]
line-length = 120
preview = true
target_version = ['py311']

[tool.pytest.ini_options]
addopts = "--cov-report term --cov-config=.coveragerc -W ignore:DeprecationWarning --cov=src -v"
xfail_strict = true
asyncio_mode = "auto"
markers = ["pytest.mark.anyio"]
miniversion = "6.0"
testpaths = ["tests"]

[tool.coverage.report]
Expand All @@ -114,3 +112,28 @@ exclude_lines = [

[tool.coverage.run]
concurrency=["thread", "greenlet"]

[tool.mypy]
exclude = "^tools/.*"
plugins = ["pydantic.mypy"]

warn_unused_ignores = true
warn_redundant_casts = true
warn_unused_configs = true
warn_unreachable = true
warn_return_any = true
strict = true
disallow_untyped_decorators = true
disallow_any_generics = false
implicit_reexport = false
show_error_codes = true


[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true
warn_untyped_fields = true

[tool.pyright]
include = ["src", "tests", "examples"]
7 changes: 3 additions & 4 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware.errors import ServerErrorMiddleware

from src.core.config import settings
from src.core.error.auth_exceptions import default_exception_handler, exception_handlers, sentry_ignore_errors
from src.enums import Env
from src.core.config import _Env, settings
from src.core.errors.auth_exceptions import default_exception_handler, exception_handlers, sentry_ignore_errors
from src.libs.redis import cache
from src.openapi import openapi_description
from src.register.middlewares import RequestMiddleware
Expand All @@ -26,7 +25,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: # noqa: ARG001
yield
await pool.disconnect()

if settings.ENV == Env.PROD.name: # noqa: SIM300
if _Env.PROD.name == settings.ENV:
sentry_sdk.init(
dsn=settings.WEB_SENTRY_DSN,
sample_rate=settings.SENTRY_SAMPLE_RATE,
Expand Down
5 changes: 2 additions & 3 deletions src/core/_types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import datetime
from typing import Annotated, Generic, Literal, ParamSpec, TypeAlias, TypedDict, TypeVar
from typing import Annotated, Generic, Literal, ParamSpec, TypedDict, TypeVar

import pydantic
from fastapi import Query
Expand All @@ -12,7 +12,7 @@
P = ParamSpec("P")
R = TypeVar("R")

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

StrList = Annotated[str | list[str], BeforeValidator(items_to_list)]
IntList = Annotated[int | list[int], BeforeValidator(items_to_list)]
Expand Down Expand Up @@ -57,7 +57,6 @@ class ListT(BaseModel, Generic[T]):
results: list[T] | None = None



class AuditTimeQuery(BaseModel):
created_at__lte: datetime
created_at__gte: datetime
Expand Down
6 changes: 4 additions & 2 deletions src/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ class Settings(BaseSettings):
SENTRY_SAMPLE_RATE: float = Field(default=1.0, gt=0.0, le=1.0)
SENTRY_TRACES_SAMPLE_RATE: float | None = Field(default=None, gt=0.0, le=1.0)

SQLALCHEMY_DATABASE_URI: str = Field(default=
"postgresql+asyncpg://demo:91fb8e9e009f5b9ce1854d947e6fe4a3@localhost:5432/demo")
SQLALCHEMY_DATABASE_URI: str = Field(
default="postgresql+asyncpg://demo:91fb8e9e009f5b9ce1854d947e6fe4a3@localhost:5432/demo"
)
DATABASE_POOL_SIZE: int | None = Field(default=50)
DATABASE_POOL_MAX_OVERFLOW: int | None = Field(default=10)
REDIS_DSN: str = Field(default="redis://:cfe1c2c4703abb205d71abdc07cc3f3d@localhost:6379")
Expand All @@ -44,4 +45,5 @@ class Settings(BaseSettings):

model_config = SettingsConfigDict(env_file=f"{PROJECT_DIR}/.env", case_sensitive=True, extra="allow")


settings = Settings()
26 changes: 13 additions & 13 deletions src/core/database/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from pydantic import BaseModel, Field

from src.core.utils._serailization import json_dumps, json_loads
from src.core.utils._serialization import json_dumps, json_loads
from src.core.utils.dataclass import Empty, EmptyType

if TYPE_CHECKING:
Expand All @@ -22,8 +22,7 @@ class EngineConfig(BaseModel):

connect_args: dict[Any, Any] | EmptyType = Field(
default=Empty,
description=
"""A dictionary of arguments which will be passed directly
description="""A dictionary of arguments which will be passed directly
to the DBAPI's ``connect()`` method as keyword arguments""",
)

Expand All @@ -42,7 +41,7 @@ class EngineConfig(BaseModel):
default=Empty,
description="""Optional string name of an isolation level which will be set on all new connections
unconditionally Isolation levels are typically some subset of the string names "SERIALIZABLE",
"REPEATABLE READ", "READ COMMITTED", "READ UNCOMMITTED" and "AUTOCOMMIT" based on backend."""
"REPEATABLE READ", "READ COMMITTED", "READ UNCOMMITTED" and "AUTOCOMMIT" based on backend.""",
)

json_serializer: Callable[[str], Any] = json_dumps
Expand All @@ -53,7 +52,7 @@ class EngineConfig(BaseModel):
default=Empty,
description="""The number of connections to allow in connection pool “overflow”,
that is connections that can be opened above and beyond the pool_size setting,
which defaults to five. This is only used with:class:`QueuePool <sqlalchemy.pool.QueuePool>`."""
which defaults to five. This is only used with:class:`QueuePool <sqlalchemy.pool.QueuePool>`.""",
)

pool_size: int | EmptyType = Field(
Expand All @@ -62,7 +61,7 @@ class EngineConfig(BaseModel):
:class:`QueuePool <sqlalchemy.pool.QueuePool>` as well as
:class:`SingletonThreadPool <sqlalchemy.pool.SingletonThreadPool>`. With
:class:`QueuePool <sqlalchemy.pool.QueuePool>`, a pool_size setting of ``0`` indicates no limit; to disable pooling,
set ``poolclass`` to :class:`NullPool <sqlalchemy.pool.NullPool>` instead."""
set ``poolclass`` to :class:`NullPool <sqlalchemy.pool.NullPool>` instead.""",
)

pool_recycle: int | EmptyType = Field(
Expand All @@ -71,28 +70,29 @@ class EngineConfig(BaseModel):
It defaults to``-1``, or no timeout. For example, setting to ``3600`` means connections will be recycled after
one hour. Note that MySQL in particular will disconnect automatically if no activity is detected on a connection
for eight hours (although this is configurable with the MySQLDB connection itself and the server configuration
as well)."""
as well).""",
)

pool_use_lifo: bool | EmptyType = Field(
default=Empty,
description="""Use LIFO (last-in-first-out) when retrieving connections from :class:`QueuePool <sqlalchemy.pool.
QueuePool>` instead of FIFO (first-in-first-out). Using LIFO, a server-side timeout scheme can reduce the number
of connections used during non-peak periods of use. When planning for server-side timeouts, ensure that a
recycle or pre-ping strategy is in use to gracefully handle stale connections."""
recycle or pre-ping strategy is in use to gracefully handle stale connections.""",
)

pool_pre_ping: bool | EmptyType = Field(
default=Empty,
description="""If True will enable the connection pool “pre-ping” feature that tests connections for liveness
upon eachcheckout."""
upon eachcheckout.""",
)

pool_timeout: int | EmptyType = Field(
default=Empty,
description="""Number of seconds to wait before giving up on getting a connection from the pool.
This is only used with :class:`QueuePool <sqlalchemy.pool.QueuePool>`. This can be a float but
is subject to the limitations of Python time functions which may not be reliable in the tens of milliseconds."""
is subject to the limitations of Python time functions which may not be reliable in the tens of milliseconds.
""",
)

pool: "Pool| EmptyType" = Field(
Expand All @@ -101,22 +101,22 @@ class EngineConfig(BaseModel):
:class:`QueuePool <sqlalchemy.pool.QueuePool>` instance. If non-None, this pool will be used directly as the
underlying connection pool for the engine, bypassing whatever connection parameters are present in the URL argument.
For information on constructing connection pools manually, see
`Connection Pooling <https://docs.sqlalchemy.org/en/20/core/pooling.html>`_."""
`Connection Pooling <https://docs.sqlalchemy.org/en/20/core/pooling.html>`_.""",
)

poolclass: "type[Pool]| EmptyType" = Field(
default=Empty,
description="""A :class:`Pool <sqlalchemy.pool.Pool>` subclass, which will be used to create a connection pool
instance using the connection parameters given in the URL. Note this differs from pool in that you don`t
actually instantiate the pool in this case, you just indicate what type of pool to be used."""
actually instantiate the pool in this case, you just indicate what type of pool to be used.""",
)

query_cache_size: int | EmptyType = Field(
default=Empty,
description="""Size of the cache used to cache the SQL string form of queries. Set to zero to disable caching.
See :attr:`query_cache_size <sqlalchemy.get_engine.params.query_cache_size>` for more info.
"""
""",
)

pool_reset_on_return: Literal["reset", "rollback", "commit"] | EmptyType = Empty
4 changes: 2 additions & 2 deletions src/core/database/types/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ def process_bind_param(self, value: int, dialect: "Dialect") -> int: # noqa: AR
raise ValueError(msg)

@no_type_check
def process_result_value(self, value: int, dialect: "Dialect")-> "T": # noqa: ARG002
def process_result_value(self, value: int, dialect: "Dialect") -> "T": # noqa: ARG002
return self.enum_type(value)

@no_type_check
def copy(self, **kwargs: Any)-> "IntegerEnum[T]": # noqa: ARG002
def copy(self, **kwargs: Any) -> "IntegerEnum[T]": # noqa: ARG002
return IntegerEnum(self.enum_type)
File renamed without changes.
8 changes: 2 additions & 6 deletions src/core/errors/_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,5 @@ class ErrorCode(NamedTuple):
message: str
details: list[Any] | None = None

def dict(self)-> "Error":
return {
"code": self.error,
"message": self.message,
"details": self.details
}
def dict(self) -> "Error":
return {"code": self.error, "message": self.message, "details": self.details}
5 changes: 4 additions & 1 deletion src/core/errors/auth_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,10 @@ def default_exception_handler(request: Request, exc: Exception) -> JSONResponse:
log_exception(exc, logger_trace_info=True)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={"error": base_exceptions.ERR_500.error, "message": _(base_exceptions.ERR_500.message, request_id=request_id_ctx.get())},
content={
"error": base_exceptions.ERR_500.error,
"message": _(base_exceptions.ERR_500.message, request_id=request_id_ctx.get()),
},
)


Expand Down
3 changes: 0 additions & 3 deletions src/core/models/mixins/audit_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from src.features.auth.models import User



def get_object_change(obj: Mapper) -> dict:
insp = inspect(obj)
changes: dict[str, dict] = {
Expand Down Expand Up @@ -129,5 +128,3 @@ def __declare_last__(cls) -> None:
event.listen(cls, "after_insert", cls.log_create, propagate=True)
event.listen(cls, "after_update", cls.log_update, propagate=True)
event.listen(cls, "after_delete", cls.log_delete, propagate=True)


Loading

0 comments on commit 7b4cc7a

Please sign in to comment.