strapi-kit 0.0.1__py3-none-any.whl

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 (55) hide show
  1. strapi_kit/__init__.py +97 -0
  2. strapi_kit/__version__.py +15 -0
  3. strapi_kit/_version.py +34 -0
  4. strapi_kit/auth/__init__.py +7 -0
  5. strapi_kit/auth/api_token.py +48 -0
  6. strapi_kit/cache/__init__.py +5 -0
  7. strapi_kit/cache/schema_cache.py +211 -0
  8. strapi_kit/client/__init__.py +11 -0
  9. strapi_kit/client/async_client.py +1032 -0
  10. strapi_kit/client/base.py +460 -0
  11. strapi_kit/client/sync_client.py +980 -0
  12. strapi_kit/config_provider.py +368 -0
  13. strapi_kit/exceptions/__init__.py +37 -0
  14. strapi_kit/exceptions/errors.py +205 -0
  15. strapi_kit/export/__init__.py +10 -0
  16. strapi_kit/export/exporter.py +384 -0
  17. strapi_kit/export/importer.py +619 -0
  18. strapi_kit/export/media_handler.py +322 -0
  19. strapi_kit/export/relation_resolver.py +172 -0
  20. strapi_kit/models/__init__.py +104 -0
  21. strapi_kit/models/bulk.py +69 -0
  22. strapi_kit/models/config.py +174 -0
  23. strapi_kit/models/enums.py +97 -0
  24. strapi_kit/models/export_format.py +166 -0
  25. strapi_kit/models/import_options.py +142 -0
  26. strapi_kit/models/request/__init__.py +1 -0
  27. strapi_kit/models/request/fields.py +65 -0
  28. strapi_kit/models/request/filters.py +611 -0
  29. strapi_kit/models/request/pagination.py +168 -0
  30. strapi_kit/models/request/populate.py +281 -0
  31. strapi_kit/models/request/query.py +429 -0
  32. strapi_kit/models/request/sort.py +147 -0
  33. strapi_kit/models/response/__init__.py +1 -0
  34. strapi_kit/models/response/base.py +75 -0
  35. strapi_kit/models/response/component.py +67 -0
  36. strapi_kit/models/response/media.py +91 -0
  37. strapi_kit/models/response/meta.py +44 -0
  38. strapi_kit/models/response/normalized.py +168 -0
  39. strapi_kit/models/response/relation.py +48 -0
  40. strapi_kit/models/response/v4.py +70 -0
  41. strapi_kit/models/response/v5.py +57 -0
  42. strapi_kit/models/schema.py +93 -0
  43. strapi_kit/operations/__init__.py +16 -0
  44. strapi_kit/operations/media.py +226 -0
  45. strapi_kit/operations/streaming.py +144 -0
  46. strapi_kit/parsers/__init__.py +5 -0
  47. strapi_kit/parsers/version_detecting.py +171 -0
  48. strapi_kit/protocols.py +455 -0
  49. strapi_kit/utils/__init__.py +15 -0
  50. strapi_kit/utils/rate_limiter.py +201 -0
  51. strapi_kit/utils/uid.py +88 -0
  52. strapi_kit-0.0.1.dist-info/METADATA +1098 -0
  53. strapi_kit-0.0.1.dist-info/RECORD +55 -0
  54. strapi_kit-0.0.1.dist-info/WHEEL +4 -0
  55. strapi_kit-0.0.1.dist-info/licenses/LICENSE +21 -0
strapi_kit/__init__.py ADDED
@@ -0,0 +1,97 @@
1
+ """strapi-kit: A modern Python client for Strapi CMS.
2
+
3
+ This package provides a comprehensive interface for interacting with
4
+ Strapi v4 and v5 APIs, including:
5
+ - Synchronous and asynchronous clients
6
+ - Full CRUD operations
7
+ - Import/export functionality
8
+ - Type-safe data models with Pydantic
9
+ - Automatic retry and rate limiting
10
+ """
11
+
12
+ from .__version__ import __version__
13
+ from .client import AsyncClient, SyncClient
14
+ from .config_provider import (
15
+ ConfigFactory,
16
+ ConfigurationError,
17
+ create_config,
18
+ load_config,
19
+ )
20
+ from .exceptions import (
21
+ AuthenticationError,
22
+ AuthorizationError,
23
+ ConflictError,
24
+ FormatError,
25
+ ImportExportError,
26
+ MediaError,
27
+ NetworkError,
28
+ NotFoundError,
29
+ RateLimitError,
30
+ RelationError,
31
+ ServerError,
32
+ StrapiError,
33
+ ValidationError,
34
+ )
35
+ from .export import StrapiExporter, StrapiImporter
36
+ from .models import (
37
+ BulkOperationFailure,
38
+ BulkOperationResult,
39
+ RetryConfig,
40
+ StrapiConfig,
41
+ )
42
+ from .operations.streaming import stream_entities, stream_entities_async
43
+ from .parsers import VersionDetectingParser
44
+ from .protocols import (
45
+ AsyncHTTPClient,
46
+ AuthProvider,
47
+ ConfigProvider,
48
+ HTTPClient,
49
+ ResponseParser,
50
+ SchemaProvider,
51
+ )
52
+
53
+ __all__ = [
54
+ "__version__",
55
+ # Clients
56
+ "SyncClient",
57
+ "AsyncClient",
58
+ # Configuration
59
+ "StrapiConfig",
60
+ "RetryConfig",
61
+ "ConfigFactory",
62
+ "load_config",
63
+ "create_config",
64
+ "ConfigurationError",
65
+ # Bulk Operations
66
+ "BulkOperationResult",
67
+ "BulkOperationFailure",
68
+ # Streaming
69
+ "stream_entities",
70
+ "stream_entities_async",
71
+ # Export/Import
72
+ "StrapiExporter",
73
+ "StrapiImporter",
74
+ # Protocols (for dependency injection)
75
+ "AuthProvider",
76
+ "ConfigProvider",
77
+ "HTTPClient",
78
+ "AsyncHTTPClient",
79
+ "ResponseParser",
80
+ "SchemaProvider",
81
+ # Parsers
82
+ "VersionDetectingParser",
83
+ # Exceptions
84
+ "StrapiError",
85
+ "AuthenticationError",
86
+ "AuthorizationError",
87
+ "NotFoundError",
88
+ "ValidationError",
89
+ "ConflictError",
90
+ "NetworkError",
91
+ "RateLimitError",
92
+ "ServerError",
93
+ "ImportExportError",
94
+ "FormatError",
95
+ "RelationError",
96
+ "MediaError",
97
+ ]
@@ -0,0 +1,15 @@
1
+ """Version information for strapi-kit.
2
+
3
+ This file is automatically updated by hatch-vcs during build.
4
+ For development installs, it falls back to a placeholder version.
5
+ """
6
+
7
+ try:
8
+ # Try to import version from hatch-vcs generated file
9
+ from strapi_kit._version import __version__
10
+ except ImportError:
11
+ # Development mode - not built with hatch-vcs yet
12
+ # This happens when installed with pip install -e .
13
+ __version__ = "0.0.0.dev0+local"
14
+
15
+ __all__ = ["__version__"]
strapi_kit/_version.py ADDED
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '0.0.1'
32
+ __version_tuple__ = version_tuple = (0, 0, 1)
33
+
34
+ __commit_id__ = commit_id = None
@@ -0,0 +1,7 @@
1
+ """Authentication modules for strapi-kit."""
2
+
3
+ from .api_token import APITokenAuth
4
+
5
+ __all__ = [
6
+ "APITokenAuth",
7
+ ]
@@ -0,0 +1,48 @@
1
+ """API Token authentication for Strapi.
2
+
3
+ This module provides bearer token authentication for Strapi API requests.
4
+ """
5
+
6
+
7
+ class APITokenAuth:
8
+ """API Token authentication handler.
9
+
10
+ Manages bearer token authentication for Strapi API requests.
11
+ Tokens can be created in Strapi admin panel under Settings > API Tokens.
12
+
13
+ Example:
14
+ ```python
15
+ auth = APITokenAuth("your-api-token")
16
+ headers = auth.get_headers()
17
+ # {"Authorization": "Bearer your-api-token"}
18
+ ```
19
+ """
20
+
21
+ def __init__(self, token: str) -> None:
22
+ """Initialize API token authentication.
23
+
24
+ Args:
25
+ token: The API token from Strapi
26
+ """
27
+ self.token = token
28
+
29
+ def get_headers(self) -> dict[str, str]:
30
+ """Get authentication headers for requests.
31
+
32
+ Returns:
33
+ Dictionary with Authorization header
34
+ """
35
+ return {"Authorization": f"Bearer {self.token}"}
36
+
37
+ def validate_token(self) -> bool:
38
+ """Validate that the token is not empty.
39
+
40
+ Returns:
41
+ True if token is valid (non-empty), False otherwise
42
+ """
43
+ return bool(self.token and self.token.strip())
44
+
45
+ def __repr__(self) -> str:
46
+ """Return string representation (token masked for security)."""
47
+ masked = f"{self.token[:4]}...{self.token[-4:]}" if len(self.token) > 8 else "****"
48
+ return f"APITokenAuth(token={masked})"
@@ -0,0 +1,5 @@
1
+ """Cache implementations for strapi-kit."""
2
+
3
+ from .schema_cache import InMemorySchemaCache
4
+
5
+ __all__ = ["InMemorySchemaCache"]
@@ -0,0 +1,211 @@
1
+ """In-memory schema cache implementation."""
2
+
3
+ import logging
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from ..exceptions import StrapiError
7
+ from ..models.schema import ContentTypeSchema, FieldSchema, FieldType, RelationType
8
+
9
+ if TYPE_CHECKING:
10
+ from ..client.sync_client import SyncClient
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class InMemorySchemaCache:
16
+ """In-memory cache for content type schemas.
17
+
18
+ Temporary cache for export/import operations only.
19
+ NOT persistent across process restarts.
20
+
21
+ This cache stores content type schemas to enable proper relation
22
+ resolution during import. Schemas are fetched lazily on first access
23
+ and cached for subsequent lookups.
24
+
25
+ Example:
26
+ >>> cache = InMemorySchemaCache(client)
27
+ >>> schema = cache.get_schema("api::article.article")
28
+ >>> target = schema.get_field_target("author") # Returns "api::author.author"
29
+ """
30
+
31
+ def __init__(self, client: "SyncClient") -> None:
32
+ """Initialize the schema cache.
33
+
34
+ Args:
35
+ client: Strapi client for fetching schemas
36
+ """
37
+ self.client = client
38
+ self._cache: dict[str, ContentTypeSchema] = {}
39
+ self._fetch_count = 0
40
+
41
+ def get_schema(self, content_type: str) -> ContentTypeSchema:
42
+ """Get schema (cached or fetch from API).
43
+
44
+ Lazy loading: only fetches on first access.
45
+
46
+ Args:
47
+ content_type: Content type UID (e.g., "api::article.article")
48
+
49
+ Returns:
50
+ Content type schema
51
+
52
+ Raises:
53
+ StrapiError: If schema fetch fails
54
+ """
55
+ # Check cache first (O(1) lookup)
56
+ if content_type in self._cache:
57
+ logger.debug(f"Schema cache hit: {content_type}")
58
+ return self._cache[content_type]
59
+
60
+ # Cache miss - fetch from API
61
+ logger.debug(f"Schema cache miss: {content_type}")
62
+ schema = self._fetch_schema(content_type)
63
+ self._cache[content_type] = schema
64
+ self._fetch_count += 1
65
+ return schema
66
+
67
+ def cache_schema(self, content_type: str, schema: ContentTypeSchema) -> None:
68
+ """Manually cache a schema.
69
+
70
+ Args:
71
+ content_type: Content type UID
72
+ schema: Schema to cache
73
+ """
74
+ self._cache[content_type] = schema
75
+ logger.debug(f"Manually cached schema: {content_type}")
76
+
77
+ def has_schema(self, content_type: str) -> bool:
78
+ """Check if schema is cached.
79
+
80
+ Args:
81
+ content_type: Content type UID
82
+
83
+ Returns:
84
+ True if schema is cached, False otherwise
85
+ """
86
+ return content_type in self._cache
87
+
88
+ def clear_cache(self) -> None:
89
+ """Clear all cached schemas."""
90
+ self._cache.clear()
91
+ self._fetch_count = 0
92
+ logger.debug("Schema cache cleared")
93
+
94
+ def _fetch_schema(self, content_type: str) -> ContentTypeSchema:
95
+ """Fetch schema from Strapi API.
96
+
97
+ Endpoint: GET /api/content-type-builder/content-types/{uid}
98
+
99
+ Args:
100
+ content_type: Content type UID
101
+
102
+ Returns:
103
+ Parsed content type schema
104
+
105
+ Raises:
106
+ StrapiError: If fetch fails
107
+ """
108
+ endpoint = f"content-type-builder/content-types/{content_type}"
109
+
110
+ try:
111
+ response = self.client.get(endpoint)
112
+ except Exception as e:
113
+ raise StrapiError(
114
+ f"Failed to fetch schema for {content_type}",
115
+ details={"content_type": content_type, "error": str(e)},
116
+ ) from e
117
+
118
+ schema_data = response.get("data")
119
+ if not schema_data:
120
+ raise StrapiError(
121
+ f"Invalid schema response for {content_type}",
122
+ details={"response": response},
123
+ )
124
+
125
+ return self._parse_schema_response(content_type, schema_data)
126
+
127
+ def _parse_schema_response(self, uid: str, schema_data: dict[str, Any]) -> ContentTypeSchema:
128
+ """Parse Strapi schema response.
129
+
130
+ Args:
131
+ uid: Content type UID
132
+ schema_data: Schema data from API response
133
+
134
+ Returns:
135
+ Parsed content type schema
136
+ """
137
+ info = schema_data.get("info", {})
138
+ attributes = schema_data.get("attributes", {})
139
+
140
+ fields: dict[str, FieldSchema] = {}
141
+ for field_name, field_data in attributes.items():
142
+ try:
143
+ fields[field_name] = self._parse_field_schema(field_data)
144
+ except Exception as e:
145
+ logger.warning(f"Failed to parse field {field_name}: {e}")
146
+
147
+ return ContentTypeSchema(
148
+ uid=uid,
149
+ display_name=info.get("displayName", uid),
150
+ kind=schema_data.get("kind", "collectionType"),
151
+ singular_name=info.get("singularName"),
152
+ plural_name=info.get("pluralName"),
153
+ fields=fields,
154
+ )
155
+
156
+ def _parse_field_schema(self, field_data: dict[str, Any]) -> FieldSchema:
157
+ """Parse a single field schema.
158
+
159
+ Args:
160
+ field_data: Field data from API response
161
+
162
+ Returns:
163
+ Parsed field schema
164
+ """
165
+ field_type_str = field_data.get("type", "string")
166
+
167
+ # Try to match to FieldType enum, fallback to STRING
168
+ try:
169
+ field_type = FieldType(field_type_str)
170
+ except ValueError:
171
+ logger.warning(f"Unknown field type: {field_type_str}, using STRING")
172
+ field_type = FieldType.STRING
173
+
174
+ schema = FieldSchema(
175
+ type=field_type,
176
+ required=field_data.get("required", False),
177
+ unique=field_data.get("unique", False),
178
+ )
179
+
180
+ # Relation-specific
181
+ if field_type == FieldType.RELATION:
182
+ relation_str = field_data.get("relation")
183
+ if relation_str:
184
+ try:
185
+ schema.relation = RelationType(relation_str)
186
+ except ValueError:
187
+ logger.warning(f"Unknown relation type: {relation_str}")
188
+
189
+ schema.target = field_data.get("target")
190
+ schema.mapped_by = field_data.get("mappedBy")
191
+ schema.inversed_by = field_data.get("inversedBy")
192
+
193
+ return schema
194
+
195
+ @property
196
+ def cache_size(self) -> int:
197
+ """Get number of cached schemas.
198
+
199
+ Returns:
200
+ Number of schemas in cache
201
+ """
202
+ return len(self._cache)
203
+
204
+ @property
205
+ def fetch_count(self) -> int:
206
+ """Get number of schemas fetched from API.
207
+
208
+ Returns:
209
+ Number of API fetches performed
210
+ """
211
+ return self._fetch_count
@@ -0,0 +1,11 @@
1
+ """HTTP client modules for strapi-kit."""
2
+
3
+ from .async_client import AsyncClient
4
+ from .base import BaseClient
5
+ from .sync_client import SyncClient
6
+
7
+ __all__ = [
8
+ "BaseClient",
9
+ "SyncClient",
10
+ "AsyncClient",
11
+ ]