fastapi-mongo-base 1.2.0__tar.gz → 1.3.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.
- {fastapi_mongo_base-1.2.0/src/fastapi_mongo_base.egg-info → fastapi_mongo_base-1.3.0}/PKG-INFO +9 -9
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/pyproject.toml +9 -9
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/core/config.py +0 -3
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/core/db.py +22 -23
- fastapi_mongo_base-1.3.0/src/fastapi_mongo_base/core/errors/mongodb_errors.py +186 -0
- fastapi_mongo_base-1.3.0/src/fastapi_mongo_base/core/errors/resource_errors.py +90 -0
- fastapi_mongo_base-1.3.0/src/fastapi_mongo_base/core/exceptions.py +269 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/routes.py +1 -1
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0/src/fastapi_mongo_base.egg-info}/PKG-INFO +9 -9
- fastapi_mongo_base-1.3.0/src/fastapi_mongo_base.egg-info/SOURCES.txt +53 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base.egg-info/requires.txt +5 -5
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/tests/conftest.py +7 -10
- fastapi_mongo_base-1.3.0/tests/test_mongodb_errors.py +247 -0
- fastapi_mongo_base-1.3.0/tests/test_resource_errors.py +171 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/__pycache__/__init__.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/__pycache__/models.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/__pycache__/routes.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/__pycache__/schemas.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/__pycache__/tasks.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/core/__pycache__/__init__.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/core/__pycache__/app_factory.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/core/__pycache__/config.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/core/__pycache__/db.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/core/__pycache__/exceptions.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/core/errors/db_errors.py +0 -798
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/core/errors/i18n.py +0 -118
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/core/errors/resource_errors.py +0 -354
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/core/exceptions.py +0 -374
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/utils/__pycache__/basic.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/utils/__pycache__/bsontools.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base/utils/__pycache__/timezone.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/src/fastapi_mongo_base.egg-info/SOURCES.txt +0 -80
- fastapi_mongo_base-1.2.0/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/tests/__pycache__/conftest.cpython-312-pytest-9.1.1.pyc +0 -0
- fastapi_mongo_base-1.2.0/tests/__pycache__/test_apis.cpython-312-pytest-9.1.1.pyc +0 -0
- fastapi_mongo_base-1.2.0/tests/__pycache__/test_app.cpython-312-pytest-9.1.1.pyc +0 -0
- fastapi_mongo_base-1.2.0/tests/__pycache__/test_db_init.cpython-312-pytest-9.1.1.pyc +0 -0
- fastapi_mongo_base-1.2.0/tests/__pycache__/test_i18n_errors.cpython-312-pytest-9.1.1.pyc +0 -0
- fastapi_mongo_base-1.2.0/tests/__pycache__/test_mongo_exceptions.cpython-312-pytest-9.1.1.pyc +0 -0
- fastapi_mongo_base-1.2.0/tests/__pycache__/test_resource_exceptions.cpython-312-pytest-9.1.1.pyc +0 -0
- fastapi_mongo_base-1.2.0/tests/app/__pycache__/__init__.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/tests/app/__pycache__/server.cpython-312.pyc +0 -0
- fastapi_mongo_base-1.2.0/tests/test_db_init.py +0 -84
- fastapi_mongo_base-1.2.0/tests/test_i18n_errors.py +0 -149
- fastapi_mongo_base-1.2.0/tests/test_mongo_exceptions.py +0 -127
- fastapi_mongo_base-1.2.0/tests/test_resource_exceptions.py +0 -186
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/LICENSE.txt +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/MANIFEST.in +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/README.md +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/docs/boilerplate.md +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/docs/changelog.md +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/docs/contributing.md +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/docs/endpoints.md +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/docs/extra.css +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/docs/index.md +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/docs/installation.md +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/docs/license.md +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/docs/quickstart.md +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/docs/settings.md +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/docs/tutorial.md +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/docs/usage.md +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/mkdocs.yml +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/pytest.ini +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/setup.cfg +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/__init__.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/core/__init__.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/core/app_factory.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/core/enums.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/models.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/py.typed +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/schemas.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/sql.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/tasks.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/utils/__init__.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/utils/basic.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/utils/bsontools.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/utils/timezone.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base/utils/usso_routes.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base.egg-info/dependency_links.txt +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/src/fastapi_mongo_base.egg-info/top_level.txt +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/tests/__init__.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/tests/app/__init__.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/tests/app/logs/app.log +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/tests/app/main.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/tests/app/server.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/tests/test_apis.py +0 -0
- {fastapi_mongo_base-1.2.0 → fastapi_mongo_base-1.3.0}/tests/test_app.py +0 -0
{fastapi_mongo_base-1.2.0/src/fastapi_mongo_base.egg-info → fastapi_mongo_base-1.3.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-mongo-base
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
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>
|
|
@@ -15,20 +15,20 @@ Classifier: Development Status :: 3 - Alpha
|
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
16
16
|
Classifier: Topic :: Software Development :: Build Tools
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
21
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
-
Requires-Python: >=3.
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
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
25
|
Requires-Dist: pydantic>=2.13.4
|
|
30
|
-
Requires-Dist:
|
|
26
|
+
Requires-Dist: httpx>=0.28.1
|
|
31
27
|
Requires-Dist: singleton_package>=0.8.2
|
|
28
|
+
Requires-Dist: json-advanced>=0.13.0
|
|
29
|
+
Requires-Dist: pytz>=2026.2
|
|
30
|
+
Requires-Dist: beanie>=1.30.0
|
|
31
|
+
Requires-Dist: fastapi>=0.129.0
|
|
32
32
|
Requires-Dist: uvicorn[standard]>=0.41.0
|
|
33
33
|
Requires-Dist: uuid6>=2025.0.1
|
|
34
34
|
Provides-Extra: usso
|
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fastapi-mongo-base"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.3.0"
|
|
8
8
|
description = "A simple boilerplate application, including models and schemas and abstract router, for FastAPI with MongoDB"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
license-files = ["LICENSE.txt"]
|
|
13
13
|
keywords = ["fastapi", "mongodb", "beanie"]
|
|
@@ -18,19 +18,19 @@ classifiers = [
|
|
|
18
18
|
"Intended Audience :: Developers",
|
|
19
19
|
"Topic :: Software Development :: Build Tools",
|
|
20
20
|
"Programming Language :: Python :: 3",
|
|
21
|
-
"Programming Language :: Python :: 3.10",
|
|
22
|
-
"Programming Language :: Python :: 3.11",
|
|
23
21
|
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Programming Language :: Python :: 3.14",
|
|
24
24
|
"Programming Language :: Python :: 3 :: Only",
|
|
25
25
|
]
|
|
26
26
|
dependencies = [
|
|
27
|
-
"beanie>=1.30.0",
|
|
28
|
-
"fastapi>=0.129.0",
|
|
29
|
-
"httpx>=0.28.1",
|
|
30
|
-
"json-advanced>=0.12.11",
|
|
31
27
|
"pydantic>=2.13.4",
|
|
32
|
-
"
|
|
28
|
+
"httpx>=0.28.1",
|
|
33
29
|
"singleton_package>=0.8.2",
|
|
30
|
+
"json-advanced>=0.13.0",
|
|
31
|
+
"pytz>=2026.2",
|
|
32
|
+
"beanie>=1.30.0",
|
|
33
|
+
"fastapi>=0.129.0",
|
|
34
34
|
"uvicorn[standard]>=0.41.0",
|
|
35
35
|
"uuid6>=2025.0.1",
|
|
36
36
|
]
|
|
@@ -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
|
-
|
|
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
|
|
64
|
+
except ServerSelectionTimeoutError as e:
|
|
73
65
|
logging.exception(
|
|
74
|
-
"MongoDB
|
|
66
|
+
"MongoDB connection timeout at %s", settings.mongo_uri
|
|
75
67
|
)
|
|
76
|
-
raise
|
|
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
|
-
|
|
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 = "قفل شده است"
|