svc-infra 0.1.176__py3-none-any.whl → 0.1.178__py3-none-any.whl

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.
@@ -4,6 +4,7 @@ from typing import Optional, Sequence
4
4
  from contextlib import asynccontextmanager
5
5
  from fastapi import FastAPI
6
6
 
7
+ from svc_infra.auth.user_default import make_default_user_service
7
8
  from .repository import Repository
8
9
  from .service import Service
9
10
  from .crud_router import make_crud_router_plus
@@ -12,10 +13,25 @@ from .resource import Resource
12
13
  from .session import initialize_session, dispose_session
13
14
  from .health import _make_db_health_router
14
15
 
16
+ def _looks_like_user_model(model: type) -> bool:
17
+ # minimal/robust: users have email + password_hash.
18
+ # (You can tighten this with hasattr(model, "extra") / "roles" if you want.)
19
+ return hasattr(model, "email") and hasattr(model, "password_hash")
20
+
15
21
  def add_resources(app: FastAPI, resources: Sequence[Resource]) -> None:
16
22
  for r in resources:
17
23
  repo = Repository(model=r.model, id_attr=r.id_attr, soft_delete=r.soft_delete)
18
- svc = r.service_factory(repo) if r.service_factory else Service(repo)
24
+
25
+ # 1) explicit app-provided factory wins
26
+ if r.service_factory:
27
+ svc = r.service_factory(repo)
28
+ # 2) otherwise, auto-apply our default user service if model looks like a user
29
+ elif _looks_like_user_model(r.model):
30
+ svc = make_default_user_service(repo)
31
+ # 3) else, generic service
32
+ else:
33
+ svc = Service(repo)
34
+
19
35
  if r.read_schema and r.create_schema and r.update_schema:
20
36
  Read, Create, Update = r.read_schema, r.create_schema, r.update_schema
21
37
  else:
@@ -26,6 +42,7 @@ def add_resources(app: FastAPI, resources: Sequence[Resource]) -> None:
26
42
  create_name=r.create_name,
27
43
  update_name=r.update_name,
28
44
  )
