nlbone 0.1.38__tar.gz → 0.2.0__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 (72) hide show
  1. {nlbone-0.1.38 → nlbone-0.2.0}/PKG-INFO +1 -1
  2. {nlbone-0.1.38 → nlbone-0.2.0}/pyproject.toml +1 -1
  3. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/db/query_builder.py +12 -2
  4. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/config/settings.py +2 -2
  5. nlbone-0.2.0/src/nlbone/interfaces/api/dependencies/auth.py +48 -0
  6. nlbone-0.2.0/src/nlbone/interfaces/api/exceptions.py +67 -0
  7. nlbone-0.2.0/src/nlbone/interfaces/api/pagination/__init__.py +9 -0
  8. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/api/pagination/offset_base.py +1 -24
  9. nlbone-0.1.38/src/nlbone/interfaces/api/exceptions.py +0 -8
  10. nlbone-0.1.38/src/nlbone/interfaces/api/pagination/__init__.py +0 -1
  11. {nlbone-0.1.38 → nlbone-0.2.0}/.gitignore +0 -0
  12. {nlbone-0.1.38 → nlbone-0.2.0}/LICENSE +0 -0
  13. {nlbone-0.1.38 → nlbone-0.2.0}/README.md +0 -0
  14. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/__init__.py +0 -0
  15. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/__init__.py +0 -0
  16. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/auth/__init__.py +0 -0
  17. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/auth/keycloak.py +0 -0
  18. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/db/__init__.py +0 -0
  19. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/db/memory.py +0 -0
  20. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/db/postgres.py +0 -0
  21. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/db/sqlalchemy/__init__.py +0 -0
  22. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/db/sqlalchemy/base.py +0 -0
  23. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/db/sqlalchemy/engine.py +0 -0
  24. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/db/sqlalchemy/query/__init__.py +0 -0
  25. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/db/sqlalchemy/query/builder.py +0 -0
  26. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/db/sqlalchemy/query/coercion.py +0 -0
  27. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/db/sqlalchemy/query/filters.py +0 -0
  28. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/db/sqlalchemy/query/ordering.py +0 -0
  29. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/db/sqlalchemy/query/types.py +0 -0
  30. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/db/sqlalchemy/schema.py +0 -0
  31. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/http_clients/__init__.py +0 -0
  32. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/http_clients/email_gateway.py +0 -0
  33. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/http_clients/uploadchi.py +0 -0
  34. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/http_clients/uploadchi_async.py +0 -0
  35. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/messaging/__init__.py +0 -0
  36. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/adapters/messaging/redis.py +0 -0
  37. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/config/__init__.py +0 -0
  38. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/config/logging.py +0 -0
  39. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/container.py +0 -0
  40. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/core/__init__.py +0 -0
  41. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/core/application/__init__.py +0 -0
  42. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/core/application/services/__init__.py +0 -0
  43. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/core/application/services.py +0 -0
  44. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/core/application/use_cases/__init__.py +0 -0
  45. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/core/application/use_cases/register_user.py +0 -0
  46. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/core/domain/__init__.py +0 -0
  47. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/core/domain/events.py +0 -0
  48. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/core/domain/models.py +0 -0
  49. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/core/ports/__init__.py +0 -0
  50. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/core/ports/auth.py +0 -0
  51. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/core/ports/files.py +0 -0
  52. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/core/ports/messaging.py +0 -0
  53. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/core/ports/repo.py +0 -0
  54. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/__init__.py +0 -0
  55. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/api/__init__.py +0 -0
  56. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/api/dependencies/__init__.py +0 -0
  57. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/api/dependencies/db.py +0 -0
  58. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/api/middleware/__init__.py +0 -0
  59. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/api/middleware/access_log.py +0 -0
  60. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/api/middleware/add_request_context.py +0 -0
  61. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/api/middleware/authentication.py +0 -0
  62. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/api/routers.py +0 -0
  63. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/api/schemas.py +0 -0
  64. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/cli/__init__.py +0 -0
  65. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/cli/init_db.py +0 -0
  66. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/cli/main.py +0 -0
  67. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/jobs/__init__.py +0 -0
  68. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/interfaces/jobs/sync_tokens.py +0 -0
  69. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/types.py +0 -0
  70. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/utils/__init__.py +0 -0
  71. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/utils/context.py +0 -0
  72. {nlbone-0.1.38 → nlbone-0.2.0}/src/nlbone/utils/time.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nlbone
3
- Version: 0.1.38
3
+ Version: 0.2.0
4
4
  Summary: Backbone package for interfaces and infrastructure in Python projects
