fastapi-mongo-base 1.2.1__tar.gz → 1.3.2__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 (88) hide show
  1. {fastapi_mongo_base-1.2.1/src/fastapi_mongo_base.egg-info → fastapi_mongo_base-1.3.2}/PKG-INFO +8 -6
  2. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/pyproject.toml +10 -8
  3. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/core/app_factory.py +6 -7
  4. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/core/config.py +0 -3
  5. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/core/db.py +22 -23
  6. fastapi_mongo_base-1.3.2/src/fastapi_mongo_base/core/errors/mongodb_errors.py +186 -0
  7. fastapi_mongo_base-1.3.2/src/fastapi_mongo_base/core/errors/resource_errors.py +90 -0
  8. fastapi_mongo_base-1.3.2/src/fastapi_mongo_base/core/exceptions.py +269 -0
  9. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/models.py +3 -2
  10. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/routes.py +2 -2
  11. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/schemas.py +7 -3
  12. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/sql.py +2 -1
  13. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/tasks.py +2 -1
  14. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/utils/basic.py +2 -2
  15. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2/src/fastapi_mongo_base.egg-info}/PKG-INFO +8 -6
  16. fastapi_mongo_base-1.3.2/src/fastapi_mongo_base.egg-info/SOURCES.txt +53 -0
  17. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base.egg-info/requires.txt +8 -5
  18. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/tests/conftest.py +7 -10
  19. fastapi_mongo_base-1.3.2/tests/test_mongodb_errors.py +247 -0
  20. fastapi_mongo_base-1.3.2/tests/test_resource_errors.py +171 -0
  21. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/__pycache__/__init__.cpython-312.pyc +0 -0
  22. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/__pycache__/models.cpython-312.pyc +0 -0
  23. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/__pycache__/routes.cpython-312.pyc +0 -0
  24. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/__pycache__/schemas.cpython-312.pyc +0 -0
  25. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/__pycache__/tasks.cpython-312.pyc +0 -0
  26. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/core/__pycache__/__init__.cpython-312.pyc +0 -0
  27. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/core/__pycache__/app_factory.cpython-312.pyc +0 -0
  28. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/core/__pycache__/config.cpython-312.pyc +0 -0
  29. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/core/__pycache__/db.cpython-312.pyc +0 -0
  30. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/core/__pycache__/exceptions.cpython-312.pyc +0 -0
  31. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/core/errors/db_errors.py +0 -798
  32. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/core/errors/i18n.py +0 -118
  33. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/core/errors/resource_errors.py +0 -354
  34. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/core/exceptions.py +0 -374
  35. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  36. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/utils/__pycache__/basic.cpython-312.pyc +0 -0
  37. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/utils/__pycache__/bsontools.cpython-312.pyc +0 -0
  38. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base/utils/__pycache__/timezone.cpython-312.pyc +0 -0
  39. fastapi_mongo_base-1.2.1/src/fastapi_mongo_base.egg-info/SOURCES.txt +0 -80
  40. fastapi_mongo_base-1.2.1/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  41. fastapi_mongo_base-1.2.1/tests/__pycache__/conftest.cpython-312-pytest-9.1.1.pyc +0 -0
  42. fastapi_mongo_base-1.2.1/tests/__pycache__/test_apis.cpython-312-pytest-9.1.1.pyc +0 -0
  43. fastapi_mongo_base-1.2.1/tests/__pycache__/test_app.cpython-312-pytest-9.1.1.pyc +0 -0
  44. fastapi_mongo_base-1.2.1/tests/__pycache__/test_db_init.cpython-312-pytest-9.1.1.pyc +0 -0
  45. fastapi_mongo_base-1.2.1/tests/__pycache__/test_i18n_errors.cpython-312-pytest-9.1.1.pyc +0 -0
  46. fastapi_mongo_base-1.2.1/tests/__pycache__/test_mongo_exceptions.cpython-312-pytest-9.1.1.pyc +0 -0
  47. fastapi_mongo_base-1.2.1/tests/__pycache__/test_resource_exceptions.cpython-312-pytest-9.1.1.pyc +0 -0
  48. fastapi_mongo_base-1.2.1/tests/app/__pycache__/__init__.cpython-312.pyc +0 -0
  49. fastapi_mongo_base-1.2.1/tests/app/__pycache__/server.cpython-312.pyc +0 -0
  50. fastapi_mongo_base-1.2.1/tests/test_db_init.py +0 -84
  51. fastapi_mongo_base-1.2.1/tests/test_i18n_errors.py +0 -150
  52. fastapi_mongo_base-1.2.1/tests/test_mongo_exceptions.py +0 -127
  53. fastapi_mongo_base-1.2.1/tests/test_resource_exceptions.py +0 -186
  54. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/LICENSE.txt +0 -0
  55. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/MANIFEST.in +0 -0
  56. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/README.md +0 -0
  57. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/docs/boilerplate.md +0 -0
  58. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/docs/changelog.md +0 -0
  59. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/docs/contributing.md +0 -0
  60. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/docs/endpoints.md +0 -0
  61. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/docs/extra.css +0 -0
  62. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/docs/index.md +0 -0
  63. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/docs/installation.md +0 -0
  64. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/docs/license.md +0 -0
  65. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/docs/quickstart.md +0 -0
  66. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/docs/settings.md +0 -0
  67. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/docs/tutorial.md +0 -0
  68. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/docs/usage.md +0 -0
  69. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/mkdocs.yml +0 -0
  70. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/pytest.ini +0 -0
  71. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/setup.cfg +0 -0
  72. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/__init__.py +0 -0
  73. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/core/__init__.py +0 -0
  74. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/core/enums.py +0 -0
  75. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/py.typed +0 -0
  76. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/utils/__init__.py +0 -0
  77. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/utils/bsontools.py +0 -0
  78. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/utils/timezone.py +0 -0
  79. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base/utils/usso_routes.py +0 -0
  80. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base.egg-info/dependency_links.txt +0 -0
  81. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/src/fastapi_mongo_base.egg-info/top_level.txt +0 -0
  82. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/tests/__init__.py +0 -0
  83. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/tests/app/__init__.py +0 -0
  84. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/tests/app/logs/app.log +0 -0
  85. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/tests/app/main.py +0 -0
  86. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/tests/app/server.py +0 -0
  87. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/tests/test_apis.py +0 -0
  88. {fastapi_mongo_base-1.2.1 → fastapi_mongo_base-1.3.2}/tests/test_app.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-mongo-base
3
- Version: 1.2.1
3
+ Version: 1.3.2
4
4
  Summary: A simple boilerplate application, including models and schemas and abstract router, for FastAPI with MongoDB
5
5
  Author-email: Mahdi Kiani <mahdikiany@gmail.com>
6
6
  Maintainer-email: Mahdi Kiani <mahdikiany@gmail.com>
@@ -18,19 +18,21 @@ Classifier: Programming Language :: Python :: 3
18
18
  Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
21
22
  Classifier: Programming Language :: Python :: 3 :: Only
22
23
  Requires-Python: >=3.10
23
24
  Description-Content-Type: text/markdown
24
25
  License-File: LICENSE.txt
25
- Requires-Dist: beanie>=1.30.0
26
- Requires-Dist: fastapi>=0.129.0
27
- Requires-Dist: httpx>=0.28.1
28
- Requires-Dist: json-advanced>=0.12.11
29
26
  Requires-Dist: pydantic>=2.13.4
30
- Requires-Dist: pytz>=2026.2
27
+ Requires-Dist: httpx>=0.28.1
31
28
  Requires-Dist: singleton_package>=0.8.2
29
+ Requires-Dist: json-advanced>=0.13.0
30
+ Requires-Dist: pytz>=2026.2
31
+ Requires-Dist: beanie>=1.30.0
32
+ Requires-Dist: fastapi>=0.129.0
32
33
  Requires-Dist: uvicorn[standard]>=0.41.0
33
34
  Requires-Dist: uuid6>=2025.0.1
35
+ Requires-Dist: typing_extensions>=4.5.0; python_version < "3.11"
34
36
  Provides-Extra: usso
35
37
  Requires-Dist: usso>=0.30.1; extra == "usso"
36
38
  Provides-Extra: sql
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fastapi-mongo-base"
7
- version = "1.2.1"
7
+ version = "1.3.2"
8
8
  description = "A simple boilerplate application, including models and schemas and abstract router, for FastAPI with MongoDB"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -21,18 +21,20 @@ classifiers = [
21
21
  "Programming Language :: Python :: 3.10",
22
22
  "Programming Language :: Python :: 3.11",
23
23
  "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
24
25
  "Programming Language :: Python :: 3 :: Only",
25
26
  ]
26
27
  dependencies = [
27
- "beanie>=1.30.0",
28
- "fastapi>=0.129.0",
29
- "httpx>=0.28.1",
30
- "json-advanced>=0.12.11",
31
28
  "pydantic>=2.13.4",
32
- "pytz>=2026.2",
29
+ "httpx>=0.28.1",
33
30
  "singleton_package>=0.8.2",
31
+ "json-advanced>=0.13.0",
32
+ "pytz>=2026.2",
33
+ "beanie>=1.30.0",
34
+ "fastapi>=0.129.0",
34
35
  "uvicorn[standard]>=0.41.0",
35
36
  "uuid6>=2025.0.1",
37
+ "typing_extensions>=4.5.0; python_version<'3.11'",
36
38
  ]
37
39
 
38
40
  [project.optional-dependencies]
@@ -52,7 +54,7 @@ package-data = { "usso" = ["*.dat"] }
52
54
 
53
55
  [tool.ruff]
54
56
  line-length = 79
55
- target-version = "py313"
57
+ target-version = "py310"
56
58
  fix = true
57
59
  unsafe-fixes = true
58
60
  preview = true
@@ -92,7 +94,7 @@ ignore = ["TRY003", "COM812", "B008", "D202", "D203", "D209", "D212", "D413"]
92
94
  quote-style = "double"
93
95
 
94
96
  [tool.mypy]
95
- python_version = "3.13"
97
+ python_version = "3.10"
96
98
  ignore_missing_imports = true
97
99
  check_untyped_defs = false
98
100
  disallow_untyped_defs = false
@@ -6,7 +6,6 @@ import logging
6
6
  from collections import deque
7
7
  from collections.abc import AsyncGenerator, Callable
8
8
  from contextlib import asynccontextmanager
9
- from typing import Any
10
9
 
11
10
  import fastapi
12
11
  from fastapi.responses import RedirectResponse
@@ -33,7 +32,7 @@ def health(request: fastapi.Request) -> dict[str, str]:
33
32
  async def lifespan(
34
33
  *,
35
34
  app: fastapi.FastAPI,
36
- worker: Callable[[], Any] | None = None,
35
+ worker: Callable[[], object] | None = None,
37
36
  init_functions: list | None = None,
38
37
  settings: config.Settings | None = None,
39
38
  ) -> AsyncGenerator[None]:
