quickbase-extract 0.4.2__tar.gz → 0.4.4__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.
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/CHANGELOG.md +15 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/PKG-INFO +1 -1
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/pyproject.toml +1 -1
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/src/quickbase_extract/cache_manager.py +2 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/src/quickbase_extract/cache_orchestration.py +9 -3
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/src/quickbase_extract/report_data.py +14 -10
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/.editorconfig +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/.gitignore +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/.pre-commit-config.yaml +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/.python-version +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/LICENSE.txt +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/README.md +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/TODO.md +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/src/quickbase_extract/__init__.py +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/src/quickbase_extract/api_handlers.py +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/src/quickbase_extract/cache_sync.py +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/src/quickbase_extract/config.py +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/src/quickbase_extract/py.typed +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/src/quickbase_extract/report_metadata.py +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/src/quickbase_extract/utils.py +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/tests/conftest.py +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/tests/test_api_handlers.py +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/tests/test_cache_manager.py +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/tests/test_cache_orchestration.py +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/tests/test_cache_sync.py +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/tests/test_report_data.py +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/tests/test_report_metadata.py +0 -0
- {quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/tests/test_utils.py +0 -0
|
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.4] - 2026-05-13
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Changed `ask_values` parameter type annotations from `dict` to `Mapping` in `_refresh_data_cache` and `ensure_cache_freshness` in `cache_orchestration` to match `report_data` changes and accept any mapping type
|
|
13
|
+
- Fixed `data_reasons` variable in `ensure_cache_freshness` being potentially unbound if accessed outside the `data_caching_enabled` block; initialised to `[]` alongside other data cache variables
|
|
14
|
+
- Added `None` guard in `CacheManager._sync_to_s3` to raise `RuntimeError` with a clear message if called when `s3_client` is not configured, resolving type checker error and preventing unclear `AttributeError` at runtime
|
|
15
|
+
|
|
16
|
+
## [0.4.3] - 2026-05-13
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Changed `ask_values` parameter type annotations from `dict` to `Mapping` in `get_data`, `get_data_parallel`, `_validate_ask_values`, `_normalize_ask_values`, and `_replace_ask_placeholders` to accept any mapping type and correctly reflect read-only intent; added `from collections.abc import Mapping` import
|
|
21
|
+
- Added `None` guard for `query_data` before subscripting in `get_data` to prevent `TypeError` when `handle_query` returns `None`
|
|
22
|
+
|
|
8
23
|
## [0.4.2] - 2026-05-13
|
|
9
24
|
|
|
10
25
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quickbase-extract
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: Extract and cache Quickbase report data with built-in error handling and S3 support
|
|
5
5
|
Project-URL: Homepage, https://github.com/tbrezler/quickbase-extract
|
|
6
6
|
Project-URL: Repository, https://github.com/tbrezler/quickbase-extract.git
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "quickbase-extract"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.4"
|
|
8
8
|
description = "Extract and cache Quickbase report data with built-in error handling and S3 support"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -173,6 +173,8 @@ class CacheManager:
|
|
|
173
173
|
Raises:
|
|
174
174
|
Exception: If upload fails. This is critical - Lambda /tmp is ephemeral.
|
|
175
175
|
"""
|
|
176
|
+
if self.s3_client is None:
|
|
177
|
+
raise RuntimeError("_sync_to_s3 called but s3_client is not configured")
|
|
176
178
|
try:
|
|
177
179
|
relative_path = file_path.relative_to(self.cache_root)
|
|
178
180
|
s3_key = f"{self.s3_prefix}/{relative_path}" if self.s3_prefix else str(relative_path)
|
{quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/src/quickbase_extract/cache_orchestration.py
RENAMED
|
@@ -6,6 +6,7 @@ metadata and data caches. Ensures caches are up-to-date before processing.
|
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
|
+
from collections.abc import Mapping
|
|
9
10
|
|
|
10
11
|
from quickbase_extract.cache_manager import DEFAULT_DATA_STALE_HOURS, DEFAULT_METADATA_STALE_HOURS, CacheManager
|
|
11
12
|
from quickbase_extract.config import ReportConfig
|
|
@@ -123,7 +124,7 @@ def _refresh_data_cache(
|
|
|
123
124
|
cache_manager: CacheManager,
|
|
124
125
|
reports_to_refresh: list[ReportConfig],
|
|
125
126
|
reasons: list[str],
|
|
126
|
-
ask_values:
|
|
127
|
+
ask_values: Mapping[ReportConfig, Mapping[str, str | list[str]]] | None = None,
|
|
127
128
|
) -> None:
|
|
128
129
|
"""Refresh data cache for specified reports.
|
|
129
130
|
|
|
@@ -132,8 +133,12 @@ def _refresh_data_cache(
|
|
|
132
133
|
cache_manager: CacheManager instance.
|
|
133
134
|
reports_to_refresh: Reports to refresh data for.
|
|
134
135
|
reasons: List of reasons for refresh (for logging).
|
|
135
|
-
ask_values: Optional
|
|
136
|
+
ask_values: Optional mapping of ReportConfig -> ask_values mapping.
|
|
136
137
|
Per-report "ask the user" filter values.
|
|
138
|
+
data cache. Example: {
|
|
139
|
+
ReportConfig("bq8x", "Accounts", "Python"): {"ask1": "abc"},
|
|
140
|
+
ReportConfig("bq9y", "Contacts", "Active"): {"ask1": "def"}
|
|
141
|
+
}
|
|
137
142
|
|
|
138
143
|
Raises:
|
|
139
144
|
CacheRefreshError: If data refresh fails.
|
|
@@ -164,7 +169,7 @@ def ensure_cache_freshness(
|
|
|
164
169
|
cache_manager: CacheManager,
|
|
165
170
|
report_configs_all: list[ReportConfig],
|
|
166
171
|
report_configs_to_cache: list[ReportConfig] | None = None,
|
|
167
|
-
ask_values:
|
|
172
|
+
ask_values: Mapping[ReportConfig, Mapping[str, str | list[str]]] | None = None,
|
|
168
173
|
metadata_stale_hours: float | None = None,
|
|
169
174
|
data_stale_hours: float | None = None,
|
|
170
175
|
cache_all_data: bool = False,
|
|
@@ -277,6 +282,7 @@ def ensure_cache_freshness(
|
|
|
277
282
|
# Check data cache state and determine refresh needs (only if data caching enabled)
|
|
278
283
|
data_needs_refresh = False
|
|
279
284
|
reports_to_refresh_data = []
|
|
285
|
+
data_reasons: list[str] = []
|
|
280
286
|
data_age = None
|
|
281
287
|
|
|
282
288
|
if data_caching_enabled:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
5
|
import re
|
|
6
|
+
from collections.abc import Mapping
|
|
6
7
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
7
8
|
|
|
8
9
|
from quickbase_extract.api_handlers import handle_query
|
|
@@ -13,7 +14,7 @@ logger = logging.getLogger(__name__)
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
def _validate_ask_values(
|
|
16
|
-
ask_values:
|
|
17
|
+
ask_values: Mapping[str, str | list[str]],
|
|
17
18
|
placeholders_in_filter: set[str],
|
|
18
19
|
report_config: ReportConfig,
|
|
19
20
|
) -> None:
|
|
@@ -57,7 +58,7 @@ def _validate_ask_values(
|
|
|
57
58
|
)
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
def _normalize_ask_values(ask_values:
|
|
61
|
+
def _normalize_ask_values(ask_values: Mapping[str, str | list[str]]) -> dict[str, list[str]]:
|
|
61
62
|
"""Normalize all ask_values to lists for consistent processing.
|
|
62
63
|
|
|
63
64
|
Converts single string values to single-element lists, leaving list values
|
|
@@ -81,7 +82,7 @@ def _normalize_ask_values(ask_values: dict[str, str | list[str]]) -> dict[str, l
|
|
|
81
82
|
|
|
82
83
|
def _replace_ask_placeholders(
|
|
83
84
|
report_filter: str,
|
|
84
|
-
ask_values:
|
|
85
|
+
ask_values: Mapping[str, str | list[str]],
|
|
85
86
|
report_config: ReportConfig,
|
|
86
87
|
) -> str:
|
|
87
88
|
"""Replace ask-the-user placeholders in a Quickbase filter with actual values.
|
|
@@ -215,7 +216,7 @@ def get_data(
|
|
|
215
216
|
report_config: ReportConfig,
|
|
216
217
|
report_metadata: dict[ReportConfig, dict],
|
|
217
218
|
cache: bool = False,
|
|
218
|
-
ask_values:
|
|
219
|
+
ask_values: Mapping[str, str | list[str]] | None = None,
|
|
219
220
|
) -> list[dict]:
|
|
220
221
|
"""Query a Quickbase table for data using cached report metadata.
|
|
221
222
|
|
|
@@ -270,6 +271,8 @@ def get_data(
|
|
|
270
271
|
where=report_filter,
|
|
271
272
|
sort_by=info["sort_by"],
|
|
272
273
|
)
|
|
274
|
+
if query_data is None:
|
|
275
|
+
raise ValueError(f"Query returned no response for {report_config}")
|
|
273
276
|
data = query_data["data"]
|
|
274
277
|
|
|
275
278
|
# Transform records
|
|
@@ -300,18 +303,19 @@ def get_data_parallel(
|
|
|
300
303
|
report_metadata: dict,
|
|
301
304
|
cache: bool = False,
|
|
302
305
|
max_workers: int = 8,
|
|
303
|
-
ask_values:
|
|
306
|
+
ask_values: Mapping[ReportConfig, Mapping[str, str | list[str]]] | None = None,
|
|
304
307
|
) -> dict[ReportConfig, list[dict]]:
|
|
305
308
|
"""Fetch multiple reports in parallel using cached report metadata.
|
|
306
309
|
|
|
307
310
|
Executes data fetching for multiple reports concurrently to improve
|
|
308
|
-
performance. Uses a fail-fast approach: if any report fetch fails,
|
|
309
|
-
|
|
311
|
+
performance. Uses a fail-fast approach: if any report fetch fails, the exception is raised
|
|
312
|
+
immediately and pending (not yet started) tasks will not be executed. Already
|
|
313
|
+
running tasks will complete before the exception propagates.
|
|
310
314
|
|
|
311
315
|
Args:
|
|
312
316
|
client: Quickbase API client. Should be thread-safe for concurrent use.
|
|
313
317
|
cache_manager: CacheManager instance for cache operations.
|
|
314
|
-
|
|
318
|
+
report_configs: List of ReportConfig instances to fetch.
|
|
315
319
|
report_metadata: Full metadata dict (from load_report_metadata_batch).
|
|
316
320
|
Keyed by ReportConfig instances.
|
|
317
321
|
cache: Whether to cache retrieved data. Defaults to False.
|
|
@@ -331,7 +335,7 @@ def get_data_parallel(
|
|
|
331
335
|
KeyError: If any report_config not found in report_metadata.
|
|
332
336
|
ValueError: If ask placeholders in any filter are missing values.
|
|
333
337
|
Exception: First exception encountered during parallel execution.
|
|
334
|
-
|
|
338
|
+
Pending tasks are not started; already running tasks complete first.
|
|
335
339
|
|
|
336
340
|
Example:
|
|
337
341
|
>>> cache_manager = CacheManager(cache_root=Path("my_project/dev/cache"))
|
|
@@ -440,7 +444,7 @@ def load_data_batch(
|
|
|
440
444
|
|
|
441
445
|
Args:
|
|
442
446
|
cache_manager: CacheManager instance for cache operations.
|
|
443
|
-
|
|
447
|
+
report_configs: List of ReportConfig instances to load.
|
|
444
448
|
report_metadata: Dict mapping ReportConfig -> metadata dict
|
|
445
449
|
(from load_report_metadata_batch).
|
|
446
450
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quickbase_extract-0.4.2 → quickbase_extract-0.4.4}/src/quickbase_extract/report_metadata.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|