5
5
  Author-email: Amir Hosein Kahkbazzadeh <a.khakbazzadeh@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nlbone"
7
- version = "0.1.38"
7
+ version = "0.2.0"
8
8
  description = "Backbone package for interfaces and infrastructure in Python projects"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -6,7 +6,7 @@ from sqlalchemy.orm import Session, Query
6
6
  from sqlalchemy.dialects.postgresql import ENUM as PGEnum
7
7
 
8
8
  from nlbone.interfaces.api.exceptions import UnprocessableEntityException
9
- from nlbone.interfaces.api.pagination import PaginateRequest
9
+ from nlbone.interfaces.api.pagination import PaginateRequest, PaginateResponse
10
10
 
11
11
  NULL_SENTINELS = {"None", "null", ""}
12
12
 
@@ -15,7 +15,7 @@ class _InvalidEnum(Exception):
15
15
  pass
16
16
 
17
17
 
18
- def _apply_order(pagination, entity, query):
18
+ def _apply_order(pagination: PaginateRequest, entity, query):
19
19
  if pagination.sort:
20
20
  order_clauses = []
21
21
  for sort in pagination.sort:
@@ -177,3 +177,13 @@ def apply_pagination(pagination: PaginateRequest, entity, session: Session, limi
177
177
  if limit:
178
178
  query = query.limit(pagination.limit).offset(pagination.offset)
179
179
  return query
180
+
181
+
182
+ def get_paginated_response(pagination: PaginateRequest, entity, session: Session, with_count=True) -> PaginateResponse:
183
+ query = apply_pagination(pagination, entity, session, not with_count)
184
+ total_count = None
185
+ if with_count:
186
+ total_count = query.count()
187
+ query = query.limit(pagination.limit).offset(pagination.offset)
188
+ return PaginateResponse(total_count=total_count, data=query.all(), limit=pagination.limit,
189
+ offset=pagination.offset).to_dict()
@@ -15,8 +15,8 @@ def _guess_env_file() -> str | None:
15
15
  if cwd_env.exists():
16
16
  return str(cwd_env)
17
17
 
18
- for i in range(1, 8):
19
- p = Path(__file__).resolve().parents[i]
18
+ for i in range(0, 8):
19
+ p = Path.cwd().resolve().parents[i]
20
20
  f = p / ".env"
21
21
  if f.exists():
22
22
  return str(f)
@@ -0,0 +1,48 @@
1
+ import functools
2
+
3
+ from nlbone.adapters.auth import KeycloakAuthService
4
+ from nlbone.interfaces.api.exceptions import ForbiddenException, UnauthorizedException
5
+ from nlbone.utils.context import current_request
6
+
7
+
8
+ def client_has_access(*, permissions=None):
9
+ def decorator(func):
10
+ @functools.wraps(func)
11
+ def wrapper(*args, **kwargs):
12
+ request = current_request()
13
+ if not KeycloakAuthService().client_has_access(request.state.token, permissions=permissions):
14
+ raise ForbiddenException(f"Forbidden {permissions}")
15
+
16
+ return func(*args, **kwargs)
17
+
18
+ return wrapper
19
+ return decorator
20
+
21
+
22
+
23
+ def user_authenticated(func):
24
+ @functools.wraps(func)
25
+ async def wrapper(*args, **kwargs):
26
+ request = current_request()
27
+ if not request.state.user_id:
28
+ raise UnauthorizedException()
29
+ return await func(*args, **kwargs)
30
+
31
+ return wrapper
32
+
33
+
34
+ def has_access(*, permissions=None):
35
+ def decorator(func):
36
+ @functools.wraps(func)
37
+ def wrapper(*args, **kwargs):
38
+ request = current_request()
39
+ if not request.state.user_id:
40
+ raise UnauthorizedException()
41
+ if not KeycloakAuthService().has_access(request.state.token, permissions=permissions):
42
+ raise ForbiddenException(f"Forbidden {permissions}")
43
+
44
+ return func(*args, **kwargs)
45
+
46
+ return wrapper
47
+ return decorator
48
+
@@ -0,0 +1,67 @@
1
+ from typing import Any, Iterable
2
+ from fastapi import HTTPException, status
3
+
4
+
5
+ def _error_entry(loc: Iterable[Any] | None, msg: str, type_: str) -> dict:
6
+ return {
7
+ "loc": list(loc) if loc else [],
8
+ "msg": msg,
9
+ "type": type_,
10
+ }
11
+
12
+
13
+ def _errors(loc: Iterable[Any] | None, msg: str, type_: str) -> list[dict]:
14
+ return [_error_entry(loc, msg, type_)]
15
+
16
+
17
+ class BadRequestException(HTTPException):
18
+ def __init__(self, msg: str):
19
+ super().__init__(
20
+ status_code=status.HTTP_400_BAD_REQUEST,
21
+ detail=msg,
22
+ )
23
+
24
+
25
+ class UnauthorizedException(HTTPException):
26
+ def __init__(self, msg: str = "unauthorized"):
27
+ super().__init__(
28
+ status_code=status.HTTP_401_UNAUTHORIZED,
29
+ detail=msg,
30
+ )
31
+
32
+
33
+ class ForbiddenException(HTTPException):
34
+ def __init__(self, msg: str = "forbidden"):
35
+ super().__init__(
36
+ status_code=status.HTTP_403_FORBIDDEN,
37
+ detail=msg,
38
+ )
39
+
40
+
41
+ class NotFoundException(HTTPException):
42
+ def __init__(self, msg: str = "not found"):
43
+ super().__init__(
44
+ status_code=status.HTTP_404_NOT_FOUND,
45
+ detail=msg,
46
+ )
47
+
48
+
49
+ class ConflictException(HTTPException):
50
+ def __init__(self, msg: str = "conflict"):
51
+ super().__init__(
52
+ status_code=status.HTTP_409_CONFLICT,
53
+ detail=msg,
54
+ )
55
+
56
+
57
+ class UnprocessableEntityException(HTTPException):
58
+ def __init__(self, msg: str, loc: Iterable[Any] | None = None, type_: str = "unprocessable_entity"):
59
+ super().__init__(
60
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
61
+ detail=_errors(loc, msg, type_),
62
+ )
63
+
64
+
65
+ class LogicalValidationException(UnprocessableEntityException):
66
+ def __init__(self, msg: str, loc: Iterable[Any] | None = None, type_: str = "logical_error"):
67
+ super().__init__(msg=msg, loc=loc, type_=type_)
@@ -0,0 +1,9 @@
1
+ from fastapi import Depends
2
+
3
+ from .offset_base import PaginateResponse, PaginateRequest
4
+
5
+
6
+ def get_pagination(
7
+ req: PaginateRequest = Depends(PaginateRequest)
8
+ ) -> PaginateRequest:
9
+ return req
@@ -1,6 +1,3 @@
1
- # src/nlbone/interfaces/api/pagination.py
2
- from __future__ import annotations
3
-
4
1
  import json
5
2
  from math import ceil
6
3
  from typing import Any, Optional
@@ -21,7 +18,7 @@ class PaginateRequest:
21
18
  limit: int = 10,
22
19
  offset: int = 0,
23
20
  sort: Optional[str] = None,
24
- filters: Optional[str] = Query(None, description="e.g. title:abc or JSON"),
21
+ filters: Optional[str] = Query(None, description="e.g. title:abc"),
25
22
  ) -> None:
