django-lambda-tasks 0.6.1__tar.gz → 0.6.3__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 (103) hide show
  1. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/PKG-INFO +1 -1
  2. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/handler.py +25 -8
  3. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/local_executor.py +7 -0
  4. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/pyproject.toml +1 -1
  5. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_memory_limit.py +67 -3
  6. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.github/workflows/ci.yml +0 -0
  7. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.github/workflows/release.yml +0 -0
  8. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.gitignore +0 -0
  9. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/async-local-execution/.config.kiro +0 -0
  10. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/async-local-execution/design.md +0 -0
  11. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/async-local-execution/requirements.md +0 -0
  12. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/async-local-execution/tasks.md +0 -0
  13. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/deferred-task-enqueue/.config.kiro +0 -0
  14. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/deferred-task-enqueue/design.md +0 -0
  15. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/deferred-task-enqueue/requirements.md +0 -0
  16. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/deferred-task-enqueue/tasks.md +0 -0
  17. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/eager-mode-example-app/.config.kiro +0 -0
  18. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/eager-mode-example-app/design.md +0 -0
  19. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/eager-mode-example-app/requirements.md +0 -0
  20. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/eager-mode-example-app/tasks.md +0 -0
  21. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/ignore-errors-decorator-option/.config.kiro +0 -0
  22. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/ignore-errors-decorator-option/design.md +0 -0
  23. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/ignore-errors-decorator-option/requirements.md +0 -0
  24. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/ignore-errors-decorator-option/tasks.md +0 -0
  25. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/import-string-task-resolution/.config.kiro +0 -0
  26. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/import-string-task-resolution/design.md +0 -0
  27. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/import-string-task-resolution/requirements.md +0 -0
  28. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/import-string-task-resolution/tasks.md +0 -0
  29. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/retry-delay/.config.kiro +0 -0
  30. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/retry-delay/design.md +0 -0
  31. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/retry-delay/requirements.md +0 -0
  32. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/retry-delay/tasks.md +0 -0
  33. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/rse-background-tasks/.config.kiro +0 -0
  34. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/rse-background-tasks/design.md +0 -0
  35. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/rse-background-tasks/requirements.md +0 -0
  36. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/rse-background-tasks/tasks.md +0 -0
  37. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/rse-background-tasks-bugfix/.config.kiro +0 -0
  38. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/rse-background-tasks-bugfix/bugfix.md +0 -0
  39. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/rse-background-tasks-bugfix/design.md +0 -0
  40. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/rse-background-tasks-bugfix/tasks.md +0 -0
  41. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/singleton-task/.config.kiro +0 -0
  42. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/singleton-task/design.md +0 -0
  43. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/singleton-task/requirements.md +0 -0
  44. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/singleton-task/tasks.md +0 -0
  45. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/ssm-environment-loader/.config.kiro +0 -0
  46. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/ssm-environment-loader/design.md +0 -0
  47. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/ssm-environment-loader/requirements.md +0 -0
  48. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/ssm-environment-loader/tasks.md +0 -0
  49. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/task-retry/.config.kiro +0 -0
  50. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/task-retry/design.md +0 -0
  51. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/task-retry/requirements.md +0 -0
  52. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/specs/task-retry/tasks.md +0 -0
  53. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/steering/product.md +0 -0
  54. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/steering/structure.md +0 -0
  55. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.kiro/steering/tech.md +0 -0
  56. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.pre-commit-config.yaml +0 -0
  57. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/.vscode/settings.json +0 -0
  58. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/README.md +0 -0
  59. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/example/README.md +0 -0
  60. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/example/example_app/__init__.py +0 -0
  61. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/example/example_app/apps.py +0 -0
  62. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/example/example_app/tasks.py +0 -0
  63. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/example/example_app/urls.py +0 -0
  64. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/example/example_app/views.py +0 -0
  65. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/example/example_project/__init__.py +0 -0
  66. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/example/example_project/settings.py +0 -0
  67. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/example/example_project/urls.py +0 -0
  68. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/example/example_project/wsgi.py +0 -0
  69. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/example/manage.py +0 -0
  70. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/__init__.py +0 -0
  71. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/admin.py +0 -0
  72. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/apps.py +0 -0
  73. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/decorators.py +0 -0
  74. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/environment_loader.py +0 -0
  75. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/logging.py +0 -0
  76. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/migrations/0001_initial.py +0 -0
  77. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/migrations/0002_alter_taskrecord_status.py +0 -0
  78. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/migrations/__init__.py +0 -0
  79. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/models.py +0 -0
  80. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/secret_loader.py +0 -0
  81. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/settings.py +0 -0
  82. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/tasks.py +0 -0
  83. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/lambda_tasks/timeouts.py +0 -0
  84. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/conftest.py +0 -0
  85. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/settings.py +0 -0
  86. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_admin.py +0 -0
  87. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_batch.py +0 -0
  88. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_batch_worker.py +0 -0
  89. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_decorator.py +0 -0
  90. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_decorators.py +0 -0
  91. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_deferred_enqueue.py +0 -0
  92. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_environment_loader.py +0 -0
  93. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_handler.py +0 -0
  94. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_kwargs_only.py +0 -0
  95. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_local_executor.py +0 -0
  96. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_logging.py +0 -0
  97. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_models.py +0 -0
  98. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_secret_loader.py +0 -0
  99. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_serializer.py +0 -0
  100. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_settings.py +0 -0
  101. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_tasks.py +0 -0
  102. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_timeout_validation.py +0 -0
  103. {django_lambda_tasks-0.6.1 → django_lambda_tasks-0.6.3}/tests/test_timeouts.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-lambda-tasks
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary: Run async tasks in a lambda function
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: awslambdaric
@@ -17,6 +17,7 @@ import os
17
17
  import resource
18
18
  import sys
19
19
  import uuid
20
+ from pathlib import Path
20
21
 
21
22
  import django
22
23
  from django.apps import apps
@@ -31,27 +32,43 @@ _cold_start_done: bool = False
31
32
 
32
33
  _MEMORY_RESERVED_MB = 128
33
34
  _MEMORY_MINIMUM_LIMIT_MB = 64
35
+ _CGROUP_MEMORY_MAX_PATH = Path("/sys/fs/cgroup/memory.max")
36
+
37
+
38
+ def _get_memory_mb() -> int | None:
39
+ """Get container memory limit from Lambda env var or cgroup v2."""
40
+ memory_mb_str = os.environ.get("AWS_LAMBDA_FUNCTION_MEMORY_SIZE")
41
+ if memory_mb_str is not None:
42
+ return int(memory_mb_str)
43
+
44
+ try:
45
+ value = _CGROUP_MEMORY_MAX_PATH.read_text().strip()
46
+ if value == "max":
47
+ return None
48
+ return int(value) // (1024 * 1024)
49
+ except (FileNotFoundError, ValueError):
50
+ return None
34
51
 
35
52
 
36
53
  def _set_memory_limit() -> None:
37
- """Set RLIMIT_DATA from AWS_LAMBDA_FUNCTION_MEMORY_SIZE if available.
54
+ """Set RLIMIT_AS from the container memory limit.
38
55
 
39
- When running in Lambda, this causes Python to raise MemoryError on
40
- excessive allocation instead of being killed by the OOM killer.
56
+ On Lambda, reads AWS_LAMBDA_FUNCTION_MEMORY_SIZE. On Fargate/Batch,
57
+ reads the cgroup v2 memory limit. This causes Python to raise
58
+ MemoryError on excessive allocation instead of being killed by the
59
+ OOM killer.
41
60
 
42
61
  128 MB is reserved for the Python runtime, shared libraries, and OS
43
- overhead. The limit is floored at 64 MB so that even small Lambdas
62
+ overhead. The limit is floored at 64 MB so that even small containers
44
63
  get some protection.
45
64
  """
46
- memory_mb = os.environ.get("AWS_LAMBDA_FUNCTION_MEMORY_SIZE")
65
+ memory_mb = _get_memory_mb()
47
66
 
48
67
  if memory_mb is None:
49
68
  return
50
69
 
51
70
  limit_bytes = (
52
- max(int(memory_mb) - _MEMORY_RESERVED_MB, _MEMORY_MINIMUM_LIMIT_MB)
53
- * 1024
54
- * 1024
71
+ max(memory_mb - _MEMORY_RESERVED_MB, _MEMORY_MINIMUM_LIMIT_MB) * 1024 * 1024
55
72
  )
56
73
  resource.setrlimit(resource.RLIMIT_AS, (limit_bytes, limit_bytes))
57
74
  logger.info(
@@ -23,11 +23,18 @@ def _pool_initializer() -> None:
23
23
  atexit as a fallback). This prevents workers from being killed
24
24
  mid-operation and leaking semaphores.
25
25
 
26
+ Closes all inherited database connections before setting up Django.
27
+ On Linux (fork-based spawning), child processes inherit copies of the
28
+ parent's DB connections which are in an inconsistent state.
29
+
26
30
  Then sets up Django for task execution.
27
31
  """
28
32
  signal.signal(signal.SIGINT, signal.SIG_IGN)
29
33
 
30
34
  import django
35
+ from django.db import connections
36
+
37
+ connections.close_all()
31
38
 
32
39
  django.setup()
33
40
 
@@ -7,7 +7,7 @@ packages = ["lambda_tasks"]
7
7
 
8
8
  [project]
9
9
  name = "django-lambda-tasks"
10
- version = "0.6.1"
10
+ version = "0.6.3"
11
11
  description = "Run async tasks in a lambda function"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"
@@ -7,11 +7,24 @@ killer.
7
7
  """
8
8
 
9
9
  import resource
10
- from unittest.mock import patch
11
10
 
12
11
  import lambda_tasks.handler as handler_module
13
12
 
14
13
 
14
+ class _FakePath:
15
+ def __init__(self, *, content: str | None) -> None:
16
+ self._content = content
17
+
18
+ def read_text(self) -> str:
19
+ if self._content is None:
20
+ raise FileNotFoundError
21
+ return self._content
22
+
23
+
24
+ def fake_path(content: str | None) -> _FakePath:
25
+ return _FakePath(content=content)
26
+
27
+
15
28
  class TestMemoryLimitSetDuringColdStart:
16
29
  def test_rlimit_as_set_from_env_var(self, monkeypatch):
17
30
  """When AWS_LAMBDA_FUNCTION_MEMORY_SIZE is set, RLIMIT_AS is configured with 128 MB reserved."""
@@ -35,14 +48,65 @@ class TestMemoryLimitSetDuringColdStart:
35
48
  expected = (512 - 128) * 1024 * 1024
36
49
  assert calls == [(resource.RLIMIT_AS, (expected, expected))]
37
50
 
38
- def test_rlimit_as_not_set_when_env_var_missing(self, monkeypatch):
39
- """When AWS_LAMBDA_FUNCTION_MEMORY_SIZE is not set, RLIMIT_AS is unchanged."""
51
+ def test_rlimit_as_not_set_when_env_var_missing_and_no_cgroup(self, monkeypatch):
52
+ """When AWS_LAMBDA_FUNCTION_MEMORY_SIZE is not set and cgroup is unavailable, RLIMIT_AS is unchanged."""
53
+ monkeypatch.delenv("DJANGO_SETTINGS_MODULE", raising=False)
54
+ monkeypatch.delenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", raising=False)
55
+ monkeypatch.setattr("lambda_tasks.handler.resolve_environment", lambda: None)
56
+ monkeypatch.setattr(
57
+ "lambda_tasks.handler.resolve_secrets_into_env", lambda: None
58
+ )
59
+ monkeypatch.setattr(handler_module, "_CGROUP_MEMORY_MAX_PATH", fake_path(None))
60
+
61
+ calls: list[tuple] = []
62
+
63
+ def fake_setrlimit(which: int, limits: tuple[int, int]) -> None:
64
+ calls.append((which, limits))
65
+
66
+ monkeypatch.setattr(resource, "setrlimit", fake_setrlimit)
67
+
68
+ handler_module._cold_start_done = False
69
+ handler_module.handler(event={"Records": []}, context=None)
70
+
71
+ assert calls == []
72
+
73
+ def test_rlimit_as_set_from_cgroup(self, monkeypatch):
74
+ """When AWS_LAMBDA_FUNCTION_MEMORY_SIZE is not set, falls back to cgroup v2."""
75
+ monkeypatch.delenv("DJANGO_SETTINGS_MODULE", raising=False)
76
+ monkeypatch.delenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", raising=False)
77
+ monkeypatch.setattr("lambda_tasks.handler.resolve_environment", lambda: None)
78
+ monkeypatch.setattr(
79
+ "lambda_tasks.handler.resolve_secrets_into_env", lambda: None
80
+ )
81
+ # 2 GB in bytes
82
+ monkeypatch.setattr(
83
+ handler_module,
84
+ "_CGROUP_MEMORY_MAX_PATH",
85
+ fake_path(str(2048 * 1024 * 1024)),
86
+ )
87
+
88
+ calls: list[tuple] = []
89
+
90
+ def fake_setrlimit(which: int, limits: tuple[int, int]) -> None:
91
+ calls.append((which, limits))
92
+
93
+ monkeypatch.setattr(resource, "setrlimit", fake_setrlimit)
94
+
95
+ handler_module._cold_start_done = False
96
+ handler_module.handler(event={"Records": []}, context=None)
97
+
98
+ expected = (2048 - 128) * 1024 * 1024
99
+ assert calls == [(resource.RLIMIT_AS, (expected, expected))]
100
+
101
+ def test_rlimit_as_not_set_when_cgroup_is_max(self, monkeypatch):
102
+ """When cgroup memory.max is 'max' (unlimited), RLIMIT_AS is unchanged."""
40
103
  monkeypatch.delenv("DJANGO_SETTINGS_MODULE", raising=False)
41
104
  monkeypatch.delenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", raising=False)
42
105
  monkeypatch.setattr("lambda_tasks.handler.resolve_environment", lambda: None)
43
106
  monkeypatch.setattr(
44
107
  "lambda_tasks.handler.resolve_secrets_into_env", lambda: None
45
108
  )
109
+ monkeypatch.setattr(handler_module, "_CGROUP_MEMORY_MAX_PATH", fake_path("max"))
46
110
 
47
111
  calls: list[tuple] = []
48
112