aws-python-helper 1.9.3__tar.gz → 1.10.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 (62) hide show
  1. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/PKG-INFO +1 -1
  2. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/api/base.py +79 -5
  3. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/repository/base.py +34 -0
  4. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper.egg-info/PKG-INFO +1 -1
  5. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/pyproject.toml +1 -1
  6. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/README.md +0 -0
  7. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/__init__.py +0 -0
  8. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/api/__init__.py +0 -0
  9. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/api/auth_middleware.py +0 -0
  10. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/api/auth_validators.py +0 -0
  11. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/api/dispatcher.py +0 -0
  12. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/api/exceptions.py +0 -0
  13. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/api/fetcher.py +0 -0
  14. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/api/handler.py +0 -0
  15. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/context/__init__.py +0 -0
  16. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/context/session.py +0 -0
  17. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/context/state_validator.py +0 -0
  18. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/database/__init__.py +0 -0
  19. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/database/database_proxy.py +0 -0
  20. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/database/external_database_proxy.py +0 -0
  21. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/database/external_mongo_manager.py +0 -0
  22. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/database/mongo_manager.py +0 -0
  23. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/fargate/__init__.py +0 -0
  24. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/fargate/executor.py +0 -0
  25. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/fargate/fetcher.py +0 -0
  26. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/fargate/handler.py +0 -0
  27. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/fargate/task_base.py +0 -0
  28. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/invoker/__init__.py +0 -0
  29. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/invoker/api_client.py +0 -0
  30. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/invoker/exceptions.py +0 -0
  31. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/invoker/lambda_invoker.py +0 -0
  32. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/lambda_standalone/__init__.py +0 -0
  33. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/lambda_standalone/base.py +0 -0
  34. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/lambda_standalone/fetcher.py +0 -0
  35. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/lambda_standalone/handler.py +0 -0
  36. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/model_index_sync/__init__.py +0 -0
  37. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/model_index_sync/base.py +0 -0
  38. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/model_query/__init__.py +0 -0
  39. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/model_query/base.py +0 -0
  40. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/monitoring/__init__.py +0 -0
  41. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/monitoring/run.py +0 -0
  42. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/permissions/__init__.py +0 -0
  43. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/permissions/catalog.py +0 -0
  44. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/repository/__init__.py +0 -0
  45. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/repository/audit.py +0 -0
  46. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/sns/__init__.py +0 -0
  47. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/sns/publisher.py +0 -0
  48. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/sqs/__init__.py +0 -0
  49. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/sqs/consumer_base.py +0 -0
  50. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/sqs/fetcher.py +0 -0
  51. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/sqs/handler.py +0 -0
  52. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/utils/__init__.py +0 -0
  53. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/utils/json_encoder.py +0 -0
  54. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/utils/response.py +0 -0
  55. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper/utils/serializer.py +0 -0
  56. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper.egg-info/SOURCES.txt +0 -0
  57. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper.egg-info/dependency_links.txt +0 -0
  58. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper.egg-info/requires.txt +0 -0
  59. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/aws_python_helper.egg-info/top_level.txt +0 -0
  60. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/setup.cfg +0 -0
  61. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/tests/test_audit_stamp.py +0 -0
  62. {aws_python_helper-1.9.3 → aws_python_helper-1.10.0}/tests/test_sns_publisher.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-python-helper
3
- Version: 1.9.3
3
+ Version: 1.10.0
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 @@ API Base Class - Base class for all REST APIs
4
4
 
5
5
  from abc import ABC, abstractmethod
6
6
  import logging
7
- from typing import Dict, Any, Optional, List
7
+ from typing import Dict, Any, Optional, List, Tuple
8
8
  from ..database.mongo_manager import MongoManager
9
9
  from ..database.database_proxy import DatabaseProxy
10
10
  from ..database.external_mongo_manager import ExternalMongoManager
@@ -12,6 +12,18 @@ from ..database.external_database_proxy import ExternalDatabaseProxy
12
12
  from ..context.session import get_session
13
13
 
14
14
 
15
+ def _positive(value: Any) -> Optional[int]:
16
+ """Un entero positivo de la query, o None si no lo es.
17
+
18
+ Llega como texto desde la URL, y puede llegar cualquier cosa.
19
+ """
20
+ try:
21
+ number = int(value)
22
+ except (TypeError, ValueError):
23
+ return None
24
+ return number if number > 0 else None
25
+
26
+
15
27
  class API(ABC):
16
28
  """
17
29
  Base class for all REST APIs
@@ -186,20 +198,82 @@ class API(ABC):
186
198
  """
187
199
  return self._is_authenticated
188
200
 
201
+ # Lo que una página puede pedir. El máximo no es una opinión: es lo que una
202
+ # respuesta de lambda aguanta sin que el navegador se atragante.
203
+ DEFAULT_PAGE_SIZE = 50
204
+ MAX_PAGE_SIZE = 200
205
+
206
+ def page_bounds(self) -> Tuple[int, int]:
207
+ """Qué pedazo pidieron, acotado a algo que se pueda servir.
208
+
209
+ Sale de `page` y `size` de la query, que llegan como texto. Un valor que
210
+ no es un número, o que no tiene sentido, cae al default en vez de fallar:
211
+ un listado que devuelve 400 porque alguien escribió `page=abc` en la URL
212
+ es un listado roto por nada.
213
+ """
214
+ page = max(1, _positive(self.data.get('page')) or 1)
215
+ size = _positive(self.data.get('size')) or self.DEFAULT_PAGE_SIZE
216
+ size = min(self.MAX_PAGE_SIZE, max(1, size))
217
+ return (page - 1) * size, size
218
+
219
+ def paged(
220
+ self, items: List[Any], total: int, key: str = 'items'
221
+ ) -> Dict[str, Any]:
222
+ """The body of a paged listing.
223
+
224
+ The total travels with the page because without it a panel cannot draw a
225
+ paginator, and whoever is looking cannot tell ten of twelve from ten of a
226
+ million.
227
+
228
+ `key` names the list so an endpoint can keep answering `users` or
229
+ `regions` instead of a generic `items`; what a listing is called is part
230
+ of how it reads.
231
+ """
232
+ _, size = self.page_bounds()
233
+ page = max(1, _positive(self.data.get('page')) or 1)
234
+ return {
235
+ key: items,
236
+ 'page': page,
237
+ 'size': size,
238
+ 'total': total,
239
+ 'pages': (total + size - 1) // size if size else 0,
240
+ }
241
+
189
242
  async def granted_permissions(self):
190
243
  """Which permissions the caller holds, or None when this API does not know.
191
244
 
192
- Override it to turn on permission checking for a service. Where the list
193
- comes from is the service's business: its own database, a map cached from
194
- core, whatever fits.
245
+ By default they come from the token: core signs them in, so a service
246
+ authorizes on its own without asking anybody. That is the whole point —
247
+ a service that had to call core on every request would put core back in
248
+ the read path of the entire platform.
249
+
250
+ Override it where the list comes from somewhere else. Core does: it holds
251
+ the roles, so it reads them rather than trusting a claim it just signed.
195
252
 
196
253
  Returning None with AUTHORIZATION=permission is refused, not waved
197
254
  through. An endpoint that cannot say what its caller may do has to be a
198
255
  closed door: the alternative is that forgetting to wire this up leaves it
199
256
  open to anyone holding a token, which is the failure this whole mechanism
200
257
  exists to prevent.
258
+
259
+ A token with no `permissions` claim yields an empty list, not None: it is
260
+ a caller who may do nothing, which is different from an endpoint that
261
+ cannot tell.
201
262
  """
202
- return None
263
+ auth = self.auth_data or {}
264
+
265
+ # La marca de dev y el token maestro responden por todo: son la forma de
266
+ # entrar cuando una configuración de permisos quedó mal.
267
+ if auth.get('is_bypass') or (auth.get('user') or {}).get('is_dev') is True:
268
+ from ..permissions import ALL
269
+ return [ALL]
270
+
271
+ claims = auth.get('token_data')
272
+ if not isinstance(claims, dict):
273
+ return None
274
+
275
+ granted = claims.get('permissions')
276
+ return list(granted) if isinstance(granted, list) else []
203
277
 
204
278
  @property
205
279
  def service_code(self) -> str:
@@ -263,6 +263,40 @@ class Repository(ABC):
263
263
 
264
264
  return self._collection_cache[key]
265
265
 
266
+ async def page(
267
+ self,
268
+ query: Optional[Dict[str, Any]] = None,
269
+ *,
270
+ sort: Optional[List[tuple]] = None,
271
+ skip: int = 0,
272
+ limit: int = 50,
273
+ projection: Optional[Dict[str, Any]] = None,
274
+ ) -> Tuple[List[Dict[str, Any]], int]:
275
+ """A page of this collection and how many there are in total.
276
+
277
+ The count is a separate query and not a `$facet`: counting a filtered
278
+ million is expensive, and putting it in the same pipeline makes the page
279
+ wait for the count.
280
+
281
+ With no filter the total is **estimated**, which reads collection
282
+ metadata instead of the documents. Over a large collection that is the
283
+ difference between instant and a second full read, and the number is only
284
+ there to draw a paginator — nobody needs to know there are exactly
285
+ 1,221,736 rather than about that.
286
+ """
287
+ query = query or {}
288
+ cursor = self.collection.find(query, projection)
289
+ if sort:
290
+ cursor = cursor.sort(sort)
291
+ items = await cursor.skip(skip).limit(limit).to_list(length=limit)
292
+
293
+ total = (
294
+ await self.collection.count_documents(query)
295
+ if query
296
+ else await self.collection.estimated_document_count()
297
+ )
298
+ return items, total
299
+
266
300
  async def ensure_indexes(self, database_name: Optional[str] = None):
267
301
  """
268
302
  Creates all indexes defined in the `indexes` property, blocking until done.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-python-helper
3
- Version: 1.9.3
3
+ Version: 1.10.0
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.9.3"
7
+ version = "1.10.0"
8
8
  description = "AWS Python Helper Framework"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"