26
23
  self.limit = max(0, limit)
27
24
  self.offset = max(0, offset)
@@ -78,26 +75,6 @@ class PaginateRequest:
78
75
  filters_dict[key] = value_cast
79
76
  return filters_dict
80
77
 
81
- # Helpers for SQLAlchemy usage:
82
- def build_order_by(self, model) -> list[Any]:
83
- """
84
- Translate parsed sort to SQLAlchemy order_by list.
85
- Example: model.created_at.desc(), model.id.asc()
86
- """
87
- items: list[Any] = []
88
- for item in self.sort:
89
- field = getattr(model, item["field"], None)
90
- if field is None:
91
- continue
92
- items.append(desc(field) if item["order"] == "desc" else asc(field))
93
- return items
94
-
95
- def filter_kwargs(self) -> dict[str, Any]:
96
- """
97
- Return simple equality filters (for model.filter_by(**kwargs)).
98
- For advanced ops, extend this to parse operators (gt, lt, like, in, etc.).
99
- """
100
- return {k: v for k, v in self.filters.items() if k != "$"}
101
78
 
102
79
 
103
80
  class PaginateResponse:
@@ -1,8 +0,0 @@
1
- from fastapi import HTTPException, status
2
-
3
- class UnprocessableEntityException(HTTPException):
4
- def __init__(self, detail: str, loc: list[str] | None = None):
5
- super().__init__(
6
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
7
- detail={"msg": detail, "loc": loc or []},
8
- )
@@ -1 +0,0 @@
1
- from .offset_base import PaginateResponse, PaginateRequest
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes