aws-python-helper 1.6.0__tar.gz → 1.7.1__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 (58) hide show
  1. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/PKG-INFO +1 -1
  2. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/api/base.py +28 -3
  3. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/api/fetcher.py +29 -24
  4. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/permissions/__init__.py +2 -1
  5. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/permissions/catalog.py +51 -1
  6. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper.egg-info/PKG-INFO +1 -1
  7. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/pyproject.toml +1 -1
  8. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/README.md +0 -0
  9. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/__init__.py +0 -0
  10. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/api/__init__.py +0 -0
  11. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/api/auth_middleware.py +0 -0
  12. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/api/auth_validators.py +0 -0
  13. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/api/dispatcher.py +0 -0
  14. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/api/exceptions.py +0 -0
  15. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/api/handler.py +0 -0
  16. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/context/__init__.py +0 -0
  17. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/context/session.py +0 -0
  18. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/context/state_validator.py +0 -0
  19. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/database/__init__.py +0 -0
  20. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/database/database_proxy.py +0 -0
  21. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/database/external_database_proxy.py +0 -0
  22. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/database/external_mongo_manager.py +0 -0
  23. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/database/mongo_manager.py +0 -0
  24. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/fargate/__init__.py +0 -0
  25. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/fargate/executor.py +0 -0
  26. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/fargate/fetcher.py +0 -0
  27. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/fargate/handler.py +0 -0
  28. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/fargate/task_base.py +0 -0
  29. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/invoker/__init__.py +0 -0
  30. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/invoker/api_client.py +0 -0
  31. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/invoker/exceptions.py +0 -0
  32. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/invoker/lambda_invoker.py +0 -0
  33. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/lambda_standalone/__init__.py +0 -0
  34. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/lambda_standalone/base.py +0 -0
  35. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/lambda_standalone/fetcher.py +0 -0
  36. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/lambda_standalone/handler.py +0 -0
  37. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/model_index_sync/__init__.py +0 -0
  38. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/model_index_sync/base.py +0 -0
  39. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/model_query/__init__.py +0 -0
  40. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/model_query/base.py +0 -0
  41. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/repository/__init__.py +0 -0
  42. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/repository/audit.py +0 -0
  43. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/repository/base.py +0 -0
  44. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/sns/__init__.py +0 -0
  45. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/sns/publisher.py +0 -0
  46. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/sqs/__init__.py +0 -0
  47. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/sqs/consumer_base.py +0 -0
  48. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/sqs/fetcher.py +0 -0
  49. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/sqs/handler.py +0 -0
  50. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/utils/__init__.py +0 -0
  51. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/utils/json_encoder.py +0 -0
  52. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/utils/response.py +0 -0
  53. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper/utils/serializer.py +0 -0
  54. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper.egg-info/SOURCES.txt +0 -0
  55. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper.egg-info/dependency_links.txt +0 -0
  56. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper.egg-info/requires.txt +0 -0
  57. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/aws_python_helper.egg-info/top_level.txt +0 -0
  58. {aws_python_helper-1.6.0 → aws_python_helper-1.7.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-python-helper
3
- Version: 1.6.0
3
+ Version: 1.7.1
4
4
  Summary: AWS Python Helper Framework
5
5
  Author-email: Fabian Claros <neufabiae@gmail.com>
6
6
  License-Expression: MIT
@@ -203,10 +203,14 @@ class API(ABC):
203
203
 
204
204
  @property
205
205
  def service_code(self) -> str:
206
- """Names this service inside a permission, as in `core:GET /users`."""
207
- import os
206
+ """Names this service inside a permission, as in `core:GET /users`.
208
207
 
209
- return os.getenv('SERVICE_CODE', os.getenv('NAMESPACE', 'service'))
208
+ Resolved by the same function the catalog uses, so the name a permission
209
+ is stored under and the name it is demanded by cannot drift.
210
+ """
211
+ from ..permissions import service_code
212
+
213
+ return service_code()
210
214
 
211
215
  @property
212
216
  def auth_data(self) -> Optional[Dict[str, Any]]:
@@ -288,6 +292,27 @@ class API(ABC):
288
292
  'headers': self._response_headers
289
293
  }
290
294
 
295
+ @property
296
+ def public(self) -> bool:
297
+ """
298
+ Whether this endpoint answers without a token.
299
+
300
+ Declared here, next to the code, so the permission catalog can leave it
301
+ out: a permission for something nobody needs permission for is noise in
302
+ every role screen, and an administrator ticking it would be granting
303
+ nothing.
304
+
305
+ This does not make an endpoint public — terraform does, by leaving
306
+ `authorization` unset on its lambda. This says so, so the rest of the
307
+ framework can act on it.
308
+
309
+ class AuthLoginAPI(API):
310
+ @property
311
+ def public(self):
312
+ return True
313
+ """
314
+ return False
315
+
291
316
  @property
292
317
  def schema(self):
293
318
  """
@@ -7,6 +7,34 @@ import importlib.util
7
7
  from pathlib import Path
8
8
  from typing import List
9
9
 
10
+ def load_controller_class(file_path: str):
11
+ """The API class a controller file defines.
12
+
13
+ Shared so the permission catalog asks the class the same questions the
14
+ dispatcher does — a second way of finding it would answer differently the
15
+ day one of them changes.
16
+ """
17
+ spec = importlib.util.spec_from_file_location("api_module", file_path)
18
+ if not spec or not spec.loader:
19
+ raise ImportError(f"Could not load module spec from: {file_path}")
20
+
21
+ module = importlib.util.module_from_spec(spec)
22
+ spec.loader.exec_module(module)
23
+
24
+ for item_name in dir(module):
25
+ item = getattr(module, item_name)
26
+ if (isinstance(item, type) and
27
+ hasattr(item, 'process') and
28
+ item.__name__ not in ['API', 'ABC'] and
29
+ item.__module__ == module.__name__):
30
+ return item
31
+
32
+ raise ValueError(
33
+ f"No API class found in {file_path}\n"
34
+ f"Make sure your file exports a class that inherits from API"
35
+ )
36
+
37
+
10
38
  class Fetcher:
11
39
  """
12
40
  Dynamically load API controllers
@@ -187,30 +215,7 @@ class Fetcher:
187
215
  f"Expected file for endpoint '{self.endpoint}' with method '{self.method}'"
188
216
  )
189
217
 
190
- # Load module dynamically
191
- spec = importlib.util.spec_from_file_location("api_module", file_path)
192
- if not spec or not spec.loader:
193
- raise ImportError(f"Could not load module spec from: {file_path}")
194
-
195
- module = importlib.util.module_from_spec(spec)
196
- spec.loader.exec_module(module)
197
-
198
- # Search for a class that inherits from API
199
- controller_class = None
200
- for item_name in dir(module):
201
- item = getattr(module, item_name)
202
- if (isinstance(item, type) and
203
- hasattr(item, 'process') and
204
- item.__name__ not in ['API', 'ABC'] and
205
- item.__module__ == module.__name__):
206
- controller_class = item
207
- break
208
-
209
- if not controller_class:
210
- raise ValueError(
211
- f"No API class found in {file_path}\n"
212
- f"Make sure your file exports a class that inherits from API"
213
- )
218
+ controller_class = load_controller_class(file_path)
214
219
 
215
220
  # Cache the class (not the instance)
216
221
  self._cache[file_path] = controller_class
@@ -1,9 +1,10 @@
1
- from .catalog import ALL, allows, discover, required_for, service_wildcard
1
+ from .catalog import ALL, allows, discover, required_for, service_code, service_wildcard
2
2
 
3
3
  __all__ = [
4
4
  "ALL",
5
5
  "allows",
6
6
  "discover",
7
7
  "required_for",
8
+ "service_code",
8
9
  "service_wildcard",
9
10
  ]
@@ -12,10 +12,14 @@ otherwise silently stop matching in the other, and an access check that never
12
12
  matches fails open or shut without anyone noticing.
