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.
Files changed (104) hide show
  1. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.github/workflows/ci.yml +2 -2
  2. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.pre-commit-config.yaml +0 -9
  3. strapi_kit-0.1.0/CHANGELOG.md +220 -0
  4. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/CLAUDE.md +3 -3
  5. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/Makefile +2 -2
  6. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/PKG-INFO +2 -3
  7. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/README.md +1 -1
  8. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/pyproject.toml +5 -6
  9. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/_version.py +2 -2
  10. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/cache/schema_cache.py +91 -3
  11. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/client/async_client.py +83 -47
  12. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/client/base.py +9 -2
  13. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/client/sync_client.py +23 -12
  14. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/export/__init__.py +3 -1
  15. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/export/exporter.py +160 -4
  16. strapi_kit-0.1.0/src/strapi_kit/export/importer.py +1072 -0
  17. strapi_kit-0.1.0/src/strapi_kit/export/jsonl_reader.py +195 -0
  18. strapi_kit-0.1.0/src/strapi_kit/export/jsonl_writer.py +134 -0
  19. strapi_kit-0.1.0/src/strapi_kit/export/relation_resolver.py +401 -0
  20. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/__init__.py +8 -1
  21. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/export_format.py +13 -0
  22. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/import_options.py +10 -0
  23. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/schema.py +6 -1
  24. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/utils/__init__.py +3 -0
  25. strapi_kit-0.1.0/src/strapi_kit/utils/schema.py +35 -0
  26. strapi_kit-0.0.6/CHANGELOG.md +0 -124
  27. strapi_kit-0.0.6/src/strapi_kit/export/importer.py +0 -619
  28. strapi_kit-0.0.6/src/strapi_kit/export/relation_resolver.py +0 -172
  29. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.coderabbit.yaml +0 -0
  30. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.env.example +0 -0
  31. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.github/dependabot.yml +0 -0
  32. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.github/pull_request_template.md +0 -0
  33. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.github/workflows/dev-release.yml +0 -0
  34. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.github/workflows/document.yml +0 -0
  35. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.github/workflows/guard-main-origin.yml +0 -0
  36. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.github/workflows/release.yml +0 -0
  37. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.gitignore +0 -0
  38. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/.secrets.baseline +0 -0
  39. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/AGENTS.md +0 -0
  40. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/LICENSE +0 -0
  41. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/LLM.md +0 -0
  42. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/codecov.yml +0 -0
  43. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/changelog.md +0 -0
  44. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/configuration.md +0 -0
  45. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/development/architecture.md +0 -0
  46. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/development/contributing.md +0 -0
  47. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/development/release-process.md +0 -0
  48. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/development/testing.md +0 -0
  49. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/export-import.md +0 -0
  50. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/index.md +0 -0
  51. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/installation.md +0 -0
  52. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/media.md +0 -0
  53. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/models.md +0 -0
  54. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/quickstart.md +0 -0
  55. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/docs/stylesheets/extra.css +0 -0
  56. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/MIGRATION_GUIDE.md +0 -0
  57. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/async_operations.py +0 -0
  58. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/basic_crud.py +0 -0
  59. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/config_di_demo.py +0 -0
  60. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/export_import_with_media.py +0 -0
  61. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/export_import_with_schemas.py +0 -0
  62. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/full_migration_v5.py +0 -0
  63. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/simple_migration.py +0 -0
  64. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/examples/verify_installation.py +0 -0
  65. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/mkdocs.yml +0 -0
  66. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/__init__.py +0 -0
  67. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/__version__.py +0 -0
  68. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/auth/__init__.py +0 -0
  69. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/auth/api_token.py +0 -0
  70. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/cache/__init__.py +0 -0
  71. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/client/__init__.py +0 -0
  72. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/config_provider.py +0 -0
  73. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/exceptions/__init__.py +0 -0
  74. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/exceptions/errors.py +0 -0
  75. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/export/media_handler.py +0 -0
  76. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/bulk.py +0 -0
  77. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/config.py +0 -0
  78. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/content_type.py +0 -0
  79. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/enums.py +0 -0
  80. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/request/__init__.py +0 -0
  81. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/request/fields.py +0 -0
  82. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/request/filters.py +0 -0
  83. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/request/pagination.py +0 -0
  84. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/request/populate.py +0 -0
  85. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/request/query.py +0 -0
  86. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/request/sort.py +0 -0
  87. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/__init__.py +0 -0
  88. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/base.py +0 -0
  89. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/component.py +0 -0
  90. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/media.py +0 -0
  91. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/meta.py +0 -0
  92. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/normalized.py +0 -0
  93. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/relation.py +0 -0
  94. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/v4.py +0 -0
  95. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/models/response/v5.py +0 -0
  96. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/operations/__init__.py +0 -0
  97. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/operations/media.py +0 -0
  98. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/operations/streaming.py +0 -0
  99. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/parsers/__init__.py +0 -0
  100. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/parsers/version_detecting.py +0 -0
  101. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/protocols.py +0 -0
  102. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/utils/rate_limiter.py +0 -0
  103. {strapi_kit-0.0.6 → strapi_kit-0.1.0}/src/strapi_kit/utils/seo.py +0 -0
  104. {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
- bandit -c pyproject.toml -r src/
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 (bandit)
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 (bandit on src/ only)
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 bandit only scan `src/` directory (not tests or examples)
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 (bandit)
83
+ security: ## Run security checks (ruff S rules)
84
84
  @echo "$(BLUE)Running security checks...$(NC)"
85
- bandit -c pyproject.toml -r src/
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.6
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 (bandit)
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 (bandit)
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.6'
32
- __version_tuple__ = version_tuple = (0, 0, 6)
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
- info = schema_data.get("info", {})
138
- attributes = schema_data.get("attributes", {})
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=schema_data.get("kind", "collectionType"),
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