strapi-kit 0.0.3__tar.gz → 0.0.4__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 (98) hide show
  1. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/CHANGELOG.md +12 -0
  2. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/PKG-INFO +2 -1
  3. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/README.md +1 -0
  4. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/examples/basic_crud.py +9 -5
  5. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/_version.py +2 -2
  6. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/client/base.py +13 -6
  7. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/utils/uid.py +16 -5
  8. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/.coderabbit.yaml +0 -0
  9. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/.env.example +0 -0
  10. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/.github/dependabot.yml +0 -0
  11. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/.github/pull_request_template.md +0 -0
  12. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/.github/workflows/ci.yml +0 -0
  13. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/.github/workflows/dev-release.yml +0 -0
  14. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/.github/workflows/document.yml +0 -0
  15. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/.github/workflows/guard-main-origin.yml +0 -0
  16. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/.github/workflows/release.yml +0 -0
  17. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/.gitignore +0 -0
  18. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/.pre-commit-config.yaml +0 -0
  19. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/.secrets.baseline +0 -0
  20. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/AGENTS.md +0 -0
  21. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/CLAUDE.md +0 -0
  22. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/LICENSE +0 -0
  23. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/LLM.md +0 -0
  24. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/Makefile +0 -0
  25. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/codecov.yml +0 -0
  26. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/docs/changelog.md +0 -0
  27. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/docs/configuration.md +0 -0
  28. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/docs/development/architecture.md +0 -0
  29. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/docs/development/contributing.md +0 -0
  30. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/docs/development/release-process.md +0 -0
  31. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/docs/development/testing.md +0 -0
  32. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/docs/export-import.md +0 -0
  33. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/docs/index.md +0 -0
  34. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/docs/installation.md +0 -0
  35. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/docs/media.md +0 -0
  36. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/docs/models.md +0 -0
  37. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/docs/quickstart.md +0 -0
  38. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/docs/stylesheets/extra.css +0 -0
  39. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/examples/MIGRATION_GUIDE.md +0 -0
  40. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/examples/async_operations.py +0 -0
  41. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/examples/config_di_demo.py +0 -0
  42. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/examples/export_import_with_media.py +0 -0
  43. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/examples/export_import_with_schemas.py +0 -0
  44. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/examples/full_migration_v5.py +0 -0
  45. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/examples/simple_migration.py +0 -0
  46. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/examples/verify_installation.py +0 -0
  47. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/mkdocs.yml +0 -0
  48. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/pyproject.toml +0 -0
  49. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/__init__.py +0 -0
  50. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/__version__.py +0 -0
  51. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/auth/__init__.py +0 -0
  52. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/auth/api_token.py +0 -0
  53. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/cache/__init__.py +0 -0
  54. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/cache/schema_cache.py +0 -0
  55. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/client/__init__.py +0 -0
  56. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/client/async_client.py +0 -0
  57. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/client/sync_client.py +0 -0
  58. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/config_provider.py +0 -0
  59. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/exceptions/__init__.py +0 -0
  60. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/exceptions/errors.py +0 -0
  61. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/export/__init__.py +0 -0
  62. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/export/exporter.py +0 -0
  63. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/export/importer.py +0 -0
  64. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/export/media_handler.py +0 -0
  65. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/export/relation_resolver.py +0 -0
  66. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/__init__.py +0 -0
  67. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/bulk.py +0 -0
  68. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/config.py +0 -0
  69. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/content_type.py +0 -0
  70. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/enums.py +0 -0
  71. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/export_format.py +0 -0
  72. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/import_options.py +0 -0
  73. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/request/__init__.py +0 -0
  74. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/request/fields.py +0 -0
  75. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/request/filters.py +0 -0
  76. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/request/pagination.py +0 -0
  77. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/request/populate.py +0 -0
  78. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/request/query.py +0 -0
  79. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/request/sort.py +0 -0
  80. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/response/__init__.py +0 -0
  81. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/response/base.py +0 -0
  82. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/response/component.py +0 -0
  83. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/response/media.py +0 -0
  84. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/response/meta.py +0 -0
  85. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/response/normalized.py +0 -0
  86. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/response/relation.py +0 -0
  87. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/response/v4.py +0 -0
  88. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/response/v5.py +0 -0
  89. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/models/schema.py +0 -0
  90. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/operations/__init__.py +0 -0
  91. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/operations/media.py +0 -0
  92. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/operations/streaming.py +0 -0
  93. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/parsers/__init__.py +0 -0
  94. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/parsers/version_detecting.py +0 -0
  95. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/protocols.py +0 -0
  96. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/utils/__init__.py +0 -0
  97. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/utils/rate_limiter.py +0 -0
  98. {strapi_kit-0.0.3 → strapi_kit-0.0.4}/src/strapi_kit/utils/seo.py +0 -0
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Fixed
11
+
12
+ - **Exception handling improvements** ([#23](https://github.com/MehdiZare/strapi-kit/pull/23))
13
+ - Use `StrapiError` instead of bare `Exception` in examples for precise error handling
14
+ - Catch `PydanticValidationError` specifically in Content-Type Builder parsing
15
+ - Add proper exception chaining when re-raising validation errors
16
+ - Fix docstring to document `ConfigurationError` instead of `ValueError`
17
+
18
+ - **Singularization bug fix** ([#23](https://github.com/MehdiZare/strapi-kit/pull/23))
19
+ - Fix `api_id_to_singular()` for `-zzes` endings: `quizzes` → `quiz`, `buzzes` → `buzz`
20
+ - Use length-based heuristic to distinguish single-z doubled vs double-z base words
21
+
10
22
  ### Added
11
23
 
12
24
  - **Content-Type Builder API** ([#15](https://github.com/MehdiZare/strapi-kit/issues/15))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: strapi-kit
3
- Version: 0.0.3
3
+ Version: 0.0.4
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/
@@ -728,6 +728,7 @@ uid_to_endpoint("api::class.class") # "classes"
728
728
  # Convert plural API ID to singular
729
729
  api_id_to_singular("articles") # "article"
730
730
  api_id_to_singular("categories") # "category"
731
+ api_id_to_singular("quizzes") # "quiz" (handles -zzes endings)
731
732
  api_id_to_singular("people") # "person" (handles irregular plurals)
732
733
  api_id_to_singular("children") # "child"
733
734
 
@@ -682,6 +682,7 @@ uid_to_endpoint("api::class.class") # "classes"
682
682
  # Convert plural API ID to singular
683
683
  api_id_to_singular("articles") # "article"
684
684
  api_id_to_singular("categories") # "category"
685
+ api_id_to_singular("quizzes") # "quiz" (handles -zzes endings)
685
686
  api_id_to_singular("people") # "person" (handles irregular plurals)
686
687
  api_id_to_singular("children") # "child"
687
688
 
@@ -7,6 +7,7 @@ using the synchronous client.
7
7
  from pydantic import SecretStr
8
8
 
9
9
  from strapi_kit import StrapiConfig, SyncClient
10
+ from strapi_kit.exceptions import StrapiError
10
11
 
11
12
 
12
13
  def main() -> None:
@@ -27,7 +28,7 @@ def main() -> None:
27
28
  try:
28
29
  response = client.get("articles", params={"pagination[limit]": 5})
29
30
  print(f"Found {len(response.get('data', []))} articles")
30
- except Exception as e:
31
+ except StrapiError as e:
31
32
  print(f"Error: {e}")
32
33
 
33
34
  # POST request - Create article
@@ -42,8 +43,11 @@ def main() -> None:
42
43
  }
43
44
  response = client.post("articles", json=new_article)
44
45
  article_id = response.get("data", {}).get("id")
46
+ if not article_id:
47
+ print("Error creating article: missing id in response")
48
+ return
45
49
  print(f"Created article with ID: {article_id}")
46
- except Exception as e:
50
+ except StrapiError as e:
47
51
  print(f"Error creating article: {e}")
48
52
  print("Skipping update/get/delete operations")
49
53
  return # Exit early if create fails
@@ -59,7 +63,7 @@ def main() -> None:
59
63
  }
60
64
  response = client.put(f"articles/{article_id}", json=update_data)
61
65
  print(f"Updated article: {response.get('data', {}).get('title')}")
62
- except Exception as e:
66
+ except StrapiError as e:
63
67
  print(f"Error: {e}")
64
68
 
65
69
  # GET request - Get single article
@@ -68,7 +72,7 @@ def main() -> None:
68
72
  if article_id:
69
73
  response = client.get(f"articles/{article_id}")
70
74
  print(f"Retrieved: {response.get('data', {}).get('title')}")
71
- except Exception as e:
75
+ except StrapiError as e:
72
76
  print(f"Error: {e}")
73
77
 
74
78
  # DELETE request - Delete article
@@ -77,7 +81,7 @@ def main() -> None:
77
81
  if article_id:
78
82
  response = client.delete(f"articles/{article_id}")
79
83
  print("Article deleted successfully")
80
- except Exception as e:
84
+ except StrapiError as e:
81
85
  print(f"Error: {e}")
82
86
 
83
87
 
@@ -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.3'
32
- __version_tuple__ = version_tuple = (0, 0, 3)
31
+ __version__ = version = '0.0.4'
32
+ __version_tuple__ = version_tuple = (0, 0, 4)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -12,6 +12,7 @@ if TYPE_CHECKING:
12
12
  from ..models.content_type import ContentTypeSchema as CTBContentTypeSchema
13
13
 
14
14
  import httpx
15
+ from pydantic import ValidationError as PydanticValidationError
15
16
  from tenacity import (
16
17
  before_sleep_log,
17
18
  retry,
@@ -75,7 +76,7 @@ class BaseClient:
75
76
  parser: Response parser (defaults to VersionDetectingParser)
76
77
 
77
78
  Raises:
78
- ValueError: If authentication token is invalid
79
+ ConfigurationError: If authentication token is invalid
79
80
  """
80
81
  self.config: ConfigProvider = config
81
82
  self.base_url = config.get_base_url()
@@ -492,9 +493,9 @@ class BaseClient:
492
493
  try:
493
494
  content_type = ContentTypeListItem.model_validate(item)
494
495
  result.append(content_type)
495
- except Exception:
496
+ except PydanticValidationError as e:
496
497
  # Skip malformed items
497
- logger.warning(f"Failed to parse content type: {uid}")
498
+ logger.warning(f"Failed to parse content type: {uid}", exc_info=e)
498
499
  continue
499
500
 
500
501
  return result
@@ -521,9 +522,9 @@ class BaseClient:
521
522
  try:
522
523
  component = ComponentListItem.model_validate(item)
523
524
  result.append(component)
524
- except Exception:
525
+ except PydanticValidationError as e:
525
526
  # Skip malformed items
526
- logger.warning(f"Failed to parse component: {uid}")
527
+ logger.warning(f"Failed to parse component: {uid}", exc_info=e)
527
528
  continue
528
529
 
529
530
  return result
@@ -546,4 +547,10 @@ class BaseClient:
546
547
  from ..models.content_type import ContentTypeSchema as CTBContentTypeSchema
547
548
 
548
549
  data = response_data.get("data", response_data)
549
- return CTBContentTypeSchema.model_validate(data)
550
+ try:
551
+ return CTBContentTypeSchema.model_validate(data)
552
+ except PydanticValidationError as e:
553
+ raise ValidationError(
554
+ "Invalid content type schema response",
555
+ details={"errors": e.errors()},
556
+ ) from e
@@ -146,15 +146,26 @@ def api_id_to_singular(api_id: str) -> str:
146
146
  if name.endswith("ies"):
147
147
  return name[:-3] + "y"
148
148
 
149
- # Handle -es for words ending in s, x, z, ch, sh (classes -> class)
149
+ # Handle -zzes specifically
150
+ # Words with single z double it when pluralized: quiz -> quizzes (remove -zes, keep 1 z)
151
+ # Words with double z just add es: buzz -> buzzes, fizz -> fizzes (remove -es, keep zz)
152
+ if name.endswith("zzes"):
153
+ # Common double-z words: buzz, fizz, fuzz, jazz, razz, etc. (pattern: consonant + vowel + zz)
154
+ # Common single-z words that double: quiz, whiz (pattern: vowel + i + z or similar)
155
+ # Heuristic: 4-letter bases (buzz, fizz, jazz, fuzz) become 6-letter plurals
156
+ # 4-letter bases like quiz become 7-letter plurals
157
+ # So length 6 -> likely double-z base (remove -es)
158
+ # length 7+ -> likely single-z base that was doubled (remove -zes)
159
+ if len(name) <= 6:
160
+ return name[:-2] # buzzes -> buzz, fizzes -> fizz
161
+ else:
162
+ return name[:-3] # quizzes -> quiz, whizzes -> whiz
163
+
164
+ # Handle -es for words ending in s, x, z, ch, sh (classes -> class, buses -> bus)
150
165
  if name.endswith("es"):
151
- # Check if removing -es gives a word ending in s, x, z, ch, sh
152
166
  base = name[:-2]
153
167
  if base.endswith(("s", "x", "z", "ch", "sh")):
154
168
  return base
155
- # Also handle -ses, -zes (buses -> bus, quizzes -> quiz)
156
- if name.endswith("ses") or name.endswith("zes"):
157
- return name[:-2]
158
169
 
159
170
  # Handle standard -s removal (articles -> article)
160
171
  if name.endswith("s") and len(name) > 1:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes