quickbase-extract 0.4.1__tar.gz → 0.4.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 (28) hide show
  1. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/CHANGELOG.md +14 -0
  2. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/PKG-INFO +1 -1
  3. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/pyproject.toml +1 -1
  4. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/src/quickbase_extract/api_handlers.py +3 -3
  5. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/src/quickbase_extract/cache_orchestration.py +2 -2
  6. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/src/quickbase_extract/report_data.py +14 -10
  7. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/.editorconfig +0 -0
  8. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/.gitignore +0 -0
  9. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/.pre-commit-config.yaml +0 -0
  10. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/.python-version +0 -0
  11. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/LICENSE.txt +0 -0
  12. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/README.md +0 -0
  13. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/TODO.md +0 -0
  14. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/src/quickbase_extract/__init__.py +0 -0
  15. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/src/quickbase_extract/cache_manager.py +0 -0
  16. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/src/quickbase_extract/cache_sync.py +0 -0
  17. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/src/quickbase_extract/config.py +0 -0
  18. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/src/quickbase_extract/py.typed +0 -0
  19. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/src/quickbase_extract/report_metadata.py +0 -0
  20. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/src/quickbase_extract/utils.py +0 -0
  21. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/tests/conftest.py +0 -0
  22. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/tests/test_api_handlers.py +0 -0
  23. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/tests/test_cache_manager.py +0 -0
  24. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/tests/test_cache_orchestration.py +0 -0
  25. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/tests/test_cache_sync.py +0 -0
  26. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/tests/test_report_data.py +0 -0
  27. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/tests/test_report_metadata.py +0 -0
  28. {quickbase_extract-0.4.1 → quickbase_extract-0.4.3}/tests/test_utils.py +0 -0
@@ -5,6 +5,20 @@ 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.3] - 2026-05-13
9
+
10
+ ### Fixed
11
+
12
+ - 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
13
+ - Added `None` guard for `query_data` before subscripting in `get_data` to prevent `TypeError` when `handle_query` returns `None`
14
+
15
+ ## [0.4.2] - 2026-05-13
16
+
17
+ ### Fixed
18
+
19
+ - Fixed type signature of `get_data_parallel()` `ask_values` parameter from `dict[ReportConfig, dict[str, str | list[str]] | None] | None` to `dict[ReportConfig, dict[str, str | list[str]]] | None` to correctly reflect that inner dict values should not be `None`
20
+ - Fixed api_handlers functions to properly indicate they can return `None` on all code paths
21
+
8
22
  ## [0.4.1] - 2026-05-01
9
23
 
10
24
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quickbase-extract
3
- Version: 0.4.1
3
+ Version: 0.4.3
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.1"
7
+ version = "0.4.3"
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"
@@ -26,7 +26,7 @@ def handle_upsert(
26
26
  data: list[dict],
27
27
  description: str = "",
28
28
  max_retries: int = 3,
29
- ) -> dict:
29
+ ) -> dict | None:
30
30
  """Execute a Quickbase upsert with error handling, retry logic, and logging.
31
31
 
32
32
  Retries on rate limiting (429 errors) with exponential backoff and jitter.
@@ -85,7 +85,7 @@ def handle_delete(
85
85
  where: str,
86
86
  description: str = "",
87
87
  max_retries: int = 3,
88
- ) -> int:
88
+ ) -> int | None:
89
89
  """Execute a Quickbase delete with error handling, logging, and rate limit retry.
90
90
 
91
91
  Only retries on rate limiting (429 errors) with exponential backoff and jitter.
@@ -144,7 +144,7 @@ def handle_query(
144
144
  options: dict | None = None,
145
145
  description: str = "",
146
146
  max_retries: int = 3,
147
- ) -> dict:
147
+ ) -> dict | None:
148
148
  """Execute a Quickbase query with error handling, retry logic, and logging.
149
149
 
150
150
  Retries on rate limiting (429 errors) with exponential backoff and jitter.
@@ -123,7 +123,7 @@ def _refresh_data_cache(
123
123
  cache_manager: CacheManager,
124
124
  reports_to_refresh: list[ReportConfig],
125
125
  reasons: list[str],
126
- ask_values: dict[ReportConfig, dict[str, str | list[str]] | None] | None = None,
126
+ ask_values: dict[ReportConfig, dict[str, str | list[str]]] | None = None,
127
127
  ) -> None:
128
128
  """Refresh data cache for specified reports.
129
129
 
@@ -164,7 +164,7 @@ def ensure_cache_freshness(
164
164
  cache_manager: CacheManager,
165
165
  report_configs_all: list[ReportConfig],
166
166
  report_configs_to_cache: list[ReportConfig] | None = None,
167
- ask_values: dict[ReportConfig, dict[str, str | list[str]] | None] | None = None,
167
+ ask_values: dict[ReportConfig, dict[str, str | list[str]]] | None = None,
168
168
  metadata_stale_hours: float | None = None,
169
169
  data_stale_hours: float | None = None,
170
170
  cache_all_data: bool = False,
@@ -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: dict[str, str | list[str]],
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: dict[str, str | list[str]]) -> dict[str, list[str]]:
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: dict[str, str | list[str]],
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: dict[str, str | list[str]] | None = None,
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: dict[ReportConfig, dict[str, str | list[str]] | None] | None = None,
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
- all remaining tasks are cancelled and the exception is raised immediately.
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
- report_config: List of ReportConfig instances to fetch.
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
- All pending tasks are cancelled when an error occurs.
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
- report_config: List of ReportConfig instances to load.
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