45
+
29
46
  router = make_crud_router_plus(
30
47
  model=r.model,
31
48
  service=svc,
@@ -26,5 +26,5 @@ class Resource:
26
26
 
27
27
  create_exclude: tuple[str, ...] = ("id",)
28
28
 
29
- # NEW: let callers provide a custom service constructor
29
+ # NEW optional hook to build a custom Service
30
30
  service_factory: Optional[Callable[[Repository], Service]] = None
@@ -1,5 +1,5 @@
1
1
  from .integration import enable_auth
2
- from .user_service import make_user_service
2
+ from .user_default import make_user_service
3
3
 
4
4
  __all__ = [
5
5
  "enable_auth",
@@ -6,26 +6,22 @@ from svc_infra.api.fastapi.db.repository import Repository
6
6
 
7
7
  _pwd = PasswordHelper()
8
8
 
9
- def _user_pre_create(data: Dict[str, Any]) -> Dict[str, Any]:
10
- data = dict(data) # don’t mutate caller’s dict
11
- # map payload fields -> model columns
9
+ def _pre_create(data: Dict[str, Any]) -> Dict[str, Any]:
10
+ data = dict(data)
12
11
  if "password" in data:
13
12
  data["password_hash"] = _pwd.hash(data.pop("password"))
14
- if "metadata" in data: # pydantic uses alias "metadata" for model column "extra"
13
+ if "metadata" in data: # pydantic alias -> model column "metadata" (extra)
15
14
  data["extra"] = data.pop("metadata")
16
- # roles default if missing
17
15
  data.setdefault("roles", [])
18
- # booleans default come from model; fine to ignore if absent
19
16
  return data
20
17
 
21
- def _user_pre_update(data: Dict[str, Any]) -> Dict[str, Any]:
18
+ def _pre_update(data: Dict[str, Any]) -> Dict[str, Any]:
22
19
  data = dict(data)
23
- # allow password change via generic update too (optional)
24
20
  if "password" in data:
25
21
  data["password_hash"] = _pwd.hash(data.pop("password"))
26
22
  if "metadata" in data:
27
23
  data["extra"] = data.pop("metadata")
28
24
  return data
29
25
 
30
- def make_user_service(repo: Repository):
31
- return ServiceWithHooks(repo, pre_create=_user_pre_create, pre_update=_user_pre_update)
26
+ def make_default_user_service(repo: Repository):
27
+ return ServiceWithHooks(repo, pre_create=_pre_create, pre_update=_pre_update)
@@ -3,6 +3,8 @@ from datetime import datetime
3
3
  from typing import Optional
4
4
  import uuid
5
5
 
6
+ from svc_infra.auth.user_default import _pwd
7
+
6
8
  from sqlalchemy import (
7
9
  String, Boolean, DateTime, JSON, Text, func, UniqueConstraint, Index
8
10
  )
@@ -23,8 +25,16 @@ class User(ModelBase):
23
25
  is_superuser: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
24
26
  is_verified: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
25
27
 
26
- # auth state
27
28
  password_hash: Mapped[str] = mapped_column(String(512), nullable=False)
29
+
30
+ @property
31
+ def password(self) -> str: # never readable
32
+ raise AttributeError("password is write-only")
33
+
34
+ @password.setter
35
+ def password(self, raw: str) -> None:
36
+ self.password_hash = _pwd.hash(raw)
37
+
28
38
  last_login: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
29
39
  disabled_reason: Mapped[Optional[str]] = mapped_column(Text)
30
40
 
@@ -35,6 +35,7 @@ class UserCreate(BaseModel):
35
35
  class UserUpdate(BaseModel):
36
36
  email: Optional[EmailStr] = None
37
37
  full_name: Optional[str] = None
38
+ password: Optional[str] = None
38
39
  is_active: Optional[bool] = None
39
40
  is_superuser: Optional[bool] = None
40
41
  is_verified: Optional[bool] = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: svc-infra
3
- Version: 0.1.176
3
+ Version: 0.1.178
4
4
  Summary: Infrastructure for building and deploying prod-ready services
5
5
  License: MIT
6
6
  Keywords: fastapi,sqlalchemy,alembic,auth,infra,async,pydantic
@@ -3,13 +3,13 @@ svc_infra/api/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
3
3
  svc_infra/api/fastapi/__init__.py,sha256=-clEe_YA8P_VekTIrFN3RD4suas0CUpvvMnxwpfr4v0,5573
4
4
  svc_infra/api/fastapi/db/README.md,sha256=NVD6r-3ArHNaYuo-rks3YbmIQP_hLqg5tBAbvDbdMJ8,3055
5
5
  svc_infra/api/fastapi/db/__init__.py,sha256=Xsu7IpWtFsaUSkKq7yhETfkstG1KEd3XQ1Rh6kNPWf8,210
6
- svc_infra/api/fastapi/db/add.py,sha256=GlHXGU9wd3IHrYb-8_QmSiV0WDjnkDKqxUUkVEOXiKY,2682
6
+ svc_infra/api/fastapi/db/add.py,sha256=QXdyZAmuYSzCQHDuT9KtFtDJBQ9rdz-b3dvWPEtNpas,3315
7
7
  svc_infra/api/fastapi/db/crud_router.py,sha256=e55Y_XHQnW5n_FoxIPcfaXPV3YhSr-QXWetbYrNirzM,4708
8
8
  svc_infra/api/fastapi/db/health.py,sha256=rWSsQFlvut8oPheqM8SH4fo32z6rz5a2gN_M0CgONE0,715
9
9
  svc_infra/api/fastapi/db/http.py,sha256=41iDMq_3Ws1fdXpfaftJii92mJO4ZGNx8ZteDtRTz8I,2276
10
10
  svc_infra/api/fastapi/db/management.py,sha256=A5NOeTT9lW6DAJ58Qd1-oe8oQYM1mi0vTmJpz2njvPU,4186
11
11
  svc_infra/api/fastapi/db/repository.py,sha256=Pd1zCAdTdxl1tB_1a8W1rl9XGmLBbxfK04qc21kZTfY,5844
12
- svc_infra/api/fastapi/db/resource.py,sha256=wN4AnJUW138iR58RVCu2IvXUCtATBkebOpaYiUNEDMM,877
12
+ svc_infra/api/fastapi/db/resource.py,sha256=h-kCjp0_Sw_lGthizkEaneKyIZZXX3SCs6LFrxDvHR4,871
13
13
  svc_infra/api/fastapi/db/service.py,sha256=w7WFzETndKuk-2wkOdjmGbznY8lJTzUP3781Dg42alE,2033
14
14
  svc_infra/api/fastapi/db/service_hooks.py,sha256=KZzkKL-2y7hrPx4bfcDdwItAqPBksyVFUjnx1hGJwxw,655
15
15
  svc_infra/api/fastapi/db/session.py,sha256=1ixBxRZLbdE8LXLP6R87u7pDzITZkZRYZ3Un8ugvJNY,1775
@@ -26,12 +26,12 @@ svc_infra/app/env.py,sha256=7MCEGuKqYeFT-aq7hiOn0hXJlCIy9VRXAsNHqDMHzPo,4430
26
26
  svc_infra/app/logging.py,sha256=xxRKbyYYrxkbjyZ9LKisad6IuBhiP5jyi6BUjkD4JRY,5863
27
27
  svc_infra/app/root.py,sha256=i6Rw1tchhKDlUbZgXwN5ec6KNMflqVry_y_GFB3Oioo,1576
28
28
  svc_infra/app/settings.py,sha256=6zmRRZxGvHxTNPaLiRdiWaq_YjleFNedbeIG81j7uOE,621
29
- svc_infra/auth/__init__.py,sha256=954N2yfG0m7rSWWJFjRxJU32lc55uGBCwjfnEPrsGx0,138
29
+ svc_infra/auth/__init__.py,sha256=cOFiGShQF5z2pQnFCzEaRHOHWRN_XneJO1qdXXatL6M,138
30
30
  svc_infra/auth/integration.py,sha256=L230xUw7vRM0AJIZwNzufRQrfNTPRdczp4BPd_NILeo,1606
31
31
  svc_infra/auth/oauth_router.py,sha256=JY3VQ9_0oS_a2gGg418IDG2TbK79sQWcpA9KPiOAfjY,4918
32
32
  svc_infra/auth/providers.py,sha256=fiw0ouuGKtwcwMY0Zw7cEv-CXNaUYDnqo_NmiWiB8Lc,3185
33
33
  svc_infra/auth/settings.py,sha256=wVCLQnpA9M6zfiWyUpSdn9fo4q-Z4WpVyC3KvkG3HYg,1742
34
- svc_infra/auth/user_service.py,sha256=UbG5xKr25m4LfVCdmk99IxHNMzzELEtIUBVQn8--nG4,1227
34
+ svc_infra/auth/user_default.py,sha256=Jn2XeeW8QgijZfxXUsV4T4CvaewsWA6VsDVqotYIgxQ,968
35
35
  svc_infra/auth/users.py,sha256=4rlTCELU-po7bF7u6z02Bd8pXLGcLI2Adj0VjA-gg5E,2098
36
36
  svc_infra/cli/__init__.py,sha256=g47RSROuFW8LSsB6WFNSus5BnUl9z_DrPK9idYo3O6M,401
37
37
  svc_infra/cli/cmds/__init__.py,sha256=263YKSg73Ik-SQeilyGePdc663BYi4RfBrc0wMFxeoU,212
@@ -49,8 +49,8 @@ svc_infra/db/core.py,sha256=3Efm88fajfKJa8gDiiTVkhx2ci_UbrLiR4B8gMQYuwU,10745
49
49
  svc_infra/db/scaffold.py,sha256=tlqqw_D7Oyzt1qm-UQnAKkzwHpmTTNmcNgInauzgoQ8,9896
50
50
  svc_infra/db/templates/__init__.py,sha256=HDUUOs-5eR9yuEs8JakwWewPw6swvRvmorWagSoSMVg,51
51
51
  svc_infra/db/templates/models_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
- svc_infra/db/templates/models_schemas/auth/models.py.tmpl,sha256=t38JDK6StZbGCVuZ1KNV_MWXuGulwP8vKTLxuknoxIw,2258
53
- svc_infra/db/templates/models_schemas/auth/schemas.py.tmpl,sha256=wzg2EdjLiGRId12tdHFK-gNXydhyyc00hx0mjlvuMXs,1658
52
+ svc_infra/db/templates/models_schemas/auth/models.py.tmpl,sha256=5H0I301gR9m2_zrxvay29TF0wV0yUjIlgR46oJkFT5s,2515
53
+ svc_infra/db/templates/models_schemas/auth/schemas.py.tmpl,sha256=GjDDEHO-BNV-Xi5z2ZOygXovnECgSu8OkEg8Qx6vgIE,1693
54
54
  svc_infra/db/templates/models_schemas/entity/models.py.tmpl,sha256=dZk7a3WwpddSylPW81ARTyjbqJ-4rduX8-KAZ3Tlomo,1173
55
55
  svc_infra/db/templates/models_schemas/entity/schemas.py.tmpl,sha256=zBhid1jFPwVN86OvkiIJkTFphN7LDcs_Hs9kxxeoNjE,1009
56
56
  svc_infra/db/templates/setup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -75,7 +75,7 @@ svc_infra/observability/templates/prometheus_rules.yml,sha256=sbVLm1h40FMkGSeWO4
75
75
  svc_infra/observability/tracing/__init__.py,sha256=TOs2yCicqBdo4OfOHTMmqeHsn7DBRu5EdvF2L5f31Y0,237
76
76
  svc_infra/observability/tracing/setup.py,sha256=21Ob276U4KZOs6M2o1O79wQXFHV0gY6YdMyjeqMrzMU,5042
77
77
  svc_infra/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
- svc_infra-0.1.176.dist-info/METADATA,sha256=DHjyLhKfoWhRd803g23DbC2DbTBb-2E_Kpq4QSa7bdw,4987
79
- svc_infra-0.1.176.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
80
- svc_infra-0.1.176.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
81
- svc_infra-0.1.176.dist-info/RECORD,,
78
+ svc_infra-0.1.178.dist-info/METADATA,sha256=KJUDnzRLLHDuSm8sYtPM2vf5u85sKOUB2STDYJg2_vg,4987
79
+ svc_infra-0.1.178.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
80
+ svc_infra-0.1.178.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
81
+ svc_infra-0.1.178.dist-info/RECORD,,