strapi-kit 0.0.6__tar.gz → 0.1.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.
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.github/workflows/ci.yml +2 -2
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.pre-commit-config.yaml +0 -9
- strapi_kit-0.1.0/CHANGELOG.md +220 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/CLAUDE.md +3 -3
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/Makefile +2 -2
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/PKG-INFO +2 -3
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/README.md +1 -1
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/pyproject.toml +5 -6
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/_version.py +2 -2
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/cache/schema_cache.py +91 -3
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/client/async_client.py +83 -47
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/client/base.py +9 -2
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/client/sync_client.py +23 -12
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/export/__init__.py +3 -1
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/export/exporter.py +160 -4
- strapi_kit-0.1.0/src/strapi_kit/export/importer.py +1072 -0
- strapi_kit-0.1.0/src/strapi_kit/export/jsonl_reader.py +195 -0
- strapi_kit-0.1.0/src/strapi_kit/export/jsonl_writer.py +134 -0
- strapi_kit-0.1.0/src/strapi_kit/export/relation_resolver.py +401 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/__init__.py +8 -1
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/export_format.py +13 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/import_options.py +10 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/schema.py +6 -1
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/utils/__init__.py +3 -0
- strapi_kit-0.1.0/src/strapi_kit/utils/schema.py +35 -0
- strapi_kit-0.0.6/CHANGELOG.md +0 -124
- strapi_kit-0.0.6/src/strapi_kit/export/importer.py +0 -619
- strapi_kit-0.0.6/src/strapi_kit/export/relation_resolver.py +0 -172
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.coderabbit.yaml +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.env.example +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.github/dependabot.yml +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.github/pull_request_template.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.github/workflows/dev-release.yml +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.github/workflows/document.yml +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.github/workflows/guard-main-origin.yml +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.github/workflows/release.yml +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.gitignore +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.secrets.baseline +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/AGENTS.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/LICENSE +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/LLM.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/codecov.yml +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/changelog.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/configuration.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/development/architecture.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/development/contributing.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/development/release-process.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/development/testing.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/export-import.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/index.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/installation.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/media.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/models.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/quickstart.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/stylesheets/extra.css +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/MIGRATION_GUIDE.md +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/async_operations.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/basic_crud.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/config_di_demo.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/export_import_with_media.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/export_import_with_schemas.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/full_migration_v5.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/simple_migration.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/verify_installation.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/mkdocs.yml +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/__init__.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/__version__.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/auth/__init__.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/auth/api_token.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/cache/__init__.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/client/__init__.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/config_provider.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/exceptions/__init__.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/exceptions/errors.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/export/media_handler.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/bulk.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/config.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/content_type.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/enums.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/request/__init__.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/request/fields.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/request/filters.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/request/pagination.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/request/populate.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/request/query.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/request/sort.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/__init__.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/base.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/component.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/media.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/meta.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/normalized.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/relation.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/v4.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/v5.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/operations/__init__.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/operations/media.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/operations/streaming.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/parsers/__init__.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/parsers/version_detecting.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/protocols.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/utils/rate_limiter.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/utils/seo.py +0 -0
- {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/utils/uid.py +0 -0
|
@@ -57,8 +57,8 @@ jobs:
|
|
|
57
57
|
# Run type checking
|
|
58
58
|
mypy src/strapi_kit/
|
|
59
59
|
|
|
60
|
-
# Run security checks
|
|
61
|
-
|
|
60
|
+
# Run security checks (ruff S rules)
|
|
61
|
+
ruff check src/ --select S
|
|
62
62
|
|
|
63
63
|
# Coverage job - run all tests in one job for simplicity
|
|
64
64
|
coverage:
|
|
@@ -31,15 +31,6 @@ repos:
|
|
|
31
31
|
args: [--fix, --exit-non-zero-on-fix]
|
|
32
32
|
types_or: [python, pyi]
|
|
33
33
|
|
|
34
|
-
# Security checks
|
|
35
|
-
- repo: https://github.com/PyCQA/bandit
|
|
36
|
-
rev: 1.9.3
|
|
37
|
-
hooks:
|
|
38
|
-
- id: bandit
|
|
39
|
-
args: ['-c', 'pyproject.toml']
|
|
40
|
-
additional_dependencies: ['bandit[toml]']
|
|
41
|
-
exclude: ^(tests/|examples/)
|
|
42
|
-
|
|
43
34
|
# Type checking
|
|
44
35
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
45
36
|
rev: v1.19.1
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-02-04
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Race conditions in async bulk operations** ([#30](https://github.com/MehdiZare/strapi-kit/pull/30))
|
|
15
|
+
- Added `asyncio.Lock()` to protect shared state mutations in `bulk_create()`, `bulk_update()`, `bulk_delete()`
|
|
16
|
+
- Ensures thread-safe updates to `completed`, `successes`, `failures` counters
|
|
17
|
+
|
|
18
|
+
- **JSONL media manifest stream consumption** ([#30](https://github.com/MehdiZare/strapi-kit/pull/30))
|
|
19
|
+
- Fixed critical bug where `read_media_manifest()` consumed entity stream
|
|
20
|
+
- Now uses separate reader for media manifest to preserve entity iteration
|
|
21
|
+
|
|
22
|
+
- **V5 string relation ID support** ([#30](https://github.com/MehdiZare/strapi-kit/pull/30))
|
|
23
|
+
- Updated `_extract_ids_from_field()` to accept both `int` and `str` for v5 documentId relations
|
|
24
|
+
- Added `doc_id_to_new_id` mapping to `ImportResult` for v5 string relation resolution
|
|
25
|
+
- `_validate_relations()` now tracks both numeric IDs and documentIds
|
|
26
|
+
|
|
27
|
+
- **JSONL media import metadata preservation** ([#30](https://github.com/MehdiZare/strapi-kit/pull/30))
|
|
28
|
+
- Use `MediaHandler.upload_media_file()` instead of `client.upload_file()` to preserve alt text and captions
|
|
29
|
+
|
|
30
|
+
- **Strict mypy compliance** ([#30](https://github.com/MehdiZare/strapi-kit/pull/30))
|
|
31
|
+
- Changed `self._file: Any` to `IO[str] | None` in `JSONLImportReader` and `JSONLExportWriter`
|
|
32
|
+
- Added type guard for non-dict `info` payloads in `extract_info_from_schema()`
|
|
33
|
+
- Narrowed broad `except Exception` catches to `except StrapiError` in JSONL loops
|
|
34
|
+
|
|
35
|
+
- **Code quality improvements** ([#30](https://github.com/MehdiZare/strapi-kit/pull/30))
|
|
36
|
+
- Created shared `extract_info_from_schema()` utility in `utils/schema.py`
|
|
37
|
+
- Added parent directory creation in `JSONLExportWriter.__enter__()`
|
|
38
|
+
- Use explicit `is not None` checks instead of truthy checks for ID lookups
|
|
39
|
+
- Replaced Unicode multiplication symbol with plain `x` in docstrings
|
|
40
|
+
|
|
41
|
+
- **JSONL import path traversal protection** ([#29](https://github.com/MehdiZare/strapi-kit/pull/29))
|
|
42
|
+
- Added path traversal validation to JSONL media import matching standard import security pattern
|
|
43
|
+
- Uses `resolve()` and `is_relative_to()` to prevent directory traversal attacks
|
|
44
|
+
|
|
45
|
+
- **JSONL import two-pass streaming** ([#29](https://github.com/MehdiZare/strapi-kit/pull/29))
|
|
46
|
+
- Refactored `import_from_jsonl()` to use true O(1) memory with two-pass streaming
|
|
47
|
+
- Pass 1: Create entities, store only ID mappings (old_id → new_id)
|
|
48
|
+
- Pass 2: Re-read file to resolve relations using ID mappings
|
|
49
|
+
- Memory profile reduced from O(entities) to O(entity_count x 2 ints)
|
|
50
|
+
- Fixed: ID mappings now properly copied to `ImportResult` for caller access
|
|
51
|
+
|
|
52
|
+
- **Strapi v5 update endpoint consistency** ([#29](https://github.com/MehdiZare/strapi-kit/pull/29))
|
|
53
|
+
- Fixed UPDATE conflict resolution to use `document_id` instead of numeric ID for endpoint path
|
|
54
|
+
- Added `doc_id_mapping` field to `ImportResult` to track document_ids for v5 endpoints
|
|
55
|
+
- Relation updates now use `document_id` when available (v5) with fallback to numeric ID (v4)
|
|
56
|
+
- Applies to both standard import and JSONL streaming import
|
|
57
|
+
|
|
58
|
+
- **Removed unused test fixtures** ([#29](https://github.com/MehdiZare/strapi-kit/pull/29))
|
|
59
|
+
- Removed unused `mock_media_response` parameter from `test_update_media_not_found` in sync and async tests
|
|
60
|
+
|
|
61
|
+
- **`update_media` version detection** ([#28](https://github.com/MehdiZare/strapi-kit/issues/28))
|
|
62
|
+
- Fixed bug where `update_media()` used wrong endpoint when `api_version="auto"` and no prior API calls
|
|
63
|
+
- Now calls `get_media()` first to trigger version detection before choosing v4 vs v5 endpoint
|
|
64
|
+
|
|
65
|
+
- **Media download streaming** ([#28](https://github.com/MehdiZare/strapi-kit/issues/28))
|
|
66
|
+
- Fixed `download_file()` to stream directly to disk when `save_path` is provided
|
|
67
|
+
- Previously buffered entire file in memory before writing, causing issues with large files
|
|
68
|
+
|
|
69
|
+
- **Async bulk `batch_size` parameter** ([#28](https://github.com/MehdiZare/strapi-kit/issues/28))
|
|
70
|
+
- Fixed `batch_size` parameter in async `bulk_create()`, `bulk_update()`, `bulk_delete()`
|
|
71
|
+
- Now properly processes items in batches to control memory usage
|
|
72
|
+
- `batch_size` controls items per processing wave, `max_concurrency` controls parallel requests within each wave
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
- **Schema-driven relation extraction** ([#28](https://github.com/MehdiZare/strapi-kit/issues/28))
|
|
77
|
+
- `extract_relations_with_schema()` - Extract relations using content type schema for accuracy
|
|
78
|
+
- `strip_relations_with_schema()` - Remove only actual relation fields, preserving non-relation fields
|
|
79
|
+
- Recursive extraction from components and dynamic zones
|
|
80
|
+
- Extended `FieldSchema` with `component`, `components`, and `repeatable` fields
|
|
81
|
+
- Added `get_component_schema()` to schema cache for component schema lookups
|
|
82
|
+
- Exporter now uses schema-aware extraction to avoid false positives
|
|
83
|
+
|
|
84
|
+
- **JSONL streaming export/import** ([#28](https://github.com/MehdiZare/strapi-kit/issues/28))
|
|
85
|
+
- `ExportFormat.JSONL` enum for selecting export format
|
|
86
|
+
- `JSONLExportWriter` - O(1) memory streaming export writer
|
|
87
|
+
- `JSONLImportReader` - O(1) memory streaming import reader
|
|
88
|
+
- `exporter.export_to_jsonl()` - Stream entities to JSONL file as they're fetched
|
|
89
|
+
- `importer.import_from_jsonl()` - Stream import from JSONL file
|
|
90
|
+
- Enables processing exports larger than available RAM
|
|
91
|
+
|
|
92
|
+
- **Import options implementation** ([#28](https://github.com/MehdiZare/strapi-kit/issues/28))
|
|
93
|
+
- `validate_relations` - Pre-import validation that all relation targets exist in export data
|
|
94
|
+
- `overwrite_media` - Check for existing media by hash before uploading (skip duplicates)
|
|
95
|
+
- `batch_size` - Batch-based progress reporting during entity import
|
|
96
|
+
- Added `relations_imported` field to `ImportResult`
|
|
97
|
+
|
|
98
|
+
### Changed
|
|
99
|
+
|
|
100
|
+
- Removed empty leftover directories (`import_export/`, `importexport/`)
|
|
101
|
+
- **Consolidated linting tools into ruff**
|
|
102
|
+
- Replaced bandit with ruff's `S` (flake8-bandit) rules for security checks
|
|
103
|
+
- Removed bandit dependency from dev requirements
|
|
104
|
+
- Updated pre-commit hooks, CI workflow, and Makefile
|
|
105
|
+
|
|
106
|
+
## [0.0.6] - 2026-02-03
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
- **StrapiConfig extra env vars** ([#25](https://github.com/MehdiZare/strapi-kit/issues/25), [#26](https://github.com/MehdiZare/strapi-kit/pull/26))
|
|
110
|
+
- Added `extra="ignore"` to `StrapiConfig` and `RetryConfig` model_config
|
|
111
|
+
- Prevents `ValidationError: Extra inputs are not permitted` when unrelated `STRAPI_*` environment variables exist
|
|
112
|
+
|
|
113
|
+
- **Content type v5 parsing** ([#25](https://github.com/MehdiZare/strapi-kit/issues/25), [#26](https://github.com/MehdiZare/strapi-kit/pull/26))
|
|
114
|
+
- Added `_normalize_content_type_item()` and `_normalize_content_types_list()` helpers
|
|
115
|
+
- Flattens nested `schema` structure returned by Strapi v5 Content-Type Builder API
|
|
116
|
+
- `get_content_types()`, `get_components()`, and `get_content_type_schema()` now work with both v4 and v5
|
|
117
|
+
|
|
118
|
+
- **Exception handling improvements** ([#23](https://github.com/MehdiZare/strapi-kit/pull/23), [#24](https://github.com/MehdiZare/strapi-kit/pull/24))
|
|
119
|
+
- Use `StrapiError` instead of bare `Exception` in examples for precise error handling
|
|
120
|
+
- Catch `PydanticValidationError` specifically in Content-Type Builder parsing
|
|
121
|
+
- Add proper exception chaining when re-raising validation errors
|
|
122
|
+
- Fix docstring to document `ConfigurationError` instead of `ValueError`
|
|
123
|
+
|
|
124
|
+
- **Singularization bug fix** ([#23](https://github.com/MehdiZare/strapi-kit/pull/23), [#24](https://github.com/MehdiZare/strapi-kit/pull/24))
|
|
125
|
+
- Fix `api_id_to_singular()` for `-zzes` endings: `quizzes` → `quiz`, `buzzes` → `buzz`
|
|
126
|
+
- Use length-based heuristic to distinguish single-z doubled vs double-z base words
|
|
127
|
+
|
|
128
|
+
### Changed
|
|
129
|
+
|
|
130
|
+
- **StrEnum migration** ([#26](https://github.com/MehdiZare/strapi-kit/pull/26))
|
|
131
|
+
- Refactored 6 enum classes from `(str, Enum)` to `StrEnum` (Python 3.11+)
|
|
132
|
+
- Affected: `FilterOperator`, `SortDirection`, `PublicationState`, `ConflictResolution`, `FieldType`, `RelationType`
|
|
133
|
+
- Fixes UP042 linting errors in ruff preview mode
|
|
134
|
+
|
|
135
|
+
- Test coverage maintained at 86% (542 passing tests)
|
|
136
|
+
- Added 14 new tests for config extra env vars and v5 content type parsing
|
|
137
|
+
|
|
138
|
+
### Added
|
|
139
|
+
|
|
140
|
+
- **Content-Type Builder API** ([#15](https://github.com/MehdiZare/strapi-kit/issues/15))
|
|
141
|
+
- `get_content_types(include_plugins=False)` - List all content types from Strapi
|
|
142
|
+
- `get_components()` - List all components
|
|
143
|
+
- `get_content_type_schema(uid)` - Get full schema for a content type
|
|
144
|
+
- New models: `ContentTypeListItem`, `ComponentListItem`, `CTBContentTypeSchema`, `CTBContentTypeInfo`
|
|
145
|
+
- Schema helper methods: `get_field_type()`, `is_relation_field()`, `is_component_field()`, `get_relation_target()`, `get_component_uid()`
|
|
146
|
+
- Full async support for all methods
|
|
147
|
+
|
|
148
|
+
- **UID Conversion Utilities** ([#16](https://github.com/MehdiZare/strapi-kit/issues/16))
|
|
149
|
+
- `api_id_to_singular()` - Convert plural API IDs to singular form (handles irregular plurals like people→person, children→child)
|
|
150
|
+
- `uid_to_admin_url()` - Build Strapi admin panel URLs from content type UIDs
|
|
151
|
+
- `uid_to_api_id` - Alias for `uid_to_endpoint` for clarity
|
|
152
|
+
- Export of existing utilities: `extract_model_name()`, `is_api_content_type()`
|
|
153
|
+
|
|
154
|
+
- **SEO Configuration Detection** ([#17](https://github.com/MehdiZare/strapi-kit/issues/17))
|
|
155
|
+
- `detect_seo_configuration()` - Detect SEO setup in content type schemas
|
|
156
|
+
- `SEOConfiguration` dataclass for structured detection results
|
|
157
|
+
- Support for component-based SEO (shared.seo, meta, metadata)
|
|
158
|
+
- Support for flat SEO fields (metaTitle, meta_description, ogTitle, etc.)
|
|
159
|
+
- Case-insensitive matching for field names and component UIDs
|
|
160
|
+
|
|
161
|
+
## [0.0.5] - 2025-01-XX
|
|
162
|
+
|
|
163
|
+
### Added
|
|
164
|
+
|
|
165
|
+
- Retry logic with exponential backoff
|
|
166
|
+
- Rate limit handling with Retry-After support
|
|
167
|
+
- Bulk operations (create, update, delete)
|
|
168
|
+
- Progress callbacks for long operations
|
|
169
|
+
|
|
170
|
+
## [0.0.4] - 2025-01-XX
|
|
171
|
+
|
|
172
|
+
### Added
|
|
173
|
+
|
|
174
|
+
- Media upload/download operations
|
|
175
|
+
- Streaming support for large files
|
|
176
|
+
|
|
177
|
+
## [0.0.3] - 2025-01-XX
|
|
178
|
+
|
|
179
|
+
### Added
|
|
180
|
+
|
|
181
|
+
- Type-safe query builder
|
|
182
|
+
- Response normalization for v4/v5
|
|
183
|
+
|
|
184
|
+
## [0.0.2] - 2025-01-XX
|
|
185
|
+
|
|
186
|
+
### Added
|
|
187
|
+
|
|
188
|
+
- Export/Import functionality with automatic relation resolution
|
|
189
|
+
- Schema caching for efficient content type metadata handling
|
|
190
|
+
- Media file export/download support
|
|
191
|
+
- Full migration examples (simple and production-ready)
|
|
192
|
+
|
|
193
|
+
### Changed
|
|
194
|
+
|
|
195
|
+
- Improved test coverage to 85%
|
|
196
|
+
|
|
197
|
+
## [0.0.1] - 2025-01-XX
|
|
198
|
+
|
|
199
|
+
### Added
|
|
200
|
+
|
|
201
|
+
- Initial release
|
|
202
|
+
- HTTP clients (sync and async)
|
|
203
|
+
- Configuration with Pydantic and environment variable support
|
|
204
|
+
- Authentication (API tokens)
|
|
205
|
+
- Exception hierarchy with semantic error types
|
|
206
|
+
- API version detection (v4/v5)
|
|
207
|
+
- Type-safe query builder with 24 filter operators
|
|
208
|
+
- Response normalization for both Strapi v4 and v5
|
|
209
|
+
- Media upload/download operations
|
|
210
|
+
- Dependency injection support with protocols
|
|
211
|
+
- Full type hints and mypy strict mode compliance
|
|
212
|
+
|
|
213
|
+
[Unreleased]: https://github.com/MehdiZare/strapi-kit/compare/v0.1.0...HEAD
|
|
214
|
+
[0.1.0]: https://github.com/MehdiZare/strapi-kit/compare/v0.0.6...v0.1.0
|
|
215
|
+
[0.0.6]: https://github.com/MehdiZare/strapi-kit/compare/v0.0.5...v0.0.6
|
|
216
|
+
[0.0.5]: https://github.com/MehdiZare/strapi-kit/compare/v0.0.4...v0.0.5
|
|
217
|
+
[0.0.4]: https://github.com/MehdiZare/strapi-kit/compare/v0.0.3...v0.0.4
|
|
218
|
+
[0.0.3]: https://github.com/MehdiZare/strapi-kit/compare/v0.0.2...v0.0.3
|
|
219
|
+
[0.0.2]: https://github.com/MehdiZare/strapi-kit/compare/v0.0.1...v0.0.2
|
|
220
|
+
[0.0.1]: https://github.com/MehdiZare/strapi-kit/releases/tag/v0.0.1
|
|
@@ -530,7 +530,7 @@ make install-hooks
|
|
|
530
530
|
# 1. Format code (ruff format)
|
|
531
531
|
# 2. Fix linting issues (ruff check --fix)
|
|
532
532
|
# 3. Run type checking (mypy)
|
|
533
|
-
# 4. Check for security issues (
|
|
533
|
+
# 4. Check for security issues (ruff S rules)
|
|
534
534
|
# 5. Prevent committing secrets (detect-secrets)
|
|
535
535
|
|
|
536
536
|
# Run hooks manually on all files
|
|
@@ -544,7 +544,7 @@ make update-hooks
|
|
|
544
544
|
- ✅ Code formatting (ruff format)
|
|
545
545
|
- ✅ Linting (ruff check with auto-fix)
|
|
546
546
|
- ✅ Type checking (mypy strict mode on src/ only)
|
|
547
|
-
- ✅ Security issues (
|
|
547
|
+
- ✅ Security issues (ruff S rules on src/ only)
|
|
548
548
|
- ✅ Secrets detection (detect-secrets)
|
|
549
549
|
- ✅ File consistency (trailing whitespace, EOF, YAML/TOML syntax, etc.)
|
|
550
550
|
|
|
@@ -555,7 +555,7 @@ git commit --no-verify
|
|
|
555
555
|
|
|
556
556
|
**Important notes:**
|
|
557
557
|
- Hooks only run on staged files by default (fast)
|
|
558
|
-
- Type checking and
|
|
558
|
+
- Type checking and security checks only scan `src/` directory (not tests or examples)
|
|
559
559
|
- mkdocs.yml is excluded from YAML checks (uses custom tags)
|
|
560
560
|
- Hooks are configured in `.pre-commit-config.yaml`
|
|
561
561
|
- Secrets baseline is stored in `.secrets.baseline`
|
|
@@ -80,9 +80,9 @@ type-check: ## Run type checking with mypy
|
|
|
80
80
|
quality: lint type-check ## Run all quality checks (lint + type-check)
|
|
81
81
|
@echo "$(GREEN)✓ All quality checks passed$(NC)"
|
|
82
82
|
|
|
83
|
-
security: ## Run security checks (
|
|
83
|
+
security: ## Run security checks (ruff S rules)
|
|
84
84
|
@echo "$(BLUE)Running security checks...$(NC)"
|
|
85
|
-
|
|
85
|
+
ruff check src/ --select S
|
|
86
86
|
@echo "$(GREEN)✓ Security checks complete$(NC)"
|
|
87
87
|
|
|
88
88
|
security-baseline: ## Create baseline for detect-secrets
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: strapi-kit
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: A modern Python client for Strapi CMS with import/export capabilities
|
|
5
5
|
Project-URL: Homepage, https://github.com/mehdizare/strapi-kit
|
|
6
6
|
Project-URL: Documentation, https://mehdizare.github.io/strapi-kit/
|
|
@@ -27,7 +27,6 @@ Requires-Dist: python-dateutil>=2.9.0
|
|
|
27
27
|
Requires-Dist: tenacity>=9.0.0
|
|
28
28
|
Requires-Dist: typing-extensions>=4.15.0
|
|
29
29
|
Provides-Extra: dev
|
|
30
|
-
Requires-Dist: bandit[toml]>=1.9.3; extra == 'dev'
|
|
31
30
|
Requires-Dist: detect-secrets>=1.5.0; extra == 'dev'
|
|
32
31
|
Requires-Dist: mypy>=1.19.1; extra == 'dev'
|
|
33
32
|
Requires-Dist: pre-commit>=4.5.0; extra == 'dev'
|
|
@@ -1175,7 +1174,7 @@ pre-commit autoupdate
|
|
|
1175
1174
|
- ✅ Code formatting (ruff format)
|
|
1176
1175
|
- ✅ Linting (ruff check)
|
|
1177
1176
|
- ✅ Type checking (mypy strict mode)
|
|
1178
|
-
- ✅ Security issues (
|
|
1177
|
+
- ✅ Security issues (ruff S rules)
|
|
1179
1178
|
- ✅ Secrets detection (detect-secrets)
|
|
1180
1179
|
- ✅ File consistency (trailing whitespace, EOF, etc.)
|
|
1181
1180
|
|
|
@@ -1129,7 +1129,7 @@ pre-commit autoupdate
|
|
|
1129
1129
|
- ✅ Code formatting (ruff format)
|
|
1130
1130
|
- ✅ Linting (ruff check)
|
|
1131
1131
|
- ✅ Type checking (mypy strict mode)
|
|
1132
|
-
- ✅ Security issues (
|
|
1132
|
+
- ✅ Security issues (ruff S rules)
|
|
1133
1133
|
- ✅ Secrets detection (detect-secrets)
|
|
1134
1134
|
- ✅ File consistency (trailing whitespace, EOF, etc.)
|
|
1135
1135
|
|
|
@@ -40,7 +40,6 @@ dev = [
|
|
|
40
40
|
"mypy>=1.19.1",
|
|
41
41
|
"ruff>=0.14.14",
|
|
42
42
|
"pre-commit>=4.5.0",
|
|
43
|
-
"bandit[toml]>=1.9.3",
|
|
44
43
|
"detect-secrets>=1.5.0",
|
|
45
44
|
"safety>=2.3.0,<4.0.0", # <3.0.0 to avoid API auth requirements in CI
|
|
46
45
|
]
|
|
@@ -94,15 +93,19 @@ select = [
|
|
|
94
93
|
"B", # flake8-bugbear
|
|
95
94
|
"C4", # flake8-comprehensions
|
|
96
95
|
"UP", # pyupgrade
|
|
96
|
+
"S", # flake8-bandit (security)
|
|
97
97
|
]
|
|
98
98
|
ignore = [
|
|
99
99
|
"E501", # line too long (handled by formatter)
|
|
100
100
|
"UP046", # Generic type parameter syntax (Python 3.12+, not using PEP 695 yet)
|
|
101
|
+
"S101", # assert used in tests (ignored via per-file-ignores, kept for explicitness)
|
|
101
102
|
]
|
|
102
103
|
|
|
103
104
|
[tool.ruff.lint.per-file-ignores]
|
|
104
105
|
"__init__.py" = ["F401"] # Allow unused imports in __init__.py
|
|
105
|
-
"_version.py" = ["E", "W", "F", "I", "B", "C4", "UP"] # Auto-generated by hatch-vcs, skip all checks
|
|
106
|
+
"_version.py" = ["E", "W", "F", "I", "B", "C4", "UP", "S"] # Auto-generated by hatch-vcs, skip all checks
|
|
107
|
+
"tests/**" = ["S"] # Skip security checks in tests
|
|
108
|
+
"examples/**" = ["S"] # Skip security checks in examples
|
|
106
109
|
|
|
107
110
|
[tool.pytest.ini_options]
|
|
108
111
|
asyncio_mode = "auto"
|
|
@@ -151,7 +154,3 @@ exclude_lines = [
|
|
|
151
154
|
"if TYPE_CHECKING:",
|
|
152
155
|
"@abstractmethod",
|
|
153
156
|
]
|
|
154
|
-
|
|
155
|
-
[tool.bandit]
|
|
156
|
-
exclude_dirs = ["tests", ".venv", "build", "dist"]
|
|
157
|
-
skips = ["B101"] # Skip assert_used (we use asserts in tests)
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.0
|
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.1.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
5
5
|
|
|
6
6
|
from ..exceptions import StrapiError
|
|
7
7
|
from ..models.schema import ContentTypeSchema, FieldSchema, FieldType, RelationType
|
|
8
|
+
from ..utils.schema import extract_info_from_schema
|
|
8
9
|
|
|
9
10
|
if TYPE_CHECKING:
|
|
10
11
|
from ..client.sync_client import SyncClient
|
|
@@ -36,6 +37,7 @@ class InMemorySchemaCache:
|
|
|
36
37
|
"""
|
|
37
38
|
self.client = client
|
|
38
39
|
self._cache: dict[str, ContentTypeSchema] = {}
|
|
40
|
+
self._component_cache: dict[str, ContentTypeSchema] = {}
|
|
39
41
|
self._fetch_count = 0
|
|
40
42
|
|
|
41
43
|
def get_schema(self, content_type: str) -> ContentTypeSchema:
|
|
@@ -88,9 +90,80 @@ class InMemorySchemaCache:
|
|
|
88
90
|
def clear_cache(self) -> None:
|
|
89
91
|
"""Clear all cached schemas."""
|
|
90
92
|
self._cache.clear()
|
|
93
|
+
self._component_cache.clear()
|
|
91
94
|
self._fetch_count = 0
|
|
92
95
|
logger.debug("Schema cache cleared")
|
|
93
96
|
|
|
97
|
+
def get_component_schema(self, component_uid: str) -> ContentTypeSchema:
|
|
98
|
+
"""Get component schema (cached or fetch from API).
|
|
99
|
+
|
|
100
|
+
Lazy loading: only fetches on first access.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
component_uid: Component UID (e.g., "blog.author-bio")
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Component schema
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
StrapiError: If schema fetch fails
|
|
110
|
+
"""
|
|
111
|
+
# Check cache first
|
|
112
|
+
if component_uid in self._component_cache:
|
|
113
|
+
logger.debug(f"Component schema cache hit: {component_uid}")
|
|
114
|
+
return self._component_cache[component_uid]
|
|
115
|
+
|
|
116
|
+
# Cache miss - fetch from API
|
|
117
|
+
logger.debug(f"Component schema cache miss: {component_uid}")
|
|
118
|
+
schema = self._fetch_component_schema(component_uid)
|
|
119
|
+
self._component_cache[component_uid] = schema
|
|
120
|
+
self._fetch_count += 1
|
|
121
|
+
return schema
|
|
122
|
+
|
|
123
|
+
def has_component_schema(self, component_uid: str) -> bool:
|
|
124
|
+
"""Check if component schema is cached.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
component_uid: Component UID
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
True if schema is cached, False otherwise
|
|
131
|
+
"""
|
|
132
|
+
return component_uid in self._component_cache
|
|
133
|
+
|
|
134
|
+
def _fetch_component_schema(self, component_uid: str) -> ContentTypeSchema:
|
|
135
|
+
"""Fetch component schema from Strapi API.
|
|
136
|
+
|
|
137
|
+
Endpoint: GET /api/content-type-builder/components/{uid}
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
component_uid: Component UID
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Parsed component schema
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
StrapiError: If fetch fails
|
|
147
|
+
"""
|
|
148
|
+
endpoint = f"content-type-builder/components/{component_uid}"
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
response = self.client.get(endpoint)
|
|
152
|
+
except Exception as e:
|
|
153
|
+
raise StrapiError(
|
|
154
|
+
f"Failed to fetch component schema for {component_uid}",
|
|
155
|
+
details={"component_uid": component_uid, "error": str(e)},
|
|
156
|
+
) from e
|
|
157
|
+
|
|
158
|
+
schema_data = response.get("data")
|
|
159
|
+
if not schema_data:
|
|
160
|
+
raise StrapiError(
|
|
161
|
+
f"Invalid component schema response for {component_uid}",
|
|
162
|
+
details={"response": response},
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return self._parse_schema_response(component_uid, schema_data)
|
|
166
|
+
|
|
94
167
|
def _fetch_schema(self, content_type: str) -> ContentTypeSchema:
|
|
95
168
|
"""Fetch schema from Strapi API.
|
|
96
169
|
|
|
@@ -134,8 +207,14 @@ class InMemorySchemaCache:
|
|
|
134
207
|
Returns:
|
|
135
208
|
Parsed content type schema
|
|
136
209
|
"""
|
|
137
|
-
|
|
138
|
-
|
|
210
|
+
# Handle v5 nested schema format (Issue #28)
|
|
211
|
+
if "schema" in schema_data and isinstance(schema_data["schema"], dict):
|
|
212
|
+
schema = schema_data["schema"]
|
|
213
|
+
else:
|
|
214
|
+
schema = schema_data
|
|
215
|
+
|
|
216
|
+
info = extract_info_from_schema(schema)
|
|
217
|
+
attributes = schema.get("attributes", {})
|
|
139
218
|
|
|
140
219
|
fields: dict[str, FieldSchema] = {}
|
|
141
220
|
for field_name, field_data in attributes.items():
|
|
@@ -147,7 +226,7 @@ class InMemorySchemaCache:
|
|
|
147
226
|
return ContentTypeSchema(
|
|
148
227
|
uid=uid,
|
|
149
228
|
display_name=info.get("displayName", uid),
|
|
150
|
-
kind=
|
|
229
|
+
kind=schema.get("kind", "collectionType"),
|
|
151
230
|
singular_name=info.get("singularName"),
|
|
152
231
|
plural_name=info.get("pluralName"),
|
|
153
232
|
fields=fields,
|
|
@@ -190,6 +269,15 @@ class InMemorySchemaCache:
|
|
|
190
269
|
schema.mapped_by = field_data.get("mappedBy")
|
|
191
270
|
schema.inversed_by = field_data.get("inversedBy")
|
|
192
271
|
|
|
272
|
+
# Component-specific
|
|
273
|
+
if field_type == FieldType.COMPONENT:
|
|
274
|
+
schema.component = field_data.get("component")
|
|
275
|
+
schema.repeatable = field_data.get("repeatable", False)
|
|
276
|
+
|
|
277
|
+
# Dynamic zone-specific
|
|
278
|
+
if field_type == FieldType.DYNAMIC_ZONE:
|
|
279
|
+
schema.components = field_data.get("components", [])
|
|
280
|
+
|
|
193
281
|
return schema
|
|
194
282
|
|
|
195
283
|
@property
|