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.
- strapi_kit/__init__.py +97 -0
- strapi_kit/__version__.py +15 -0
- strapi_kit/_version.py +34 -0
- strapi_kit/auth/__init__.py +7 -0
- strapi_kit/auth/api_token.py +48 -0
- strapi_kit/cache/__init__.py +5 -0
- strapi_kit/cache/schema_cache.py +211 -0
- strapi_kit/client/__init__.py +11 -0
- strapi_kit/client/async_client.py +1032 -0
- strapi_kit/client/base.py +460 -0
- strapi_kit/client/sync_client.py +980 -0
- strapi_kit/config_provider.py +368 -0
- strapi_kit/exceptions/__init__.py +37 -0
- strapi_kit/exceptions/errors.py +205 -0
- strapi_kit/export/__init__.py +10 -0
- strapi_kit/export/exporter.py +384 -0
- strapi_kit/export/importer.py +619 -0
- strapi_kit/export/media_handler.py +322 -0
- strapi_kit/export/relation_resolver.py +172 -0
- strapi_kit/models/__init__.py +104 -0
- strapi_kit/models/bulk.py +69 -0
- strapi_kit/models/config.py +174 -0
- strapi_kit/models/enums.py +97 -0
- strapi_kit/models/export_format.py +166 -0
- strapi_kit/models/import_options.py +142 -0
- strapi_kit/models/request/__init__.py +1 -0
- strapi_kit/models/request/fields.py +65 -0
- strapi_kit/models/request/filters.py +611 -0
- strapi_kit/models/request/pagination.py +168 -0
- strapi_kit/models/request/populate.py +281 -0
- strapi_kit/models/request/query.py +429 -0
- strapi_kit/models/request/sort.py +147 -0
- strapi_kit/models/response/__init__.py +1 -0
- strapi_kit/models/response/base.py +75 -0
- strapi_kit/models/response/component.py +67 -0
- strapi_kit/models/response/media.py +91 -0
- strapi_kit/models/response/meta.py +44 -0
- strapi_kit/models/response/normalized.py +168 -0
- strapi_kit/models/response/relation.py +48 -0
- strapi_kit/models/response/v4.py +70 -0
- strapi_kit/models/response/v5.py +57 -0
- strapi_kit/models/schema.py +93 -0
- strapi_kit/operations/__init__.py +16 -0
- strapi_kit/operations/media.py +226 -0
- strapi_kit/operations/streaming.py +144 -0
- strapi_kit/parsers/__init__.py +5 -0
- strapi_kit/parsers/version_detecting.py +171 -0
- strapi_kit/protocols.py +455 -0
- strapi_kit/utils/__init__.py +15 -0
- strapi_kit/utils/rate_limiter.py +201 -0
- strapi_kit/utils/uid.py +88 -0
- strapi_kit-0.0.1.dist-info/METADATA +1098 -0
- strapi_kit-0.0.1.dist-info/RECORD +55 -0
- strapi_kit-0.0.1.dist-info/WHEEL +4 -0
- strapi_kit-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Component and dynamic zone models for Strapi responses.
|
|
2
|
+
|
|
3
|
+
Models for handling components and dynamic zones which have special structure.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Component(BaseModel):
|
|
12
|
+
"""Strapi component.
|
|
13
|
+
|
|
14
|
+
Components are reusable data structures that can be used across content types.
|
|
15
|
+
They have a special '__component' field identifying their type.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
id: Component instance ID
|
|
19
|
+
document_id: Document ID (v5 only)
|
|
20
|
+
component_type: Component type identifier (from __component field)
|
|
21
|
+
Additional fields are allowed based on component schema.
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
>>> component = Component(
|
|
25
|
+
... id=1,
|
|
26
|
+
... component_type="shared.seo",
|
|
27
|
+
... title="SEO Title",
|
|
28
|
+
... description="SEO Description"
|
|
29
|
+
... )
|
|
30
|
+
>>> component.component_type
|
|
31
|
+
'shared.seo'
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
35
|
+
|
|
36
|
+
id: int | None = Field(None, description="Component instance ID")
|
|
37
|
+
document_id: str | None = Field(None, alias="documentId", description="Document ID (v5)")
|
|
38
|
+
component_type: str = Field(..., alias="__component", description="Component type")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DynamicZoneBlock(BaseModel):
|
|
42
|
+
"""Dynamic zone block.
|
|
43
|
+
|
|
44
|
+
Dynamic zones are arrays of components of different types.
|
|
45
|
+
Each block has a '__component' field identifying its type.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
id: Block instance ID
|
|
49
|
+
document_id: Document ID (v5 only)
|
|
50
|
+
component_type: Block type identifier (from __component field)
|
|
51
|
+
Additional fields are allowed based on block schema.
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
>>> block = DynamicZoneBlock(
|
|
55
|
+
... id=1,
|
|
56
|
+
... component_type="content.rich-text",
|
|
57
|
+
... body="<p>Rich text content</p>"
|
|
58
|
+
... )
|
|
59
|
+
>>> block.component_type
|
|
60
|
+
'content.rich-text'
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
64
|
+
|
|
65
|
+
id: int | None = Field(None, description="Block instance ID")
|
|
66
|
+
document_id: str | None = Field(None, alias="documentId", description="Document ID (v5)")
|
|
67
|
+
component_type: str = Field(..., alias="__component", description="Block type")
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Media file models for Strapi file uploads.
|
|
2
|
+
|
|
3
|
+
Models for parsing media/file fields with multiple formats.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MediaFormat(BaseModel):
|
|
15
|
+
"""A specific format/size of a media file.
|
|
16
|
+
|
|
17
|
+
Strapi generates multiple formats for images (thumbnail, small, medium, large).
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
name: Format name (e.g., "thumbnail", "small", "medium", "large")
|
|
21
|
+
hash: File hash
|
|
22
|
+
ext: File extension
|
|
23
|
+
mime: MIME type
|
|
24
|
+
width: Image width in pixels
|
|
25
|
+
height: Image height in pixels
|
|
26
|
+
size: File size in KB
|
|
27
|
+
path: File path (if applicable)
|
|
28
|
+
url: URL to access the file
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
model_config = ConfigDict(extra="allow")
|
|
32
|
+
|
|
33
|
+
name: str = Field(..., description="Format name")
|
|
34
|
+
hash: str = Field(..., description="File hash")
|
|
35
|
+
ext: str = Field(..., description="File extension")
|
|
36
|
+
mime: str = Field(..., description="MIME type")
|
|
37
|
+
width: int | None = Field(None, description="Image width")
|
|
38
|
+
height: int | None = Field(None, description="Image height")
|
|
39
|
+
size: float = Field(..., description="File size in KB")
|
|
40
|
+
path: str | None = Field(None, description="File path")
|
|
41
|
+
url: str = Field(..., description="File URL")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MediaFile(BaseModel):
|
|
45
|
+
"""Media file entity from Strapi.
|
|
46
|
+
|
|
47
|
+
Represents uploaded files with multiple format variations.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
id: Numeric file ID
|
|
51
|
+
document_id: Document ID (v5 only)
|
|
52
|
+
name: Original filename
|
|
53
|
+
alternative_text: Alt text for accessibility
|
|
54
|
+
caption: Image caption
|
|
55
|
+
width: Original width
|
|
56
|
+
height: Original height
|
|
57
|
+
formats: Available formats (thumbnail, small, medium, large)
|
|
58
|
+
hash: File hash
|
|
59
|
+
ext: File extension
|
|
60
|
+
mime: MIME type
|
|
61
|
+
size: File size in KB
|
|
62
|
+
url: URL to original file
|
|
63
|
+
preview_url: Preview URL
|
|
64
|
+
provider: Storage provider (local, s3, etc.)
|
|
65
|
+
provider_metadata: Provider-specific metadata
|
|
66
|
+
created_at: Upload timestamp
|
|
67
|
+
updated_at: Last update timestamp
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
71
|
+
|
|
72
|
+
id: int = Field(..., description="Numeric file ID")
|
|
73
|
+
document_id: str | None = Field(None, alias="documentId", description="Document ID (v5)")
|
|
74
|
+
name: str = Field(..., description="Original filename")
|
|
75
|
+
alternative_text: str | None = Field(None, alias="alternativeText")
|
|
76
|
+
caption: str | None = Field(None, description="Image caption")
|
|
77
|
+
width: int | None = Field(None, description="Image width")
|
|
78
|
+
height: int | None = Field(None, description="Image height")
|
|
79
|
+
formats: dict[str, MediaFormat] | None = Field(None, description="Available formats")
|
|
80
|
+
hash: str = Field(..., description="File hash")
|
|
81
|
+
ext: str = Field(..., description="File extension")
|
|
82
|
+
mime: str = Field(..., description="MIME type")
|
|
83
|
+
size: float = Field(..., description="File size in KB")
|
|
84
|
+
url: str = Field(..., description="File URL")
|
|
85
|
+
preview_url: str | None = Field(None, alias="previewUrl")
|
|
86
|
+
provider: str = Field(..., description="Storage provider")
|
|
87
|
+
provider_metadata: dict[str, Any] | None = Field(
|
|
88
|
+
None, alias="providerMetadata", description="Provider metadata"
|
|
89
|
+
)
|
|
90
|
+
created_at: datetime | None = Field(None, alias="createdAt")
|
|
91
|
+
updated_at: datetime | None = Field(None, alias="updatedAt")
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Metadata models for Strapi API responses.
|
|
2
|
+
|
|
3
|
+
Contains pagination metadata and response metadata structures.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PaginationMeta(BaseModel):
|
|
12
|
+
"""Pagination metadata from Strapi responses.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
page: Current page number (1-indexed)
|
|
16
|
+
page_size: Number of items per page
|
|
17
|
+
page_count: Total number of pages
|
|
18
|
+
total: Total number of items across all pages
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
22
|
+
|
|
23
|
+
page: int | None = Field(None, description="Current page number")
|
|
24
|
+
page_size: int | None = Field(None, alias="pageSize", description="Items per page")
|
|
25
|
+
page_count: int | None = Field(None, alias="pageCount", description="Total pages")
|
|
26
|
+
total: int | None = Field(None, description="Total items")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ResponseMeta(BaseModel):
|
|
30
|
+
"""Response metadata from Strapi.
|
|
31
|
+
|
|
32
|
+
Contains pagination information and other metadata like available locales.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
pagination: Pagination metadata (if pagination was used)
|
|
36
|
+
available_locales: Available locale codes (if i18n is enabled)
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
40
|
+
|
|
41
|
+
pagination: PaginationMeta | None = Field(None, description="Pagination metadata")
|
|
42
|
+
available_locales: list[str] | None = Field(
|
|
43
|
+
None, alias="availableLocales", description="Available locales"
|
|
44
|
+
)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Normalized response models providing version-agnostic interface.
|
|
2
|
+
|
|
3
|
+
The normalization layer abstracts the differences between Strapi v4 and v5,
|
|
4
|
+
providing a consistent interface for working with entities regardless of version.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
|
|
14
|
+
from strapi_kit.models.response.base import StrapiCollectionResponse, StrapiSingleResponse
|
|
15
|
+
from strapi_kit.models.response.v4 import V4Entity
|
|
16
|
+
from strapi_kit.models.response.v5 import V5Entity
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class NormalizedEntity(BaseModel):
|
|
20
|
+
"""Version-agnostic entity representation.
|
|
21
|
+
|
|
22
|
+
Provides a consistent interface abstracting v4/v5 differences:
|
|
23
|
+
- V4: nested attributes → flattened in NormalizedEntity
|
|
24
|
+
- V5: already flat → preserved in NormalizedEntity
|
|
25
|
+
- Document ID: v5 only, None for v4
|
|
26
|
+
- Timestamps: extracted to top level
|
|
27
|
+
- Custom fields: grouped in 'attributes' dict
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
id: Numeric entity ID (present in both v4 and v5)
|
|
31
|
+
document_id: String document ID (v5 only, None for v4)
|
|
32
|
+
created_at: Creation timestamp
|
|
33
|
+
updated_at: Last update timestamp
|
|
34
|
+
published_at: Publication timestamp
|
|
35
|
+
locale: Locale code
|
|
36
|
+
attributes: All custom fields (non-timestamp, non-system fields)
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
>>> # From v4
|
|
40
|
+
>>> v4_entity = V4Entity(
|
|
41
|
+
... id=1,
|
|
42
|
+
... attributes=V4Attributes(
|
|
43
|
+
... createdAt=datetime.now(),
|
|
44
|
+
... title="Test",
|
|
45
|
+
... content="Body"
|
|
46
|
+
... )
|
|
47
|
+
... )
|
|
48
|
+
>>> normalized = NormalizedEntity.from_v4(v4_entity)
|
|
49
|
+
>>> normalized.attributes["title"]
|
|
50
|
+
'Test'
|
|
51
|
+
>>> normalized.document_id # None for v4
|
|
52
|
+
None
|
|
53
|
+
|
|
54
|
+
>>> # From v5
|
|
55
|
+
>>> v5_entity = V5Entity(
|
|
56
|
+
... id=1,
|
|
57
|
+
... documentId="abc123",
|
|
58
|
+
... createdAt=datetime.now(),
|
|
59
|
+
... title="Test",
|
|
60
|
+
... content="Body"
|
|
61
|
+
... )
|
|
62
|
+
>>> normalized = NormalizedEntity.from_v5(v5_entity)
|
|
63
|
+
>>> normalized.document_id
|
|
64
|
+
'abc123'
|
|
65
|
+
>>> normalized.attributes["title"]
|
|
66
|
+
'Test'
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
id: int = Field(..., description="Numeric entity ID")
|
|
70
|
+
document_id: str | None = Field(None, description="Document ID (v5 only)")
|
|
71
|
+
created_at: datetime | None = Field(None, description="Creation timestamp")
|
|
72
|
+
updated_at: datetime | None = Field(None, description="Last update timestamp")
|
|
73
|
+
published_at: datetime | None = Field(None, description="Publication timestamp")
|
|
74
|
+
locale: str | None = Field(None, description="Locale code")
|
|
75
|
+
attributes: dict[str, Any] = Field(default_factory=dict, description="All custom entity fields")
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def from_v4(cls, entity: V4Entity) -> NormalizedEntity:
|
|
79
|
+
"""Create normalized entity from v4 entity.
|
|
80
|
+
|
|
81
|
+
Extracts attributes from the nested 'attributes' object and
|
|
82
|
+
promotes timestamps to top level.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
entity: V4 entity to normalize
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Normalized entity with flattened structure
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
>>> v4 = V4Entity(
|
|
92
|
+
... id=1,
|
|
93
|
+
... attributes=V4Attributes(title="Test", content="Body")
|
|
94
|
+
... )
|
|
95
|
+
>>> normalized = NormalizedEntity.from_v4(v4)
|
|
96
|
+
>>> normalized.id
|
|
97
|
+
1
|
|
98
|
+
"""
|
|
99
|
+
# Get all attributes as dict
|
|
100
|
+
attrs_dict = entity.attributes.model_dump(by_alias=False, exclude_none=False)
|
|
101
|
+
|
|
102
|
+
# Extract system fields
|
|
103
|
+
created_at = attrs_dict.pop("created_at", None)
|
|
104
|
+
updated_at = attrs_dict.pop("updated_at", None)
|
|
105
|
+
published_at = attrs_dict.pop("published_at", None)
|
|
106
|
+
locale = attrs_dict.pop("locale", None)
|
|
107
|
+
|
|
108
|
+
# Remaining fields are custom attributes
|
|
109
|
+
return cls(
|
|
110
|
+
id=entity.id,
|
|
111
|
+
document_id=None, # v4 doesn't have document_id
|
|
112
|
+
created_at=created_at,
|
|
113
|
+
updated_at=updated_at,
|
|
114
|
+
published_at=published_at,
|
|
115
|
+
locale=locale,
|
|
116
|
+
attributes=attrs_dict,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def from_v5(cls, entity: V5Entity) -> NormalizedEntity:
|
|
121
|
+
"""Create normalized entity from v5 entity.
|
|
122
|
+
|
|
123
|
+
Extracts timestamps and system fields, grouping remaining fields
|
|
124
|
+
as custom attributes.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
entity: V5 entity to normalize
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Normalized entity
|
|
131
|
+
|
|
132
|
+
Examples:
|
|
133
|
+
>>> v5 = V5Entity(
|
|
134
|
+
... id=1,
|
|
135
|
+
... documentId="abc123",
|
|
136
|
+
... title="Test",
|
|
137
|
+
... content="Body"
|
|
138
|
+
... )
|
|
139
|
+
>>> normalized = NormalizedEntity.from_v5(v5)
|
|
140
|
+
>>> normalized.document_id
|
|
141
|
+
'abc123'
|
|
142
|
+
"""
|
|
143
|
+
# Get all fields as dict
|
|
144
|
+
entity_dict = entity.model_dump(by_alias=False, exclude_none=False)
|
|
145
|
+
|
|
146
|
+
# Extract system fields
|
|
147
|
+
entity_id = entity_dict.pop("id")
|
|
148
|
+
document_id = entity_dict.pop("document_id")
|
|
149
|
+
created_at = entity_dict.pop("created_at", None)
|
|
150
|
+
updated_at = entity_dict.pop("updated_at", None)
|
|
151
|
+
published_at = entity_dict.pop("published_at", None)
|
|
152
|
+
locale = entity_dict.pop("locale", None)
|
|
153
|
+
|
|
154
|
+
# Remaining fields are custom attributes
|
|
155
|
+
return cls(
|
|
156
|
+
id=entity_id,
|
|
157
|
+
document_id=document_id,
|
|
158
|
+
created_at=created_at,
|
|
159
|
+
updated_at=updated_at,
|
|
160
|
+
published_at=published_at,
|
|
161
|
+
locale=locale,
|
|
162
|
+
attributes=entity_dict,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# Type aliases for normalized responses
|
|
167
|
+
NormalizedSingleResponse = StrapiSingleResponse[NormalizedEntity]
|
|
168
|
+
NormalizedCollectionResponse = StrapiCollectionResponse[NormalizedEntity]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Relation data models for Strapi responses.
|
|
2
|
+
|
|
3
|
+
Models for handling relation fields which are wrapped in a 'data' key.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Generic, TypeVar
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
11
|
+
|
|
12
|
+
# Generic type for relation data
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RelationData(BaseModel, Generic[T]):
|
|
17
|
+
"""Wrapper for relation fields.
|
|
18
|
+
|
|
19
|
+
In Strapi, populated relations are wrapped in a 'data' key:
|
|
20
|
+
- Single relation: {"data": {...}}
|
|
21
|
+
- Multiple relations: {"data": [{...}, {...}]}
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
data: The actual relation data (entity or list of entities)
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
>>> # Single relation
|
|
28
|
+
>>> from strapi_kit.models.response.v5 import V5Entity
|
|
29
|
+
>>> relation = RelationData[V5Entity](
|
|
30
|
+
... data=V5Entity(id=1, documentId="abc", title="Related")
|
|
31
|
+
... )
|
|
32
|
+
>>> relation.data.title
|
|
33
|
+
'Related'
|
|
34
|
+
|
|
35
|
+
>>> # Multiple relations
|
|
36
|
+
>>> relation = RelationData[list[V5Entity]](
|
|
37
|
+
... data=[
|
|
38
|
+
... V5Entity(id=1, documentId="abc", title="First"),
|
|
39
|
+
... V5Entity(id=2, documentId="def", title="Second")
|
|
40
|
+
... ]
|
|
41
|
+
... )
|
|
42
|
+
>>> len(relation.data)
|
|
43
|
+
2
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
model_config = ConfigDict(extra="allow")
|
|
47
|
+
|
|
48
|
+
data: T | None = Field(None, description="Relation data")
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Strapi v4 response models.
|
|
2
|
+
|
|
3
|
+
Models for parsing Strapi v4 API responses with nested attributes structure.
|
|
4
|
+
|
|
5
|
+
V4 Structure:
|
|
6
|
+
{
|
|
7
|
+
"data": {
|
|
8
|
+
"id": 1,
|
|
9
|
+
"attributes": {
|
|
10
|
+
"title": "Hello",
|
|
11
|
+
"createdAt": "2024-01-01T00:00:00.000Z",
|
|
12
|
+
"updatedAt": "2024-01-01T00:00:00.000Z",
|
|
13
|
+
"publishedAt": "2024-01-01T00:00:00.000Z",
|
|
14
|
+
"locale": "en",
|
|
15
|
+
...custom fields
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from datetime import datetime
|
|
24
|
+
|
|
25
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
26
|
+
|
|
27
|
+
from strapi_kit.models.response.base import StrapiCollectionResponse, StrapiSingleResponse
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class V4Attributes(BaseModel):
|
|
31
|
+
"""Attributes container for v4 entities.
|
|
32
|
+
|
|
33
|
+
In v4, all entity data is nested under the 'attributes' key.
|
|
34
|
+
This includes timestamps, locale, and all custom fields.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
created_at: Creation timestamp
|
|
38
|
+
updated_at: Last update timestamp
|
|
39
|
+
published_at: Publication timestamp (if draft & publish enabled)
|
|
40
|
+
locale: Locale code (if i18n enabled)
|
|
41
|
+
Additional fields are allowed and will be preserved.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
45
|
+
|
|
46
|
+
created_at: datetime | None = Field(None, alias="createdAt")
|
|
47
|
+
updated_at: datetime | None = Field(None, alias="updatedAt")
|
|
48
|
+
published_at: datetime | None = Field(None, alias="publishedAt")
|
|
49
|
+
locale: str | None = Field(None, description="Locale code")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class V4Entity(BaseModel):
|
|
53
|
+
"""Strapi v4 entity structure.
|
|
54
|
+
|
|
55
|
+
V4 entities have a numeric ID and nested attributes.
|
|
56
|
+
|
|
57
|
+
Attributes:
|
|
58
|
+
id: Numeric entity ID
|
|
59
|
+
attributes: All entity data (timestamps + custom fields)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
model_config = ConfigDict(extra="allow")
|
|
63
|
+
|
|
64
|
+
id: int = Field(..., description="Numeric entity ID")
|
|
65
|
+
attributes: V4Attributes = Field(..., description="Entity attributes")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Type aliases for v4 responses
|
|
69
|
+
V4SingleResponse = StrapiSingleResponse[V4Entity]
|
|
70
|
+
V4CollectionResponse = StrapiCollectionResponse[V4Entity]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Strapi v5 response models.
|
|
2
|
+
|
|
3
|
+
Models for parsing Strapi v5 API responses with flattened structure.
|
|
4
|
+
|
|
5
|
+
V5 Structure:
|
|
6
|
+
{
|
|
7
|
+
"data": {
|
|
8
|
+
"id": 1,
|
|
9
|
+
"documentId": "abc123",
|
|
10
|
+
"title": "Hello",
|
|
11
|
+
"createdAt": "2024-01-01T00:00:00.000Z",
|
|
12
|
+
"updatedAt": "2024-01-01T00:00:00.000Z",
|
|
13
|
+
"publishedAt": "2024-01-01T00:00:00.000Z",
|
|
14
|
+
"locale": "en",
|
|
15
|
+
...custom fields
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
|
|
24
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
25
|
+
|
|
26
|
+
from strapi_kit.models.response.base import StrapiCollectionResponse, StrapiSingleResponse
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class V5Entity(BaseModel):
|
|
30
|
+
"""Strapi v5 entity structure.
|
|
31
|
+
|
|
32
|
+
V5 entities have a flattened structure with both numeric ID and document ID.
|
|
33
|
+
All fields (timestamps, locale, custom) are at the same level.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
id: Numeric entity ID (for backward compatibility)
|
|
37
|
+
document_id: String document ID (new in v5, unique identifier)
|
|
38
|
+
created_at: Creation timestamp
|
|
39
|
+
updated_at: Last update timestamp
|
|
40
|
+
published_at: Publication timestamp (if draft & publish enabled)
|
|
41
|
+
locale: Locale code (if i18n enabled)
|
|
42
|
+
Additional fields are allowed and will be preserved.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
46
|
+
|
|
47
|
+
id: int = Field(..., description="Numeric entity ID")
|
|
48
|
+
document_id: str = Field(..., alias="documentId", description="Document ID (v5)")
|
|
49
|
+
created_at: datetime | None = Field(None, alias="createdAt")
|
|
50
|
+
updated_at: datetime | None = Field(None, alias="updatedAt")
|
|
51
|
+
published_at: datetime | None = Field(None, alias="publishedAt")
|
|
52
|
+
locale: str | None = Field(None, description="Locale code")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Type aliases for v5 responses
|
|
56
|
+
V5SingleResponse = StrapiSingleResponse[V5Entity]
|
|
57
|
+
V5CollectionResponse = StrapiCollectionResponse[V5Entity]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Content type schema models."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FieldType(str, Enum):
|
|
9
|
+
"""Field types in Strapi."""
|
|
10
|
+
|
|
11
|
+
STRING = "string"
|
|
12
|
+
TEXT = "text"
|
|
13
|
+
RICH_TEXT = "richtext"
|
|
14
|
+
EMAIL = "email"
|
|
15
|
+
PASSWORD = "password" # nosec B105 - This is a field type enum, not a hardcoded password
|
|
16
|
+
INTEGER = "integer"
|
|
17
|
+
BIG_INTEGER = "biginteger"
|
|
18
|
+
FLOAT = "float"
|
|
19
|
+
DECIMAL = "decimal"
|
|
20
|
+
DATE = "date"
|
|
21
|
+
TIME = "time"
|
|
22
|
+
DATETIME = "datetime"
|
|
23
|
+
TIMESTAMP = "timestamp"
|
|
24
|
+
BOOLEAN = "boolean"
|
|
25
|
+
ENUMERATION = "enumeration"
|
|
26
|
+
JSON = "json"
|
|
27
|
+
MEDIA = "media"
|
|
28
|
+
RELATION = "relation"
|
|
29
|
+
COMPONENT = "component"
|
|
30
|
+
DYNAMIC_ZONE = "dynamiczone"
|
|
31
|
+
UID = "uid"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RelationType(str, Enum):
|
|
35
|
+
"""Relation types in Strapi."""
|
|
36
|
+
|
|
37
|
+
ONE_TO_ONE = "oneToOne"
|
|
38
|
+
ONE_TO_MANY = "oneToMany"
|
|
39
|
+
MANY_TO_ONE = "manyToOne"
|
|
40
|
+
MANY_TO_MANY = "manyToMany"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class FieldSchema(BaseModel):
|
|
44
|
+
"""Schema for a single field."""
|
|
45
|
+
|
|
46
|
+
type: FieldType
|
|
47
|
+
required: bool = False
|
|
48
|
+
unique: bool = False
|
|
49
|
+
|
|
50
|
+
# Relation-specific
|
|
51
|
+
relation: RelationType | None = None
|
|
52
|
+
target: str | None = None # Target content type UID
|
|
53
|
+
mapped_by: str | None = None
|
|
54
|
+
inversed_by: str | None = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ContentTypeSchema(BaseModel):
|
|
58
|
+
"""Complete schema for a content type."""
|
|
59
|
+
|
|
60
|
+
uid: str
|
|
61
|
+
display_name: str
|
|
62
|
+
kind: str = "collectionType"
|
|
63
|
+
singular_name: str | None = Field(None, description="Singular name from Strapi schema")
|
|
64
|
+
plural_name: str | None = Field(
|
|
65
|
+
None, description="Plural name from Strapi schema (API endpoint)"
|
|
66
|
+
)
|
|
67
|
+
fields: dict[str, FieldSchema] = Field(default_factory=dict)
|
|
68
|
+
|
|
69
|
+
def get_field_target(self, field_name: str) -> str | None:
|
|
70
|
+
"""Get target content type for a relation field.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
field_name: Name of the field to check
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Target content type UID if field is a relation, None otherwise
|
|
77
|
+
"""
|
|
78
|
+
field = self.fields.get(field_name)
|
|
79
|
+
if field and field.type == FieldType.RELATION:
|
|
80
|
+
return field.target
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
def is_relation_field(self, field_name: str) -> bool:
|
|
84
|
+
"""Check if a field is a relation.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
field_name: Name of the field to check
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
True if field is a relation, False otherwise
|
|
91
|
+
"""
|
|
92
|
+
field = self.fields.get(field_name)
|
|
93
|
+
return field is not None and field.type == FieldType.RELATION
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Operations module for strapi-kit.
|
|
2
|
+
|
|
3
|
+
This module contains utility functions and helpers for various operations.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from strapi_kit.operations.media import (
|
|
7
|
+
build_media_download_url,
|
|
8
|
+
build_upload_payload,
|
|
9
|
+
normalize_media_response,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"build_media_download_url",
|
|
14
|
+
"build_upload_payload",
|
|
15
|
+
"normalize_media_response",
|
|
16
|
+
]
|