13
13
  """
14
14
 
15
+ import logging
15
16
  import os
17
+ import re
16
18
  from typing import Dict, List
17
19
 
18
- from ..api.fetcher import Fetcher
20
+ from ..api.fetcher import Fetcher, load_controller_class
21
+
22
+ logger = logging.getLogger(__name__)
19
23
 
20
24
  # Cómo se llama el archivo según el método. `list` y `get` son ambos GET: uno
21
25
  # responde la colección, el otro un elemento.
@@ -31,6 +35,19 @@ FILE_METHODS = {
31
35
  ALL = "*"
32
36
 
33
37
 
38
+ def service_code() -> str:
39
+ """The name this service takes inside a permission code, as in `core:GET /users`.
40
+
41
+ One function on purpose. Publishing the catalog and checking a permission are
42
+ two different places, and if each resolves the name its own way one stores
43
+ `core:GET /users` while the other demands `constitution-core:GET /users`.
44
+ Nothing reports that: every role with explicit permissions just gets 403 on
45
+ everything, and a role holding `*` keeps working, so it looks like a problem
46
+ with the role.
47
+ """
48
+ return os.getenv("SERVICE_CODE") or os.getenv("NAMESPACE") or "service"
49
+
50
+
34
51
  def discover(api_root: str, service: str) -> List[Dict[str, str]]:
35
52
  """Every endpoint this service answers, as permission records.
36
53
 
@@ -46,6 +63,10 @@ def discover(api_root: str, service: str) -> List[Dict[str, str]]:
46
63
  if extension != ".py" or stem not in FILE_METHODS:
47
64
  continue
48
65
 
66
+ controller = _controller(os.path.join(current, filename))
67
+ if controller and getattr(controller(), "public", False):
68
+ continue
69
+
49
70
  relative = os.path.relpath(current, api_root)
50
71
  parts = [] if relative == "." else relative.split(os.sep)
51
72
  path = "/" + "/".join(parts)
@@ -62,11 +83,40 @@ def discover(api_root: str, service: str) -> List[Dict[str, str]]:
62
83
  "method": method,
63
84
  "path": path,
64
85
  "scope": "item" if stem == "get" else "collection",
86
+ "name": _readable(controller),
65
87
  })
66
88
 
67
89
  return sorted(found, key=lambda item: item["code"])
68
90
 
69
91
 
92
+ def _controller(file_path: str):
93
+ """The controller class, or None if it cannot be read.
94
+
95
+ None errs towards listing the endpoint. A permission that appears for a
96
+ public endpoint is noise; one that disappears for a protected endpoint
97
+ leaves the check with nothing to match and locks everybody out of it.
98
+ """
99
+ try:
100
+ return load_controller_class(file_path)
101
+ except Exception as exc:
102
+ logger.warning("Could not read %s, listing it anyway: %s", file_path, exc)
103
+ return None
104
+
105
+
106
+ def _readable(controller) -> str:
107
+ """The class name as words: `UsersInviteAPI` -> `Users Invite`.
108
+
109
+ A panel showing raw paths makes people read routing to answer "what does
110
+ this let someone do". The class already names the thing; this only spaces it
111
+ out. Empty when the class could not be read, so the panel falls back to the
112
+ path rather than inventing a name.
113
+ """
114
+ if controller is None:
115
+ return ""
116
+ name = re.sub(r"(API|Api)$", "", controller.__name__)
117
+ return re.sub(r"(?<=[a-z0-9])(?=[A-Z])", " ", name).strip()
118
+
119
+
70
120
  def service_wildcard(service: str) -> str:
71
121
  return f"{service}:*"
72
122
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-python-helper
3
- Version: 1.6.0
3
+ Version: 1.7.1
4
4
  Summary: AWS Python Helper Framework
5
5
  Author-email: Fabian Claros <neufabiae@gmail.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aws-python-helper"
7
- version = "1.6.0"
7
+ version = "1.7.1"
8
8
  description = "AWS Python Helper Framework"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"