quickbase-extract 0.4.0__tar.gz → 0.4.2__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.0 → quickbase_extract-0.4.2}/.pre-commit-config.yaml +1 -1
  2. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/CHANGELOG.md +17 -3
  3. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/PKG-INFO +4 -4
  4. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/README.md +1 -1
  5. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/pyproject.toml +3 -3
  6. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/src/quickbase_extract/api_handlers.py +3 -3
  7. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/src/quickbase_extract/cache_orchestration.py +2 -2
  8. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/src/quickbase_extract/report_data.py +1 -1
  9. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/src/quickbase_extract/report_metadata.py +7 -4
  10. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/tests/conftest.py +0 -2
  11. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/tests/test_report_metadata.py +8 -5
  12. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/.editorconfig +0 -0
  13. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/.gitignore +0 -0
  14. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/.python-version +0 -0
  15. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/LICENSE.txt +0 -0
  16. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/TODO.md +0 -0
  17. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/src/quickbase_extract/__init__.py +0 -0
  18. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/src/quickbase_extract/cache_manager.py +0 -0
  19. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/src/quickbase_extract/cache_sync.py +0 -0
  20. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/src/quickbase_extract/config.py +0 -0
  21. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/src/quickbase_extract/py.typed +0 -0
  22. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/src/quickbase_extract/utils.py +0 -0
  23. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/tests/test_api_handlers.py +0 -0
  24. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/tests/test_cache_manager.py +0 -0
  25. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/tests/test_cache_orchestration.py +0 -0
  26. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/tests/test_cache_sync.py +0 -0
  27. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/tests/test_report_data.py +0 -0
  28. {quickbase_extract-0.4.0 → quickbase_extract-0.4.2}/tests/test_utils.py +0 -0
@@ -9,7 +9,7 @@ repos:
9
9
  - id: debug-statements # Prevents accidental pdb/breakpoint commits
10
10
 
11
11
  - repo: https://github.com/astral-sh/ruff-pre-commit
12
- rev: v0.1.11
12
+ rev: v0.15.11
13
13
  hooks:
14
14
  - id: ruff
15
15
  args: [--fix]
@@ -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.2] - 2026-05-13
9
+
10
+ ### Fixed
11
+
12
+ - 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`
13
+ - Fixed api_handlers functions to properly indicate they can return `None` on all code paths
14
+
15
+ ## [0.4.1] - 2026-05-01
16
+
17
+ ### Fixed
18
+
19
+ - `load_report_metadata()` return type changed from `dict` to `dict[ReportConfig, dict]` to match `load_report_metadata_batch()` and ensure consistent API with `get_data()`
20
+ - Fixed incompatibility where `load_report_metadata()` result could not be directly passed to `get_data()` due to format mismatch
21
+
8
22
  ## [0.4.0] - 2026-04-29
9
23
 
10
24
  ### Added
@@ -107,9 +121,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
107
121
 
108
122
  - `cache_freshness.py` module — functionality consolidated into `cache_manager.py` (use `ensure_cache_freshness()` instead)
109
123
  - `check_cache_freshness()`, `get_cache_files()`, `get_cache_summary()` functions — use `CacheManager` methods directly or `ensure_cache_freshness()` for orchestration
110
- - `refresh_all()` function**: Use `ensure_cache_freshness()` for cache management
111
- - `client.py` module**: Users must create Quickbase clients directly using `quickbase-api` package
112
- - `get_cache_manager()` singleton**: Users must create `CacheManager` instances explicitly
124
+ - `refresh_all()` function\*\*: Use `ensure_cache_freshness()` for cache management
125
+ - `client.py` module\*\*: Users must create Quickbase clients directly using `quickbase-api` package
126
+ - `get_cache_manager()` singleton\*\*: Users must create `CacheManager` instances explicitly
113
127
  - `find_report()` function from utils - no longer needed with `ReportConfig`
114
128
  - Dict-based report config format - all configs must use `ReportConfig` NamedTuple
115
129
  - Nested `report` object from metadata - simplified structure
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quickbase-extract
3
- Version: 0.4.0
3
+ Version: 0.4.2
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
@@ -20,8 +20,8 @@ Requires-Dist: boto3>=1.26.0
20
20
  Requires-Dist: quickbase-api>=0.3.1
21
21
  Provides-Extra: dev
22
22
  Requires-Dist: pytest-cov>=4.0; extra == 'dev'
23
- Requires-Dist: pytest>=7.0; extra == 'dev'
24
- Requires-Dist: ruff>=0.1.0; extra == 'dev'
23
+ Requires-Dist: pytest>=9.0.3; extra == 'dev'
24
+ Requires-Dist: ruff>=0.15.11; extra == 'dev'
25
25
  Description-Content-Type: text/markdown
26
26
 
27
27
  # Quickbase Extract
@@ -47,7 +47,7 @@ pip install quickbase-extract
47
47
 
48
48
  ### Requirements
49
49
 
50
- - Python 3.9+
50
+ - Python 3.12+
51
51
  - `quickbase-api` - Quickbase API client
52
52
  - `boto3` - AWS SDK (for Lambda/S3 support)
53
53
 
@@ -21,7 +21,7 @@ pip install quickbase-extract
21
21
 
22
22
  ### Requirements
23
23
 
24
- - Python 3.9+
24
+ - Python 3.12+
25
25
  - `quickbase-api` - Quickbase API client
26
26
  - `boto3` - AWS SDK (for Lambda/S3 support)
27
27
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "quickbase-extract"
7
- version = "0.4.0"
7
+ version = "0.4.2"
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"
@@ -29,9 +29,9 @@ dependencies = [
29
29
 
30
30
  [project.optional-dependencies]
31
31
  dev = [
32
- "pytest>=7.0",
32
+ "pytest>=9.0.3",
33
33
  "pytest-cov>=4.0",
34
- "ruff>=0.1.0",
34
+ "ruff>=0.15.11",
35
35
  ]
36
36
 
37
37
  [project.urls]
@@ -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,
@@ -300,7 +300,7 @@ def get_data_parallel(
300
300
  report_metadata: dict,
301
301
  cache: bool = False,
302
302
  max_workers: int = 8,
303
- ask_values: dict[ReportConfig, dict[str, str | list[str]] | None] | None = None,
303
+ ask_values: dict[ReportConfig, dict[str, str | list[str]]] | None = None,
304
304
  ) -> dict[ReportConfig, list[dict]]:
305
305
  """Fetch multiple reports in parallel using cached report metadata.
306
306
 
@@ -238,7 +238,7 @@ def get_report_metadata_parallel(
238
238
  def load_report_metadata(
239
239
  cache_manager: CacheManager,
240
240
  report_config: ReportConfig,
241
- ) -> dict:
241
+ ) -> dict[ReportConfig, dict]:
242
242
  """Load cached report metadata from disk.
243
243
 
244
244
  Args:
@@ -246,7 +246,8 @@ def load_report_metadata(
246
246
  report_config: ReportConfig identifying the report to load.
247
247
 
248
248
  Returns:
249
- Dict containing table ID, field mappings, query config, and filters.
249
+ Dict mapping ReportConfig -> metadata dict (table ID, field mappings,
250
+ query config, and filters).
250
251
 
251
252
  Raises:
252
253
  FileNotFoundError: If cached metadata does not exist.
@@ -255,6 +256,7 @@ def load_report_metadata(
255
256
  >>> cache_manager = CacheManager(cache_root=Path("my_project/dev/cache"))
256
257
  >>> config = ReportConfig("bq8xyx9z", "Accounts", "Python")
257
258
  >>> metadata = load_report_metadata(cache_manager, config)
259
+ >>> config_metadata = metadata[config]
258
260
  """
259
261
  # Normalize names to match how they were saved
260
262
  app_name = normalize_name(report_config.app_name)
@@ -268,7 +270,8 @@ def load_report_metadata(
268
270
  f"Report metadata not found for {report_config}. Run get_report_metadata() first. Expected: {md_path}"
269
271
  )
270
272
 
271
- return json.loads(cache_manager.read_file(md_path))
273
+ metadata_dict = json.loads(cache_manager.read_file(md_path))
274
+ return {report_config: metadata_dict}
272
275
 
273
276
 
274
277
  def load_report_metadata_batch(
@@ -304,7 +307,7 @@ def load_report_metadata_batch(
304
307
 
305
308
  metadata: dict[ReportConfig, dict] = {}
306
309
  for config in report_configs:
307
- metadata[config] = load_report_metadata(cache_manager, config)
310
+ metadata.update(load_report_metadata(cache_manager, config))
308
311
 
309
312
  return metadata
310
313
 
@@ -218,5 +218,3 @@ def mock_s3_client():
218
218
  from unittest.mock import MagicMock
219
219
 
220
220
  return MagicMock()
221
- return MagicMock()
222
- return MagicMock()
@@ -300,10 +300,12 @@ class TestLoadReportMetadata:
300
300
  # Now load it
301
301
  metadata = load_report_metadata(cache_mgr, config)
302
302
 
303
- assert metadata["table_id"] == "tblXYZ123"
304
- assert metadata["table_name"] == "test_table"
305
- assert "fields" in metadata
306
- assert "filter" in metadata
303
+ # Metadata is now dict[ReportConfig, dict]
304
+ assert config in metadata
305
+ assert metadata[config]["table_id"] == "tblXYZ123"
306
+ assert metadata[config]["table_name"] == "test_table"
307
+ assert "fields" in metadata[config]
308
+ assert "filter" in metadata[config]
307
309
 
308
310
  def test_load_nonexistent_metadata(self, temp_cache_dir, sample_report_configs):
309
311
  """Test error when loading non-cached metadata."""
@@ -335,7 +337,8 @@ class TestLoadReportMetadata:
335
337
 
336
338
  # Should be able to load with original config
337
339
  metadata = load_report_metadata(cache_mgr, config)
338
- assert metadata is not None
340
+ assert config in metadata
341
+ assert metadata[config] is not None
339
342
 
340
343
 
341
344
  class TestLoadReportMetadataBatch: