svc-infra 0.1.185__py3-none-any.whl → 0.1.186__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.
@@ -2,6 +2,7 @@ from typing import Any, Dict
2
2
  from fastapi import HTTPException
3
3
  from fastapi_users.password import PasswordHelper
4
4
  from sqlalchemy import func
5
+ from sqlalchemy.exc import IntegrityError
5
6
 
6
7
  from svc_infra.api.fastapi.db.service_hooks import ServiceWithHooks
7
8
  from svc_infra.api.fastapi.db.repository import Repository
@@ -9,7 +10,7 @@ from svc_infra.api.fastapi.db.repository import Repository
9
10
  _pwd = PasswordHelper()
10
11
 
11
12
  def make_default_user_service(repo: Repository):
12
- Model = repo.model # <-- capture the actual mapped model from the repo
13
+ Model = repo.model # capture the mapped class
13
14
 
14
15
  def _user_pre_create(data: Dict[str, Any]) -> Dict[str, Any]:
15
16
  data = dict(data)
@@ -20,11 +21,10 @@ def make_default_user_service(repo: Repository):
20
21
  data["extra"] = data.pop("metadata")
21
22
  data.setdefault("roles", [])
22
23
 
23
- # BEFORE insert: app-level guard (nice 409) aligned with DB uniqueness
24
+ # attach existence checker (case-insensitive email, tenant scoped)
24
25
  email = data.get("email")
25
26
  tenant_id = data.get("tenant_id")
26
27
  if email is not None:
27
- # case-insensitive email; scope by tenant (NULL => global)
28
28
  where = [func.lower(Model.email) == func.lower(email)]
29
29
  if hasattr(Model, "tenant_id"):
30
30
  if tenant_id is None:
@@ -35,9 +35,7 @@ def make_default_user_service(repo: Repository):
35
35
  async def _exists(session):
36
36
  return await repo.exists(session, where=where)
37
37
 
38
- # stash the callback so Service.create can await it (has session)
39
38
  data["_precreate_exists_check"] = _exists
40
-
41
39
  return data
42
40
 
43
41
  def _user_pre_update(data: Dict[str, Any]) -> Dict[str, Any]:
@@ -47,7 +45,7 @@ def make_default_user_service(repo: Repository):
47
45
  if "metadata" in data:
48
46
  data["extra"] = data.pop("metadata")
49
47
 
50
- # Optional: also protect email change from creating dupes
48
+ # optional: protect email change too
51
49
  email = data.get("email")
52
50
  tenant_id = data.get("tenant_id")
53
51
  if email is not None:
@@ -61,21 +59,34 @@ def make_default_user_service(repo: Repository):
61
59
  async def _exists(session):
62
60
  return await repo.exists(session, where=where)
63
61
 
64
- data["_precreate_exists_check"] = _exists # reuse same key
65
-
62
+ data["_preupdate_exists_check"] = _exists
66
63
  return data
67
64
 
68
65
  class _Svc(ServiceWithHooks):
69
66
  async def create(self, session, data):
67
+ # IMPORTANT: run pre_create first
68
+ data = await self.pre_create(data)
70
69
  exists_cb = data.pop("_precreate_exists_check", None)
71
70
  if exists_cb and await exists_cb(session):
72
71
  raise HTTPException(status_code=409, detail="User with this email already exists.")
73
- return await super().create(session, data)
72
+ try:
73
+ return await self.repo.create(session, data)
74
+ except IntegrityError as e:
75
+ # race-safety / fallback
76
+ if "uq_users_tenant_id" in str(e.orig) or "ci_email" in str(e.orig):
77
+ raise HTTPException(status_code=409, detail="User with this email already exists.") from e
78
+ raise
74
79
 
75
80
  async def update(self, session, id_value, data):
76
- exists_cb = data.pop("_precreate_exists_check", None)
81
+ data = await self.pre_update(data)
82
+ exists_cb = data.pop("_preupdate_exists_check", None)
77
83
  if exists_cb and await exists_cb(session):
78
84
  raise HTTPException(status_code=409, detail="User with this email already exists.")
79
- return await super().update(session, id_value, data)
85
+ try:
86
+ return await self.repo.update(session, id_value, data)
87
+ except IntegrityError as e:
88
+ if "uq_users_tenant_id" in str(e.orig) or "ci_email" in str(e.orig):
89
+ raise HTTPException(status_code=409, detail="User with this email already exists.") from e
90
+ raise
80
91
 
81
92
  return _Svc(repo, pre_create=_user_pre_create, pre_update=_user_pre_update)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: svc-infra
3
- Version: 0.1.185
3
+ Version: 0.1.186
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
@@ -32,7 +32,7 @@ svc_infra/auth/integration.py,sha256=L230xUw7vRM0AJIZwNzufRQrfNTPRdczp4BPd_NILeo
32
32
  svc_infra/auth/oauth_router.py,sha256=JY3VQ9_0oS_a2gGg418IDG2TbK79sQWcpA9KPiOAfjY,4918
33
33
  svc_infra/auth/providers.py,sha256=fiw0ouuGKtwcwMY0Zw7cEv-CXNaUYDnqo_NmiWiB8Lc,3185
34
34
  svc_infra/auth/settings.py,sha256=wVCLQnpA9M6zfiWyUpSdn9fo4q-Z4WpVyC3KvkG3HYg,1742
35
- svc_infra/auth/user_default.py,sha256=D6oMni4YyFeb1muws0CokC20t-R0FwMH28HMmSEJnMw,3290
35
+ svc_infra/auth/user_default.py,sha256=VXSpYGxABvHHYGvd7eXU7OPinhwQFr-oS-qeYIufYro,3856
36
36
  svc_infra/auth/users.py,sha256=4rlTCELU-po7bF7u6z02Bd8pXLGcLI2Adj0VjA-gg5E,2098
37
37
  svc_infra/cli/__init__.py,sha256=g47RSROuFW8LSsB6WFNSus5BnUl9z_DrPK9idYo3O6M,401
38
38
  svc_infra/cli/cmds/__init__.py,sha256=263YKSg73Ik-SQeilyGePdc663BYi4RfBrc0wMFxeoU,212
@@ -77,7 +77,7 @@ svc_infra/observability/templates/prometheus_rules.yml,sha256=sbVLm1h40FMkGSeWO4
77
77
  svc_infra/observability/tracing/__init__.py,sha256=TOs2yCicqBdo4OfOHTMmqeHsn7DBRu5EdvF2L5f31Y0,237
78
78
  svc_infra/observability/tracing/setup.py,sha256=21Ob276U4KZOs6M2o1O79wQXFHV0gY6YdMyjeqMrzMU,5042
79
79
  svc_infra/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
- svc_infra-0.1.185.dist-info/METADATA,sha256=PubOT8LUdQnRSSJXHLtHa4V1EgFEGLyDSIyAFVwe_pw,4981
81
- svc_infra-0.1.185.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
82
- svc_infra-0.1.185.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
83
- svc_infra-0.1.185.dist-info/RECORD,,
80
+ svc_infra-0.1.186.dist-info/METADATA,sha256=HYc2VSpY0TqlTJxBLTfeqmallrOGoAqJvkUOF4cUjHM,4981
81
+ svc_infra-0.1.186.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
82
+ svc_infra-0.1.186.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
83
+ svc_infra-0.1.186.dist-info/RECORD,,