@@ -122,13 +121,13 @@ def get_app_kwargs(
122
121
  title: str | None = None,
123
122
  description: str | None = None,
124
123
  version: str = "0.1.0",
125
- lifespan_func: Callable[[fastapi.FastAPI], Any] | None = None,
126
- worker: Callable[[], Any] | None = None,
124
+ lifespan_func: Callable[[fastapi.FastAPI], object] | None = None,
125
+ worker: Callable[[], object] | None = None,
127
126
  init_functions: list | None = None,
128
127
  contact: dict[str, str] | None = None,
129
128
  license_info: dict[str, str] | None = None,
130
129
  **kwargs: object,
131
- ) -> dict[str, Any]:
130
+ ) -> dict[str, object]:
132
131
  """
133
132
  Generate keyword arguments for FastAPI app creation.
134
133
 
@@ -208,8 +207,8 @@ def create_app(
208
207
  version: str = "0.1.0",
209
208
  serve_coverage: bool = False,
210
209
  origins: list | None = None,
211
- lifespan_func: Callable[[fastapi.FastAPI], Any] | None = None,
212
- worker: Callable[[], Any] | None = None,
210
+ lifespan_func: Callable[[fastapi.FastAPI], object] | None = None,
211
+ worker: Callable[[], object] | None = None,
213
212
  init_functions: list | None = None,
214
213
  contact: dict[str, str] | None = None,
215
214
  license_info: dict[str, str] | None = None,
@@ -54,9 +54,6 @@ class Settings(metaclass=Singleton):
54
54
  mongo_connect_timeout_ms: int = int(
55
55
  os.getenv("MONGO_CONNECT_TIMEOUT_MS", default="5000")
56
56
  )
57
- exit_on_init_failure: bool = os.getenv(
58
- "EXIT_ON_INIT_FAILURE", default="true"
59
- ).lower() in ("true", "on", "1")
60
57
 
61
58
  @classmethod
62
59
  def get_coverage_dir(cls) -> str:
@@ -4,7 +4,6 @@ import logging
4
4
 
5
5
  from beanie import init_beanie
6
6
 
7
- from fastapi_mongo_base.core.errors.db_errors import from_pymongo_error
8
7
  from fastapi_mongo_base.models import BaseEntity
9
8
  from fastapi_mongo_base.utils import basic
10
9
 
@@ -26,17 +25,12 @@ async def init_mongo_db(settings: Settings | None = None) -> object:
26
25
 
27
26
  Raises:
28
27
  ImportError: If MongoDB client libraries are not installed.
29
- SystemExit: If connection fails and ``exit_on_init_failure`` is
30
- enabled (default; use in Docker so the container restarts
31
- until Mongo is up).
32
- MongoDBConnectionError: If MongoDB connection or initialization fails
33
- and ``exit_on_init_failure`` is disabled.
34
- MongoDBConnectionTimeoutError: If MongoDB connection times out and
35
- ``exit_on_init_failure`` is disabled.
28
+ MongoDBConnectionError: If MongoDB connection or initialization fails.
36
29
 
37
30
  """
38
31
  try:
39
32
  from pymongo import AsyncMongoClient
33
+ from pymongo.errors import PyMongoError, ServerSelectionTimeoutError
40
34
  except ImportError:
41
35
  try:
42
36
  from motor.motor_asyncio import AsyncIOMotorClient
@@ -48,14 +42,12 @@ async def init_mongo_db(settings: Settings | None = None) -> object:
48
42
  if settings is None:
49
43
  settings = Settings()
50
44
 
45
+ client = AsyncMongoClient(
46
+ settings.mongo_uri,
47
+ serverSelectionTimeoutMS=settings.mongo_server_selection_timeout_ms,
48
+ connectTimeoutMS=settings.mongo_connect_timeout_ms,
49
+ )
51
50
  try:
52
- client = AsyncMongoClient(
53
- settings.mongo_uri,
54
- serverSelectionTimeoutMS=(
55
- settings.mongo_server_selection_timeout_ms
56
- ),
57
- connectTimeoutMS=settings.mongo_connect_timeout_ms,
58
- )
59
51
  await client.server_info()
60
52
  db = client.get_database(settings.project_name)
61
53
  await init_beanie(
@@ -69,11 +61,19 @@ async def init_mongo_db(settings: Settings | None = None) -> object:
69
61
  )
70
62
  ],
71
63
  )
