svc-infra 0.1.182__tar.gz → 0.1.184__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {svc_infra-0.1.182 → svc_infra-0.1.184}/PKG-INFO +1 -1
- {svc_infra-0.1.182 → svc_infra-0.1.184}/pyproject.toml +1 -1
- svc_infra-0.1.184/src/svc_infra/api/fastapi/db/uniq.py +82 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/auth/user_default.py +1 -1
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/templates/models_schemas/auth/models.py.tmpl +10 -20
- svc_infra-0.1.184/src/svc_infra/db/templates/models_schemas/entity/models.py.tmpl +52 -0
- svc_infra-0.1.184/src/svc_infra/db/uniq.py +77 -0
- svc_infra-0.1.182/src/svc_infra/db/templates/models_schemas/entity/models.py.tmpl +0 -29
- {svc_infra-0.1.182 → svc_infra-0.1.184}/README.md +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/db/README.md +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/db/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/db/add.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/db/crud_router.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/db/health.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/db/http.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/db/management.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/db/repository.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/db/resource.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/db/service.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/db/service_hooks.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/db/session.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/middleware/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/middleware/errors/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/middleware/errors/catchall.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/middleware/errors/error_handlers.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/middleware/errors/exceptions.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/routers/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/routers/ping.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/settings.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/app/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/app/env.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/app/logging.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/app/root.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/app/settings.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/auth/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/auth/integration.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/auth/oauth_router.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/auth/providers.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/auth/settings.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/auth/users.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/cli/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/cli/cmds/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/cli/cmds/alembic_cmds.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/cli/cmds/help.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/cli/cmds/scaffold_cmds.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/cli/foundation/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/cli/foundation/runner.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/cli/foundation/typer_bootstrap.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/README.md +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/base.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/constants.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/core.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/scaffold.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/templates/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/templates/models_schemas/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/templates/models_schemas/auth/schemas.py.tmpl +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/templates/models_schemas/entity/schemas.py.tmpl +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/templates/setup/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/templates/setup/alembic.ini.tmpl +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/templates/setup/env_async.py.tmpl +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/templates/setup/env_sync.py.tmpl +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/templates/setup/script.py.mako.tmpl +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/utils.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/mcp/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/mcp/svc_infra_mcp.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/README.md +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/metrics/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/metrics/asgi.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/metrics/base.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/metrics/http.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/metrics/sqlalchemy.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/settings.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/templates/grafana_dashboard.json +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/templates/otel-collector.yaml +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/templates/prometheus_rules.yml +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/tracing/__init__.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/tracing/setup.py +0 -0
- {svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/py.typed +0 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Iterable, Optional, Dict, Any, Callable
|
4
|
+
from sqlalchemy import func, and_
|
5
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
6
|
+
|
7
|
+
from .repository import Repository
|
8
|
+
from svc_infra.db.uniq import ColumnSpec, _as_tuple
|
9
|
+
|
10
|
+
def make_uniqueness_hooks(
|
11
|
+
*,
|
12
|
+
model: type,
|
13
|
+
repo: Repository,
|
14
|
+
unique_cs: Iterable[ColumnSpec] = (),
|
15
|
+
unique_ci: Iterable[ColumnSpec] = (),
|
16
|
+
tenant_field: Optional[str] = None,
|
17
|
+
duplicate_message: str = "Duplicate resource violates uniqueness policy.",
|
18
|
+
) -> tuple[
|
19
|
+
Callable[[Dict[str, Any]], Any], # async pre_create(data) -> data
|
20
|
+
Callable[[Dict[str, Any]], Any], # async pre_update(data) -> data
|
21
|
+
]:
|
22
|
+
"""Return (pre_create, pre_update) hooks that check duplicates *before* insert/update.
|
23
|
+
|
24
|
+
Usage with ServiceWithHooks:
|
25
|
+
pre_c, pre_u = make_uniqueness_hooks(model=User, repo=repo, unique_ci=["email"], tenant_field="tenant_id")
|
26
|
+
svc = ServiceWithHooks(repo, pre_create=pre_c, pre_update=pre_u)
|
27
|
+
"""
|
28
|
+
cs_specs = [_as_tuple(s) for s in unique_cs]
|
29
|
+
ci_specs = [_as_tuple(s) for s in unique_ci]
|
30
|
+
|
31
|
+
def _build_wheres(data: Dict[str, Any]):
|
32
|
+
wheres = []
|
33
|
+
|
34
|
+
def col(name: str):
|
35
|
+
return getattr(model, name)
|
36
|
+
|
37
|
+
tenant_val = data.get(tenant_field) if tenant_field else None
|
38
|
+
|
39
|
+
# case-sensitive groups
|
40
|
+
for spec in cs_specs:
|
41
|
+
if not all(k in data for k in spec):
|
42
|
+
continue
|
43
|
+
parts = []
|
44
|
+
if tenant_field:
|
45
|
+
parts.append(col(tenant_field).is_(None) if tenant_val is None else col(tenant_field) == tenant_val)
|
46
|
+
for k in spec:
|
47
|
+
parts.append(col(k) == data[k])
|
48
|
+
wheres.append(and_(*parts))
|
49
|
+
|
50
|
+
# case-insensitive groups
|
51
|
+
for spec in ci_specs:
|
52
|
+
if not all(k in data for k in spec):
|
53
|
+
continue
|
54
|
+
parts = []
|
55
|
+
if tenant_field:
|
56
|
+
parts.append(col(tenant_field).is_(None) if tenant_val is None else col(tenant_field) == tenant_val)
|
57
|
+
for k in spec:
|
58
|
+
# compare lower(db_col) == lower(<value>) safely
|
59
|
+
parts.append(func.lower(col(k)) == func.lower(func.cast(data[k], col(k).type)))
|
60
|
+
wheres.append(and_(*parts))
|
61
|
+
|
62
|
+
return wheres
|
63
|
+
|
64
|
+
async def _deferred_check(session: AsyncSession, data: Dict[str, Any]):
|
65
|
+
wheres = _build_wheres(data)
|
66
|
+
for where in wheres:
|
67
|
+
if await repo.exists(session, where=[where]):
|
68
|
+
from fastapi import HTTPException
|
69
|
+
raise HTTPException(status_code=409, detail=duplicate_message)
|
70
|
+
|
71
|
+
async def _pre_create(data: Dict[str, Any]) -> Dict[str, Any]:
|
72
|
+
# store a coroutine to be awaited when a session is available in Service.create
|
73
|
+
data = dict(data)
|
74
|
+
data.setdefault("__uniq_check__", _deferred_check)
|
75
|
+
return data
|
76
|
+
|
77
|
+
async def _pre_update(data: Dict[str, Any]) -> Dict[str, Any]:
|
78
|
+
data = dict(data)
|
79
|
+
data.setdefault("__uniq_check__", _deferred_check)
|
80
|
+
return data
|
81
|
+
|
82
|
+
return _pre_create, _pre_update
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from typing import Any, Dict
|
2
2
|
from fastapi import HTTPException
|
3
3
|
from fastapi_users.password import PasswordHelper
|
4
|
-
from sqlalchemy import func
|
4
|
+
from sqlalchemy import func
|
5
5
|
|
6
6
|
from svc_infra.api.fastapi.db.service_hooks import ServiceWithHooks
|
7
7
|
from svc_infra.api.fastapi.db.repository import Repository
|
@@ -12,6 +12,7 @@ from sqlalchemy.ext.mutable import MutableDict, MutableList
|
|
12
12
|
|
13
13
|
from svc_infra.db.base import ModelBase
|
14
14
|
from svc_infra.auth.user_default import _pwd
|
15
|
+
from svc_infra.db.uniq import make_unique_indexes
|
15
16
|
|
16
17
|
|
17
18
|
class User(ModelBase):
|
@@ -60,23 +61,12 @@ class User(ModelBase):
|
|
60
61
|
return f"<User id={self.id} email={self.email!r}>"
|
61
62
|
|
62
63
|
|
63
|
-
#
|
64
|
-
|
65
|
-
|
66
|
-
# -
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
)
|
73
|
-
Index(
|
74
|
-
"uq_users_email_lower_per_tenant",
|
75
|
-
func.lower(User.email),
|
76
|
-
User.tenant_id,
|
77
|
-
unique=True,
|
78
|
-
postgresql_where=User.tenant_id.isnot(None),
|
79
|
-
)
|
80
|
-
|
81
|
-
# Optional helper index for case-insensitive lookups (non-unique)
|
82
|
-
Index("ix_users_email_lower", func.lower(User.email))
|
64
|
+
# Unique indexes, including case-insensitive and/or tenant-scoped.
|
65
|
+
for _ix in make_unique_indexes(
|
66
|
+
User,
|
67
|
+
unique_ci=["email"], # case-insensitive unique email
|
68
|
+
tenant_field="tenant_id", # scoped by tenant if provided
|
69
|
+
):
|
70
|
+
# Simply iterating keeps them registered with the Table metadata.
|
71
|
+
# (No further action needed if you use Alembic autogenerate or metadata.create_all.)
|
72
|
+
pass
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# models/${table_name}.py
|
2
|
+
from __future__ import annotations
|
3
|
+
from datetime import datetime
|
4
|
+
from typing import Optional
|
5
|
+
import uuid
|
6
|
+
|
7
|
+
from sqlalchemy import String, Boolean, DateTime, JSON, Text, func
|
8
|
+
from sqlalchemy.dialects.postgresql import UUID
|
9
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
10
|
+
from sqlalchemy.ext.mutable import MutableDict
|
11
|
+
|
12
|
+
from svc_infra.db.base import ModelBase
|
13
|
+
from svc_infra.db.uniq import make_unique_indexes
|
14
|
+
|
15
|
+
|
16
|
+
class ${Entity}(ModelBase):
|
17
|
+
__tablename__ = "${table_name}"
|
18
|
+
|
19
|
+
# identity
|
20
|
+
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
21
|
+
|
22
|
+
# core fields
|
23
|
+
name: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
24
|
+
description: Mapped[Optional[str]] = mapped_column(Text)
|
25
|
+
|
26
|
+
${tenant_field}\
|
27
|
+
${soft_delete_field}\
|
28
|
+
# misc (avoid attr name "metadata" clash)
|
29
|
+
extra: Mapped[dict] = mapped_column(MutableDict.as_mutable(JSON), default=dict)
|
30
|
+
|
31
|
+
# auditing (DB-side timestamps)
|
32
|
+
created_at: Mapped[datetime] = mapped_column(
|
33
|
+
DateTime(timezone=True), server_default=func.now(), nullable=False
|
34
|
+
)
|
35
|
+
updated_at: Mapped[datetime] = mapped_column(
|
36
|
+
DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False
|
37
|
+
)
|
38
|
+
|
39
|
+
def __repr__(self) -> str:
|
40
|
+
return f"<${Entity} id={self.id} name={self.name!r}>"
|
41
|
+
|
42
|
+
|
43
|
+
# --- Uniqueness policy --------------------------------------------------------
|
44
|
+
# Register functional unique indexes (case-insensitive on "name"),
|
45
|
+
# optionally scoped by tenant if present.
|
46
|
+
for _ix in make_unique_indexes(
|
47
|
+
${Entity},
|
48
|
+
unique_ci=["name"]${tenant_arg}
|
49
|
+
):
|
50
|
+
# Iteration is enough to attach them to the Table metadata
|
51
|
+
# (Alembic autogenerate / metadata.create_all will pick them up)
|
52
|
+
pass
|
@@ -0,0 +1,77 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Iterable, Sequence, Tuple, Union, List, Optional
|
3
|
+
from sqlalchemy import Index, func
|
4
|
+
|
5
|
+
ColumnSpec = Union[str, Sequence[str]]
|
6
|
+
|
7
|
+
def _as_tuple(spec: ColumnSpec) -> Tuple[str, ...]:
|
8
|
+
return (spec,) if isinstance(spec, str) else tuple(spec)
|
9
|
+
|
10
|
+
def make_unique_indexes(
|
11
|
+
model: type,
|
12
|
+
*,
|
13
|
+
unique_cs: Iterable[ColumnSpec] = (),
|
14
|
+
unique_ci: Iterable[ColumnSpec] = (),
|
15
|
+
tenant_field: Optional[str] = None,
|
16
|
+
name_prefix: str = "uq",
|
17
|
+
) -> List[Index]:
|
18
|
+
"""Return SQLAlchemy Index objects that enforce uniqueness.
|
19
|
+
|
20
|
+
- unique_cs: case-sensitive unique specs
|
21
|
+
- unique_ci: case-insensitive unique specs (lower(column))
|
22
|
+
- tenant_field: if provided, create two partial unique indexes:
|
23
|
+
* tenant IS NULL (global bucket)
|
24
|
+
* tenant IS NOT NULL (scoped per-tenant)
|
25
|
+
|
26
|
+
Declare right after your model class; Alembic or metadata.create_all will pick them up.
|
27
|
+
"""
|
28
|
+
idxs: List[Index] = []
|
29
|
+
|
30
|
+
def _col(name: str):
|
31
|
+
return getattr(model, name)
|
32
|
+
|
33
|
+
def _to_sa_cols(spec: Tuple[str, ...], *, ci: bool):
|
34
|
+
cols = []
|
35
|
+
for cname in spec:
|
36
|
+
c = _col(cname)
|
37
|
+
cols.append(func.lower(c) if ci else c)
|
38
|
+
return tuple(cols)
|
39
|
+
|
40
|
+
tenant_col = _col(tenant_field) if tenant_field else None
|
41
|
+
|
42
|
+
def _name(ci: bool, spec: Tuple[str, ...], null_bucket: Optional[str] = None):
|
43
|
+
parts = [name_prefix, model.__tablename__]
|
44
|
+
if tenant_field:
|
45
|
+
parts.append(tenant_field)
|
46
|
+
if null_bucket:
|
47
|
+
parts.append(null_bucket)
|
48
|
+
parts.append("ci" if ci else "cs")
|
49
|
+
parts.extend(spec)
|
50
|
+
return "_".join(parts)
|
51
|
+
|
52
|
+
for ci, spec_list in ((False, unique_cs), (True, unique_ci)):
|
53
|
+
for spec in spec_list:
|
54
|
+
spec_t = _as_tuple(spec)
|
55
|
+
cols = _to_sa_cols(spec_t, ci=ci)
|
56
|
+
|
57
|
+
if tenant_col is None:
|
58
|
+
idxs.append(Index(_name(ci, spec_t), *cols, unique=True))
|
59
|
+
else:
|
60
|
+
idxs.append(
|
61
|
+
Index(
|
62
|
+
_name(ci, spec_t, "null"),
|
63
|
+
*cols,
|
64
|
+
unique=True,
|
65
|
+
postgresql_where=tenant_col.is_(None),
|
66
|
+
)
|
67
|
+
)
|
68
|
+
idxs.append(
|
69
|
+
Index(
|
70
|
+
_name(ci, spec_t, "notnull"),
|
71
|
+
tenant_col,
|
72
|
+
*cols,
|
73
|
+
unique=True,
|
74
|
+
postgresql_where=tenant_col.isnot(None),
|
75
|
+
)
|
76
|
+
)
|
77
|
+
return idxs
|
@@ -1,29 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
from datetime import datetime
|
3
|
-
from typing import Optional
|
4
|
-
import uuid
|
5
|
-
|
6
|
-
from sqlalchemy import String, Boolean, DateTime, JSON, Text, func, UniqueConstraint, Index
|
7
|
-
from sqlalchemy.dialects.postgresql import UUID
|
8
|
-
from sqlalchemy.orm import Mapped, mapped_column
|
9
|
-
from sqlalchemy.ext.mutable import MutableDict
|
10
|
-
|
11
|
-
from svc_infra.db.base import ModelBase
|
12
|
-
|
13
|
-
|
14
|
-
class ${Entity}(ModelBase):
|
15
|
-
__tablename__ = "${table_name}"
|
16
|
-
|
17
|
-
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
18
|
-
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
19
|
-
description: Mapped[Optional[str]] = mapped_column(Text)
|
20
|
-
|
21
|
-
${tenant_field}${soft_delete_field} extra: Mapped[dict] = mapped_column(MutableDict.as_mutable(JSON), default=dict)
|
22
|
-
|
23
|
-
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
24
|
-
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
25
|
-
|
26
|
-
${constraints} def __repr__(self) -> str:
|
27
|
-
return f"<${Entity} id={self.id} name={self.name!r}>"
|
28
|
-
|
29
|
-
${indexes}
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/middleware/errors/__init__.py
RENAMED
File without changes
|
{svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/middleware/errors/catchall.py
RENAMED
File without changes
|
File without changes
|
{svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/api/fastapi/middleware/errors/exceptions.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/templates/models_schemas/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/db/templates/setup/script.py.mako.tmpl
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/templates/grafana_dashboard.json
RENAMED
File without changes
|
{svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/templates/otel-collector.yaml
RENAMED
File without changes
|
{svc_infra-0.1.182 → svc_infra-0.1.184}/src/svc_infra/observability/templates/prometheus_rules.yml
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|