flask_dependency 0.2.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.
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.3
2
+ Name: flask_dependency
3
+ Version: 0.2.0
4
+ Summary:
5
+ Author: ventura94
6
+ Author-email: arianventura94@gmail.com
7
+ Requires-Python: >=3.10,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Requires-Dist: flask (>=3.1.1,<4.0.0)
14
+ Requires-Dist: pydantic[dotenv] (>=1.10.19,<2.0.0)
15
+ Description-Content-Type: text/markdown
16
+
17
+ ## Flask Dependency
18
+
19
+ Dependency injection is a powerful concept that allows you to manage and inject dependencies into your application
20
+ components. In Flask, we can create a simple dependency injection system inspired by FastAPI's `Depends`.
21
+
22
+ ### Approach
23
+
24
+ 1. **Create a Dependency Class**: We'll define a class called `Depends` that will handle our dependencies. This class
25
+ will allow us to declare dependencies for specific route handlers.
26
+
27
+ 2. **Decorator for Dependency Resolution**: We'll create a decorator that inspects the function and resolves any
28
+ dependencies when called. This decorator will be applied to our route handlers.
29
+
30
+ 3. **Dependency Functions**: We'll define individual functions (similar to FastAPI's dependencies) that represent our
31
+ dependencies. These functions will be called automatically when needed.
32
+
33
+ ### Sample Code
34
+
35
+ ```python
36
+ # app.py
37
+ from flask import Flask, Blueprint
38
+ from pydantic import BaseModel
39
+ from flask_dependency import BackgroundTask, Depends, route, FormModel
40
+
41
+ app = Flask(__name__)
42
+ blueprint = Blueprint("example_blueprint", __name__)
43
+ app.debug = True
44
+
45
+
46
+ def sample_task(duration):
47
+ import time
48
+ time.sleep(duration)
49
+ return f"Slept for {duration} seconds"
50
+
51
+
52
+ # Example Backgraund Task
53
+ @route(blueprint, "/backgraund_task", methods=["POST"], endpoint="route_test")
54
+ def route_test(background_task: BackgroundTask = Depends()):
55
+ background_task.run(sample_task, 1)
56
+ return {"message": "OK"}
57
+
58
+
59
+ class InputModelForm(FormModel):
60
+ id: int
61
+ name: str
62
+
63
+
64
+ class ResponseInputModelForm(BaseModel):
65
+ id: int
66
+ name: str
67
+
68
+ class Config:
69
+ orm_mode = True
70
+
71
+
72
+ # Example Form
73
+ @route(blueprint, "/example_form", methods=["POST"], endpoint="route_test", response_schema=ResponseInputModelForm)
74
+ def route_test(input_data: InputModelForm = Depends()):
75
+ return {"message": "success"}
76
+
77
+
78
+ app.register_blueprint(blueprint)
79
+ ```
80
+
81
+
@@ -0,0 +1,64 @@
1
+ ## Flask Dependency
2
+
3
+ Dependency injection is a powerful concept that allows you to manage and inject dependencies into your application
4
+ components. In Flask, we can create a simple dependency injection system inspired by FastAPI's `Depends`.
5
+
6
+ ### Approach
7
+
8
+ 1. **Create a Dependency Class**: We'll define a class called `Depends` that will handle our dependencies. This class
9
+ will allow us to declare dependencies for specific route handlers.
10
+
11
+ 2. **Decorator for Dependency Resolution**: We'll create a decorator that inspects the function and resolves any
12
+ dependencies when called. This decorator will be applied to our route handlers.
13
+
14
+ 3. **Dependency Functions**: We'll define individual functions (similar to FastAPI's dependencies) that represent our
15
+ dependencies. These functions will be called automatically when needed.
16
+
17
+ ### Sample Code
18
+
19
+ ```python
20
+ # app.py
21
+ from flask import Flask, Blueprint
22
+ from pydantic import BaseModel
23
+ from flask_dependency import BackgroundTask, Depends, route, FormModel
24
+
25
+ app = Flask(__name__)
26
+ blueprint = Blueprint("example_blueprint", __name__)
27
+ app.debug = True
28
+
29
+
30
+ def sample_task(duration):
31
+ import time
32
+ time.sleep(duration)
33
+ return f"Slept for {duration} seconds"
34
+
35
+
36
+ # Example Backgraund Task
37
+ @route(blueprint, "/backgraund_task", methods=["POST"], endpoint="route_test")
38
+ def route_test(background_task: BackgroundTask = Depends()):
39
+ background_task.run(sample_task, 1)
40
+ return {"message": "OK"}
41
+
42
+
43
+ class InputModelForm(FormModel):
44
+ id: int
45
+ name: str
46
+
47
+
48
+ class ResponseInputModelForm(BaseModel):
49
+ id: int
50
+ name: str
51
+
52
+ class Config:
53
+ orm_mode = True
54
+
55
+
56
+ # Example Form
57
+ @route(blueprint, "/example_form", methods=["POST"], endpoint="route_test", response_schema=ResponseInputModelForm)
58
+ def route_test(input_data: InputModelForm = Depends()):
59
+ return {"message": "success"}
60
+
61
+
62
+ app.register_blueprint(blueprint)
63
+ ```
64
+
@@ -0,0 +1,7 @@
1
+ __version__ = "0.1.3"
2
+
3
+ from .background_task import BackgroundTask
4
+ from .depends import Depends
5
+ from .form import FormModel
6
+ from .query import QueryModel
7
+ from .route import route
@@ -0,0 +1,11 @@
1
+ from concurrent.futures import ThreadPoolExecutor, Future
2
+ from typing import Callable, Any
3
+
4
+
5
+ class BackgroundTask:
6
+ def __init__(self, max_workers: int = 1):
7
+ self.executor = ThreadPoolExecutor(max_workers=max_workers)
8
+
9
+ def run(self, func: Callable, *args: Any, **kwargs: Any) -> Future:
10
+ future = self.executor.submit(func, *args, **kwargs)
11
+ return future
@@ -0,0 +1,31 @@
1
+ from typing import Any, Callable, Optional
2
+
3
+ from flask import g
4
+
5
+
6
+ class Depends:
7
+ def __init__(self, dependency: Optional[Callable[..., Any]] = None, cache: bool = True):
8
+ self.dependency = dependency
9
+ self.cache_key = f"_depends_cache_{id(dependency)}"
10
+ self.cache = cache
11
+
12
+ def __call__(self):
13
+ if not hasattr(g, self.cache_key) or not self.cache:
14
+ result = self.dependency()
15
+ if hasattr(result, "__enter__") and hasattr(result, "__exit__"):
16
+ with result as ctx_result:
17
+ setattr(g, self.cache_key, ctx_result)
18
+ return ctx_result
19
+ setattr(g, self.cache_key, result)
20
+ return result
21
+ return getattr(g, self.cache_key)
22
+
23
+ def exists(self) -> bool:
24
+ print(hasattr(g, self.cache_key))
25
+ return bool(hasattr(g, self.cache_key))
26
+
27
+ @classmethod
28
+ def attach_dependency(cls, dependency_instance: Any) -> Any:
29
+ instance = cls(type(dependency_instance))
30
+ setattr(g, instance.cache_key, dependency_instance)
31
+ return dependency_instance
@@ -0,0 +1,22 @@
1
+ from pydantic import ValidationError
2
+
3
+
4
+ class UnprocessableContent(Exception):
5
+ def __init__(
6
+ self,
7
+ error: ValidationError,
8
+ message: str = "Unprocessable Content",
9
+ status_code: int = 422,
10
+ ):
11
+ super().__init__(error)
12
+ self.error = error
13
+ self.message = message
14
+ self.status_code = status_code
15
+ self.error_details = self.get_error_details()
16
+
17
+ def get_error_details(self):
18
+ error_messages = []
19
+ for error in self.error.errors():
20
+ field = ".".join(str(loc) for loc in error["loc"])
21
+ error_messages.append(f"{field} {error['msg']}")
22
+ return error_messages
@@ -0,0 +1,17 @@
1
+ import json
2
+
3
+ from .exceptions.unprocessable_content import UnprocessableContent
4
+ from flask import request
5
+ from pydantic import BaseModel, ValidationError
6
+
7
+
8
+ class FormModel(BaseModel):
9
+ def __init__(self):
10
+ data = request.get_json()
11
+ try:
12
+ if isinstance(data, (str, bytes, bytearray)):
13
+ super().__init__(**json.loads(data))
14
+ if isinstance(data, dict):
15
+ super().__init__(**data)
16
+ except ValidationError as exc:
17
+ raise UnprocessableContent(message="Invalid Input Data", error=exc)
@@ -0,0 +1,32 @@
1
+ from inspect import signature
2
+ from types import UnionType
3
+ from typing import get_origin, get_args
4
+
5
+ from .depends import Depends
6
+
7
+
8
+ def inject(func):
9
+ sig = signature(func)
10
+ params = sig.parameters
11
+
12
+ def wrapper(*args, **kwargs):
13
+ for name, param in params.items():
14
+ if isinstance(param.default, Depends):
15
+ if param.default.dependency:
16
+ kwargs[name] = param.default()
17
+ else:
18
+ if get_origin(param.annotation) is UnionType:
19
+ possible_types = get_args(param.annotation)
20
+ else:
21
+ possible_types = (param.annotation,)
22
+ if len(possible_types) == 1:
23
+ kwargs[name] = Depends(possible_types[0])()
24
+ else:
25
+ for dep_type in possible_types:
26
+ depends = Depends(dep_type)
27
+ if depends.exists():
28
+ kwargs[name] = depends()
29
+ break
30
+ return func(*args, **kwargs)
31
+
32
+ return wrapper
@@ -0,0 +1,17 @@
1
+ import json
2
+
3
+ from .exceptions.unprocessable_content import UnprocessableContent
4
+ from flask import request
5
+ from pydantic import BaseModel, ValidationError
6
+
7
+
8
+ class QueryModel(BaseModel):
9
+ def __init__(self):
10
+ data = request.args
11
+ try:
12
+ if isinstance(data, (str, bytes, bytearray)):
13
+ super().__init__(**json.loads(data))
14
+ if isinstance(data, dict):
15
+ super().__init__(**data)
16
+ except ValidationError as e:
17
+ raise UnprocessableContent(message="Invalid Query Data", error=e)
@@ -0,0 +1,37 @@
1
+ from functools import wraps
2
+ from typing import List, Literal, Type
3
+
4
+ from flask import Blueprint
5
+ from pydantic import BaseModel
6
+
7
+ from .depends import Depends
8
+ from .inject import inject
9
+ from .validate_schema import validate_schema
10
+
11
+
12
+ def route(
13
+ blueprint: Blueprint,
14
+ rule: str,
15
+ endpoint: str,
16
+ methods: List[Literal["GET", "POST", "PUT", "PATCH", "DELETE"]],
17
+ response_schema: Type[BaseModel] = None,
18
+ status_code_response: int = 200,
19
+ dependencies: List[Depends] = None,
20
+ ):
21
+ def decorator(func):
22
+ func = inject(func)
23
+
24
+ @wraps(func)
25
+ @blueprint.route(rule, methods=methods, endpoint=endpoint)
26
+ def wrapper(*args, **kwargs):
27
+
28
+ if dependencies:
29
+ [_() for _ in dependencies]
30
+ data_response = func(*args, **kwargs)
31
+ if response_schema:
32
+ data_response = validate_schema(response_schema, data_response).dict()
33
+ return data_response, status_code_response
34
+
35
+ return wrapper
36
+
37
+ return decorator
@@ -0,0 +1,11 @@
1
+ from typing import Type, Any, Dict
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ def validate_schema(schema: Type[BaseModel], data: Any) -> BaseModel:
7
+ if isinstance(data, str):
8
+ return schema.parse_raw(data)
9
+ if isinstance(data, Dict):
10
+ return schema(**data)
11
+ return schema.from_orm(data)
@@ -0,0 +1,19 @@
1
+ [tool.poetry]
2
+ name = "flask_dependency"
3
+ version = "0.2.0"
4
+ description = ""
5
+ authors = ["ventura94 <arianventura94@gmail.com>"]
6
+ readme = "README.md"
7
+
8
+ [tool.poetry.dependencies]
9
+ python = "^3.10"
10
+ flask = "^3.1.1"
11
+ pydantic = { version = "^1.10.19", extras = ["dotenv"] }
12
+
13
+ [tool.poetry.group.tests.dependencies]
14
+ pytest = "^8.2.2"
15
+ httpx = "^0.27.0"
16
+
17
+ [build-system]
18
+ requires = ["poetry-core"]
19
+ build-backend = "poetry.core.masonry.api"