72
- except Exception as e:
64
+ except ServerSelectionTimeoutError as e:
73
65
  logging.exception(
74
- "MongoDB initialization failed at %s", settings.mongo_uri
66
+ "MongoDB connection timeout at %s", settings.mongo_uri
75
67
  )
76
- raise from_pymongo_error(e, settings=settings) from e
68
+ raise SystemExit(1) from e
69
+
70
+ except PyMongoError as e:
71
+ logging.exception("MongoDB error at %s", settings.mongo_uri)
72
+ raise SystemExit(1) from e
73
+
74
+ except Exception as e:
75
+ logging.exception("Unexpected failure initializing MongoDB")
76
+ raise SystemExit(1) from e
77
77
 
78
78
  return db
79
79
 
@@ -90,14 +90,14 @@ def init_redis(settings: Settings | None = None) -> tuple:
90
90
  Returns (None, None) if Redis is not configured or unavailable.
91
91
 
92
92
  """
93
- if settings is None:
94
- settings = Settings()
95
-
96
93
  try:
97
94
  from redis import Redis as RedisSync
98
95
  from redis.asyncio.client import Redis
99
96
  from redis.exceptions import RedisError
100
97
 
98
+ if settings is None:
99
+ settings = Settings()
100
+
101
101
  redis_uri = getattr(settings, "redis_uri", None)
102
102
  if redis_uri:
103
103
  redis_sync: RedisSync = RedisSync.from_url(
@@ -115,8 +115,7 @@ def init_redis(settings: Settings | None = None) -> tuple:
115
115
  return redis_sync, redis
116
116
  except RedisError as e:
117
117
  logging.exception("Redis connection error")
118
- if settings.exit_on_init_failure:
119
- raise SystemExit(1) from e
118
+ raise SystemExit(1) from e
120
119
  except (ImportError, AttributeError, Exception):
121
120
  logging.exception("Error initializing Redis")
122
121
 
@@ -0,0 +1,186 @@
1
+ """MongoDB error types and PyMongo-to-HTTP exception conversion."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from fastapi_mongo_base.core.errors.resource_errors import (
8
+ ResourceAlreadyExistsError,
9
+ ResourceNotFoundError,
10
+ )
11
+ from fastapi_mongo_base.core.exceptions import BaseHTTPException
12
+
13
+ if TYPE_CHECKING:
14
+ from pymongo.errors import PyMongoError
15
+
16
+
17
+ class MongoDBError(BaseHTTPException):
18
+ """Base exception for all MongoDB errors."""
19
+
20
+ status_code: int = 500
21
+ error_code: str = "db_error"
22
+ message_en: str = "A database error occurred"
23
+ message_fa: str | None = "یک خطای پایگاه داده رخ داده است"
24
+
25
+ def __init__(
26
+ self,
27
+ detail: str | None = None,
28
+ message: dict | None = None,
29
+ **kwargs: object,
30
+ ) -> None:
31
+ """Initialize MongoDBError with optional detail, message, and data."""
32
+ super().__init__(
33
+ status_code=self.status_code,
34
+ error_code=self.error_code,
35
+ detail=detail or self.message_en,
36
+ message=message,
37
+ **kwargs,
38
+ )
39
+
40
+
41
+ class MongoDBConnectionError(MongoDBError):
42
+ """Raised when a MongoDB connection error occurs."""
43
+
44
+ status_code = 503
45
+ error_code = "mongodb_connection_error"
46
+ message_en = "A MongoDB connection error occurred"
47
+ message_fa = (
48
+ "در حال حاضر امکان اتصال به پایگاه داده وجود ندارد. "
49
+ "لطفاً چند لحظه دیگر دوباره تلاش کنید."
50
+ )
51
+
52
+
53
+ class MongoDBTimeoutError(MongoDBError):
54
+ """Raised when a MongoDB network timeout error occurs."""
55
+
56
+ status_code = 504
57
+ error_code = "mongodb_timeout_error"
58
+ message_en = "A MongoDB timeout error occurred"
59
+ message_fa = "یک خطای timeout پایگاه داده رخ داده است"
60
+
61
+
62
+ class MongodbOperationTimeoutError(MongoDBError):
63
+ """Raised when a MongoDB operation timeout error occurs."""
64
+
65
+ status_code = 504
66
+ error_code = "mongodb_operation_timeout_error"
67
+ message_en = "A MongoDB operation timeout error occurred"
68
+ message_fa = "یک خطای timeout عملیات پایگاه داده رخ داده است"
69
+
70
+
71
+ class DocumentNotFoundError(MongoDBError, ResourceNotFoundError):
72
+ """Raised when a document is not found."""
73
+
74
+ status_code = 404
75
+ error_code = "document_not_found"
76
+ message_en = "Document not found"
77
+ message_fa = "سند یافت نشد"
78
+
79
+
80
+ class DocumentAlreadyExistsError(MongoDBError, ResourceAlreadyExistsError):
81
+ """Raised when a document already exists."""
82
+
83
+ status_code = 409
84
+ error_code = "document_already_exists"
85
+ message_en = "Document already exists"
86
+ message_fa = "سند قبلاً وجود دارد"
87
+
88
+
89
+ class DocumentDuplicateKeyError(MongoDBError):
90
+ """Raised when a duplicate key constraint is violated."""
91
+
92
+ status_code = 409
93
+ error_code = "document_duplicate_key"
94
+ message_en = "Document with this key already exists"
95
+ message_fa = "سند با این کلید قبلاً وجود دارد"
96
+
97
+
98
+ class MultipleDocumentsFoundError(MongoDBError):
99
+ """Raised when a query expected one document but found several."""
100
+
101
+ status_code = 409
102
+ error_code = "multiple_documents_found"
103
+ message_en = "Multiple documents found"
104
+ message_fa = "چندین سند یافت شد"
105
+
106
+
107
+ def find_pymongo_error(exc: BaseException) -> PyMongoError | None:
108
+ """
109
+ Walk the exception chain and return the first PyMongoError.
110
+
111
+ Checks ``__cause__`` first, then ``__context__`` (unless suppressed).
112
+ """
113
+ try:
114
+ from pymongo.errors import PyMongoError
115
+ except ImportError:
116
+ return None
117
+
118
+ seen: set[int] = set()
119
+ current: BaseException | None = exc
120
+
121
+ while current is not None and id(current) not in seen:
122
+ seen.add(id(current))
123
+ if isinstance(current, PyMongoError):
124
+ return current
125
+
126
+ if current.__cause__ is not None:
127
+ current = current.__cause__
128
+ continue
129
+
130
+ if (
131
+ current.__context__ is not None
132
+ and not current.__suppress_context__
133
+ ):
134
+ current = current.__context__
135
+ continue
136
+
137
+ break
138
+
139
+ return None
140
+
141
+
142
+ def convert_pymongo_error(exc: PyMongoError) -> MongoDBError:
143
+ """Map a PyMongoError to the appropriate MongoDB HTTP exception."""
144
+ from pymongo.errors import (
145
+ AutoReconnect,
146
+ ConnectionFailure,
147
+ DuplicateKeyError,
148
+ ExecutionTimeout,
149
+ NetworkTimeout,
150
+ NotPrimaryError,
151
+ OperationFailure,
152
+ ServerSelectionTimeoutError,
153
+ WaitQueueTimeoutError,
154
+ WTimeoutError,
155
+ )
156
+
157
+ detail = str(exc)
158
+
159
+ if isinstance(exc, DuplicateKeyError):
160
+ return DocumentDuplicateKeyError(detail=detail)
161
+
162
+ if (
163
+ isinstance(exc, OperationFailure)
164
+ and getattr(exc, "code", None) == 11000
165
+ ):
166
+ return DocumentDuplicateKeyError(detail=detail)
167
+
168
+ if isinstance(exc, NetworkTimeout):
169
+ return MongoDBTimeoutError(detail=detail)
170
+
171
+ if isinstance(exc, (ExecutionTimeout, WTimeoutError)):
172
+ return MongodbOperationTimeoutError(detail=detail)
173
+
174
+ if isinstance(
175
+ exc,
176
+ (
177
+ ServerSelectionTimeoutError,
178
+ ConnectionFailure,
179
+ AutoReconnect,
180
+ NotPrimaryError,
181
+ WaitQueueTimeoutError,
182
+ ),
183
+ ):
184
+ return MongoDBConnectionError(detail=detail)
185
+
186
+ return MongoDBError(detail=detail)
@@ -0,0 +1,90 @@
1
+ """Resource error exceptions for HTTP responses."""
2
+
3
+ from fastapi_mongo_base.core.exceptions import BaseHTTPException
4
+
5
+
6
+ class ResourceError(BaseHTTPException):
7
+ """Base exception for all resource errors."""
8
+
9
+ status_code: int = 500
10
+ error_code: str = "resource_error"
11
+ message_en: str = "A resource error occurred"
12
+ message_fa: str | None = "مشکلی در منبع پیش آمد. لطفاً دوباره تلاش کنید."
13
+
14
+ def __init__(
15
+ self,
16
+ detail: str | None = None,
17
+ message: dict | None = None,
18
+ **kwargs: object,
19
+ ) -> None:
20
+ """Initialize ResourceError with optional detail, message, and data."""
21
+ super().__init__(
22
+ status_code=self.status_code,
23
+ error_code=self.error_code,
24
+ detail=detail or self.message_en,
25
+ message=message,
26
+ **kwargs,
27
+ )
28
+
29
+
30
+ class ResourceNotFoundError(ResourceError):
31
+ """Raised when a resource is not found."""
32
+
33
+ status_code = 404
34
+ error_code = "resource_not_found"
35
+ message_en = "Resource not found"
36
+ message_fa = "یافت نشد"
37
+
38
+
39
+ class ResourceAlreadyExistsError(ResourceError):
40
+ """Raised when a resource already exists."""
41
+
42
+ status_code = 409
43
+ error_code = "resource_already_exists"
44
+ message_en = "Resource already exists"
45
+ message_fa = "نمونه‌ی مشابه وجود دارد"
46
+
47
+
48
+ class ResourceConflictError(ResourceError):
49
+ """Raised when a resource conflict occurs."""
50
+
51
+ status_code = 409
52
+ error_code = "resource_conflict"
53
+ message_en = "Resource conflict"
54
+ message_fa = "اطلاعات ارسال شده تداخل دارد"
55
+
56
+
57
+ class ResourcePaymentRequiredError(ResourceError):
58
+ """Raised when a resource payment is required."""
59
+
60
+ status_code = 402
61
+ error_code = "resource_payment_required"
62
+ message_en = "Resource payment required"
63
+ message_fa = "برای دسترسی، پرداخت لازم است"
64
+
65
+
66
+ class ResourceForbiddenError(ResourceError):
67
+ """Raised when a resource is forbidden."""
68
+
69
+ status_code = 403
70
+ error_code = "permission_denied"
71
+ message_en = "Permission denied"
72
+ message_fa = "دسترسی غیر مجاز"
73
+
74
+
75
+ class ResourceGoneError(ResourceError):
76
+ """Raised when a resource is gone."""
77
+
78
+ status_code = 410
79
+ error_code = "resource_gone"
80
+ message_en = "Resource gone"
81
+ message_fa = "در دسترس نیست"
82
+
83
+
84
+ class ResourceLockedError(ResourceError):
85
+ """Raised when a resource is locked."""
86
+
87
+ status_code = 423
88
+ error_code = "resource_locked"
89
+ message_en = "Resource locked"
90
+ message_fa = "قفل شده است"