jqueue 0.1.0__tar.gz → 1.0.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 (44) hide show
  1. {jqueue-0.1.0 → jqueue-1.0.0}/.github/workflows/release.yml +2 -0
  2. {jqueue-0.1.0 → jqueue-1.0.0}/.gitignore +1 -0
  3. jqueue-1.0.0/AGENTS.md +251 -0
  4. jqueue-1.0.0/PKG-INFO +1026 -0
  5. jqueue-1.0.0/README.md +991 -0
  6. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/__init__.py +7 -0
  7. jqueue-1.0.0/jqueue/_version.py +34 -0
  8. {jqueue-0.1.0 → jqueue-1.0.0}/pyproject.toml +12 -2
  9. jqueue-1.0.0/tools/benchmark_storage.py +720 -0
  10. {jqueue-0.1.0 → jqueue-1.0.0}/uv.lock +1 -2
  11. jqueue-0.1.0/PKG-INFO +0 -712
  12. jqueue-0.1.0/README.md +0 -677
  13. {jqueue-0.1.0 → jqueue-1.0.0}/.github/workflows/ci.yml +0 -0
  14. {jqueue-0.1.0 → jqueue-1.0.0}/LICENSE +0 -0
  15. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/adapters/__init__.py +0 -0
  16. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/adapters/storage/__init__.py +0 -0
  17. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/adapters/storage/filesystem.py +0 -0
  18. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/adapters/storage/gcs.py +0 -0
  19. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/adapters/storage/memory.py +0 -0
  20. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/adapters/storage/s3.py +0 -0
  21. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/core/__init__.py +0 -0
  22. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/core/broker.py +0 -0
  23. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/core/codec.py +0 -0
  24. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/core/direct.py +0 -0
  25. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/core/group_commit.py +0 -0
  26. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/core/heartbeat.py +0 -0
  27. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/domain/__init__.py +0 -0
  28. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/domain/errors.py +0 -0
  29. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/domain/models.py +0 -0
  30. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/ports/__init__.py +0 -0
  31. {jqueue-0.1.0 → jqueue-1.0.0}/jqueue/ports/storage.py +0 -0
  32. {jqueue-0.1.0 → jqueue-1.0.0}/ports-and-adapters.md +0 -0
  33. {jqueue-0.1.0 → jqueue-1.0.0}/tests/__init__.py +0 -0
  34. {jqueue-0.1.0 → jqueue-1.0.0}/tests/test_broker_queue.py +0 -0
  35. {jqueue-0.1.0 → jqueue-1.0.0}/tests/test_codec.py +0 -0
  36. {jqueue-0.1.0 → jqueue-1.0.0}/tests/test_direct_queue.py +0 -0
  37. {jqueue-0.1.0 → jqueue-1.0.0}/tests/test_domain_errors.py +0 -0
  38. {jqueue-0.1.0 → jqueue-1.0.0}/tests/test_domain_models.py +0 -0
  39. {jqueue-0.1.0 → jqueue-1.0.0}/tests/test_group_commit.py +0 -0
  40. {jqueue-0.1.0 → jqueue-1.0.0}/tests/test_heartbeat.py +0 -0
  41. {jqueue-0.1.0 → jqueue-1.0.0}/tests/test_storage_filesystem.py +0 -0
  42. {jqueue-0.1.0 → jqueue-1.0.0}/tests/test_storage_gcs.py +0 -0
  43. {jqueue-0.1.0 → jqueue-1.0.0}/tests/test_storage_memory.py +0 -0
  44. {jqueue-0.1.0 → jqueue-1.0.0}/tests/test_storage_s3.py +0 -0
@@ -60,6 +60,8 @@ jobs:
60
60
  id-token: write # required for OIDC trusted publishing
61
61
  steps:
62
62
  - uses: actions/checkout@v4
63
+ with:
64
+ fetch-depth: 0 # Required for hatch-vcs to access git history
63
65
 
64
66
  - name: Install uv
65
67
  uses: astral-sh/setup-uv@v5
@@ -15,6 +15,7 @@ dist/
15
15
  build/
16
16
  *.egg-info/
17
17
  *.egg
18
+ jqueue/_version.py
18
19
 
19
20
  # uv
20
21
  .uv/
jqueue-1.0.0/AGENTS.md ADDED
@@ -0,0 +1,251 @@
1
+ # AGENTS.md — Coding Agent Guidelines for jqueue
2
+
3
+ This document provides coding agents with essential information about the jqueue codebase: build commands, code style, architecture patterns, and conventions.
4
+
5
+ ## Project Overview
6
+
7
+ **jqueue** is a storage-agnostic async job queue using object storage with compare-and-set (CAS) semantics. It implements the [turbopuffer object-storage queue pattern](https://turbopuffer.com/blog/object-storage-queue).
8
+
9
+ - **Language**: Python 3.12+ (strictly typed)
10
+ - **Framework**: AsyncIO with async/await throughout
11
+ - **Architecture**: Ports & Adapters (Hexagonal Architecture)
12
+ - **Package Manager**: `uv` (NOT pip or poetry)
13
+
14
+ ## Build, Test, and Lint Commands
15
+
16
+ ### Development Setup
17
+ ```bash
18
+ # Install dev dependencies
19
+ uv sync --extra dev
20
+
21
+ # Install all extras (s3, gcs, dev)
22
+ uv sync --extra s3 --extra gcs --extra dev
23
+ ```
24
+
25
+ ### Testing
26
+ ```bash
27
+ # Run all tests
28
+ uv run pytest tests/ -v
29
+
30
+ # Run a single test file
31
+ uv run pytest tests/test_broker_queue.py -v
32
+
33
+ # Run a single test function
34
+ uv run pytest tests/test_broker_queue.py::test_enqueue_returns_job -v
35
+
36
+ # Run tests matching a pattern
37
+ uv run pytest tests/ -v -k "enqueue"
38
+ ```
39
+
40
+ ### Linting and Formatting
41
+ ```bash
42
+ # Check formatting (don't modify files)
43
+ uv run ruff format --check .
44
+
45
+ # Apply formatting
46
+ uv run ruff format .
47
+
48
+ # Lint code (checks E, F, I, UP rules)
49
+ uv run ruff check .
50
+
51
+ # Auto-fix lint issues
52
+ uv run ruff check --fix .
53
+ ```
54
+
55
+ ### Type Checking
56
+ ```bash
57
+ # Run strict type checking
58
+ uv run mypy .
59
+
60
+ # Type check a specific file
61
+ uv run mypy jqueue/core/broker.py
62
+ ```
63
+
64
+ ### Building
65
+ ```bash
66
+ # Build distribution packages
67
+ uv build
68
+ ```
69
+
70
+ ## Architecture Pattern
71
+
72
+ jqueue follows **Ports & Adapters** (Hexagonal Architecture):
73
+
74
+ ```
75
+ jqueue/
76
+ ├── domain/ # Pure domain models and errors (Pydantic-based)
77
+ │ ├── models.py # Job, QueueState, JobStatus
78
+ │ └── errors.py # JQueueError, CASConflictError, JobNotFoundError, StorageError
79
+ ├── ports/ # Protocol interfaces (structural typing)
80
+ │ └── storage.py # ObjectStoragePort (the single port)
81
+ ├── core/ # Business logic
82
+ │ ├── broker.py # BrokerQueue (high-throughput with group commit)
83
+ │ ├── direct.py # DirectQueue (simple one-operation-per-write)
84
+ │ ├── group_commit.py # Group commit batching algorithm
85
+ │ ├── heartbeat.py # HeartbeatManager for job liveness
86
+ │ └── codec.py # JSON serialization
87
+ └── adapters/ # Concrete implementations
88
+ └── storage/
89
+ ├── memory.py # InMemoryStorage (testing)
90
+ ├── filesystem.py # LocalFileSystemStorage (POSIX)
91
+ ├── s3.py # S3Storage (requires aioboto3)
92
+ └── gcs.py # GCSStorage (requires google-cloud-storage)
93
+ ```
94
+
95
+ **Key Principles**:
96
+ - **Immutable domain models**: Pydantic with `frozen=True`
97
+ - **Protocol-based interfaces**: No inheritance, structural typing only
98
+ - **Dependency injection**: Via constructor parameters
99
+ - **Async-first**: All I/O operations use async/await
100
+ - **CAS semantics**: Compare-and-set for concurrency safety
101
+
102
+ ## Code Style Guidelines
103
+
104
+ ### Imports
105
+ - Use absolute imports: `from jqueue.domain.models import Job`
106
+ - Group imports: stdlib → third-party → local
107
+ - Use `from __future__ import annotations` for forward references
108
+ - Ruff enforces isort-style import ordering (rule `I`)
109
+
110
+ ### Type Hints
111
+ - **Strict typing**: All functions require type hints (mypy strict mode)
112
+ - Use `X | None` (not `Optional[X]`) — Python 3.10+ union syntax
113
+ - Use Protocol for interfaces, not abstract base classes
114
+ - Use `tuple[X, ...]` for variable-length homogeneous tuples
115
+ - Use `list[X]`, `dict[K, V]`, not `List`, `Dict` from typing
116
+ - Return type annotations are mandatory, including `-> None`
117
+
118
+ ### Naming Conventions
119
+ - **Functions/methods**: `snake_case`
120
+ - **Classes**: `PascalCase`
121
+ - **Constants**: `UPPER_SNAKE_CASE`
122
+ - **Private attributes**: prefix with single underscore (`_lock`, `_task`)
123
+ - **Domain concepts**: Use domain language (Job, QueueState, entrypoint)
124
+
125
+ ### Docstrings
126
+ - **Module-level**: Explain purpose and usage
127
+ - **Class-level**: Brief description + Parameters section
128
+ - **Public methods**: Use Google/NumPy style with sections:
129
+ ```python
130
+ """
131
+ Brief one-line summary.
132
+
133
+ Longer description if needed.
134
+
135
+ Parameters
136
+ ----------
137
+ param_name : type
138
+ Description
139
+
140
+ Returns
141
+ -------
142
+ return_type
143
+ Description
144
+
145
+ Raises
146
+ ------
147
+ ExceptionType
148
+ When this happens
149
+ """
150
+ ```
151
+ - Private methods may omit docstrings if purpose is obvious
152
+
153
+ ### Error Handling
154
+ - All custom exceptions inherit from `JQueueError`
155
+ - Use specific exceptions: `CASConflictError`, `JobNotFoundError`, `StorageError`
156
+ - Wrap external exceptions in `StorageError` with cause:
157
+ ```python
158
+ try:
159
+ await external_operation()
160
+ except Exception as e:
161
+ raise StorageError("Operation failed", cause=e) from e
162
+ ```
163
+ - Document raised exceptions in docstrings
164
+
165
+ ### Immutability Pattern
166
+ Domain models are frozen (immutable). Use functional update style:
167
+ ```python
168
+ # Good: Return new instance
169
+ updated_job = job.model_copy(update={"status": JobStatus.IN_PROGRESS})
170
+ # or use helper methods
171
+ updated_job = job.with_status(JobStatus.IN_PROGRESS)
172
+
173
+ # Bad: Don't mutate
174
+ job.status = JobStatus.IN_PROGRESS # ❌ Raises FrozenInstanceError
175
+ ```
176
+
177
+ ### Async/Await
178
+ - All I/O operations must be async
179
+ - Use `async with` for context managers (BrokerQueue, HeartbeatManager)
180
+ - Use `asyncio.gather()` for concurrent operations
181
+ - Mark async functions with `async def`, never use sync wrappers
182
+
183
+ ### Data Classes and Models
184
+ - Use `@dataclasses.dataclass` for simple containers
185
+ - Use Pydantic `BaseModel` for domain models (validation, serialization)
186
+ - Pydantic models: set `model_config = ConfigDict(frozen=True)`
187
+ - Dataclasses: no frozen required unless immutability needed
188
+
189
+ ## Testing Conventions
190
+
191
+ ### Test File Organization
192
+ - Location: `/tests/` (flat directory)
193
+ - Naming: `test_<module>.py` (e.g., `test_broker_queue.py`)
194
+ - Use section comments to group related tests:
195
+ ```python
196
+ # ---------------------------------------------------------------------------
197
+ # enqueue operations
198
+ # ---------------------------------------------------------------------------
199
+ ```
200
+
201
+ ### Test Function Naming
202
+ - Descriptive names: `test_enqueue_returns_job`, `test_dequeue_empty_returns_empty_list`
203
+ - Return type annotation: `-> None`
204
+
205
+ ### Test Patterns
206
+ ```python
207
+ async def test_operation_behavior() -> None:
208
+ # Arrange
209
+ storage = InMemoryStorage()
210
+ async with BrokerQueue(storage) as q:
211
+ # Act
212
+ result = await q.enqueue("task", b"data")
213
+
214
+ # Assert
215
+ assert result.status == JobStatus.QUEUED
216
+ ```
217
+
218
+ ### Fixtures and Helpers
219
+ - Use pytest fixtures for shared setup
220
+ - Use `InMemoryStorage()` for tests (zero dependencies)
221
+ - Test async code with `pytest-asyncio` (auto mode enabled)
222
+
223
+ ## Common Patterns
224
+
225
+ ### Creating a Queue
226
+ ```python
227
+ from jqueue import BrokerQueue, InMemoryStorage
228
+
229
+ async with BrokerQueue(InMemoryStorage()) as q:
230
+ job = await q.enqueue("send_email", b'{"to": "user@example.com"}')
231
+ ```
232
+
233
+ ### Implementing a Storage Adapter
234
+ Implement the `ObjectStoragePort` Protocol:
235
+ ```python
236
+ class CustomStorage:
237
+ async def read(self) -> tuple[bytes, str | None]:
238
+ """Return (content, etag). etag is None if object doesn't exist."""
239
+ ...
240
+
241
+ async def write(self, content: bytes, if_match: str | None = None) -> str:
242
+ """CAS write. Raise CASConflictError if if_match doesn't match."""
243
+ ...
244
+ ```
245
+
246
+ ## References
247
+
248
+ - **Architecture**: See `ports-and-adapters.md` for detailed design rationale
249
+ - **README**: Comprehensive usage examples and API documentation
250
+ - **Repository**: https://github.com/janbjorge/jqueue
251
+ - **License**: MIT