svc-infra 0.1.176__tar.gz → 0.1.177__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.
Files changed (80) hide show
  1. {svc_infra-0.1.176 → svc_infra-0.1.177}/PKG-INFO +1 -1
  2. {svc_infra-0.1.176 → svc_infra-0.1.177}/pyproject.toml +1 -1
  3. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/db/add.py +18 -1
  4. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/db/resource.py +1 -1
  5. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/auth/__init__.py +1 -1
  6. svc_infra-0.1.176/src/svc_infra/auth/user_service.py → svc_infra-0.1.177/src/svc_infra/auth/user_default.py +6 -10
  7. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/templates/models_schemas/auth/models.py.tmpl +9 -1
  8. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/templates/models_schemas/auth/schemas.py.tmpl +1 -0
  9. {svc_infra-0.1.176 → svc_infra-0.1.177}/README.md +0 -0
  10. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/__init__.py +0 -0
  11. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/__init__.py +0 -0
  12. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/__init__.py +0 -0
  13. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/db/README.md +0 -0
  14. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/db/__init__.py +0 -0
  15. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/db/crud_router.py +0 -0
  16. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/db/health.py +0 -0
  17. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/db/http.py +0 -0
  18. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/db/management.py +0 -0
  19. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/db/repository.py +0 -0
  20. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/db/service.py +0 -0
  21. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/db/service_hooks.py +0 -0
  22. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/db/session.py +0 -0
  23. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/middleware/__init__.py +0 -0
  24. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/middleware/errors/__init__.py +0 -0
  25. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/middleware/errors/catchall.py +0 -0
  26. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/middleware/errors/error_handlers.py +0 -0
  27. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/middleware/errors/exceptions.py +0 -0
  28. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/routers/__init__.py +0 -0
  29. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/routers/ping.py +0 -0
  30. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/api/fastapi/settings.py +0 -0
  31. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/app/__init__.py +0 -0
  32. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/app/env.py +0 -0
  33. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/app/logging.py +0 -0
  34. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/app/root.py +0 -0
  35. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/app/settings.py +0 -0
  36. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/auth/integration.py +0 -0
  37. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/auth/oauth_router.py +0 -0
  38. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/auth/providers.py +0 -0
  39. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/auth/settings.py +0 -0
  40. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/auth/users.py +0 -0
  41. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/cli/__init__.py +0 -0
  42. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/cli/cmds/__init__.py +0 -0
  43. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/cli/cmds/alembic_cmds.py +0 -0
  44. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/cli/cmds/help.py +0 -0
  45. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/cli/cmds/scaffold_cmds.py +0 -0
  46. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/cli/foundation/__init__.py +0 -0
  47. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/cli/foundation/runner.py +0 -0
  48. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/cli/foundation/typer_bootstrap.py +0 -0
  49. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/README.md +0 -0
  50. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/__init__.py +0 -0
  51. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/base.py +0 -0
  52. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/constants.py +0 -0
  53. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/core.py +0 -0
  54. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/scaffold.py +0 -0
  55. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/templates/__init__.py +0 -0
  56. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/templates/models_schemas/__init__.py +0 -0
  57. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/templates/models_schemas/entity/models.py.tmpl +0 -0
  58. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/templates/models_schemas/entity/schemas.py.tmpl +0 -0
  59. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/templates/setup/__init__.py +0 -0
  60. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/templates/setup/alembic.ini.tmpl +0 -0
  61. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/templates/setup/env_async.py.tmpl +0 -0
  62. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/templates/setup/env_sync.py.tmpl +0 -0
  63. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/templates/setup/script.py.mako.tmpl +0 -0
  64. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/db/utils.py +0 -0
  65. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/mcp/__init__.py +0 -0
  66. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/mcp/svc_infra_mcp.py +0 -0
  67. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/observability/README.md +0 -0
  68. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/observability/__init__.py +0 -0
  69. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/observability/metrics/__init__.py +0 -0
  70. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/observability/metrics/asgi.py +0 -0
  71. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/observability/metrics/base.py +0 -0
  72. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/observability/metrics/http.py +0 -0
  73. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/observability/metrics/sqlalchemy.py +0 -0
  74. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/observability/settings.py +0 -0
  75. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/observability/templates/grafana_dashboard.json +0 -0
  76. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/observability/templates/otel-collector.yaml +0 -0
  77. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/observability/templates/prometheus_rules.yml +0 -0
  78. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/observability/tracing/__init__.py +0 -0
  79. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/observability/tracing/setup.py +0 -0
  80. {svc_infra-0.1.176 → svc_infra-0.1.177}/src/svc_infra/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: svc-infra
3
- Version: 0.1.176
3
+ Version: 0.1.177
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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "svc-infra"
3
- version = "0.1.176"
3
+ version = "0.1.177"
4
4
  description = "Infrastructure for building and deploying prod-ready services"
5
5
  authors = ["Ali Khatami <aliikhatami94@gmail.com>"]
6
6
  license = "MIT"
@@ -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)
@@ -23,8 +23,16 @@ class User(ModelBase):
23
23
  is_superuser: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
24
24
  is_verified: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
25
25
 
26
- # auth state
27
26
  password_hash: Mapped[str] = mapped_column(String(512), nullable=False)
27
+
28
+ @property
29
+ def password(self) -> str:
30
+ raise AttributeError("password is write-only")
31
+
32
+ @password.setter
33
+ def password(self, raw: str) -> None:
34
+ self.password_hash = _pwd.hash(raw)
35
+
28
36
  last_login: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
29
37
  disabled_reason: Mapped[Optional[str]] = mapped_column(Text)
30
38
 
@@ -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
File without changes