Skip to content

Commit

Permalink
Exp #5: Try FastDepends lib
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanmach committed Nov 30, 2024
1 parent 2c12e31 commit 86f4d4f
Show file tree
Hide file tree
Showing 8 changed files with 406 additions and 116 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.DS_Store

# any pyc files
*.pyc
*.pyc

.obsidian/
21 changes: 19 additions & 2 deletions fakeflix-foundations-of-enterprise-software-development/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Next: Finish https://youtu.be/pMuiVlnGqjk?si=Ln2pIQrJbLTXB_VX&t=723

# Foundations of enterprise software development

This course covers building scalable software architectures with a focus on modularity and enterprise patterns. Starting with a streaming MVP, it explores evolutionary design, coupling, module communication (synchronous and asynchronous), and deployment strategies. Practical and theory-driven, it equips developers to create robust, adaptable systems.
Expand All @@ -12,8 +14,10 @@ This course covers building scalable software architectures with a focus on modu
7. Principles of Modular Architecture
8. Coupling and Synchronous Communication Between Modules
9. Asynchronous Communication Between Modules
10. Deploying Modular Architectures
10. Deploying Modular Architectures


# Intro
## Docker

### Docker Volumes
Expand Down Expand Up @@ -107,4 +111,17 @@ Datasource "db": PostgreSQL database "fakeflix", schema "public" at "localhost:5
🚀 Your database is now in sync with your Prisma schema. Done in 54ms
✔ Generated Prisma Client (v6.0.0) to ./node_modules/@prisma/client in 31ms
```
```

## Others
### Video streaming fundamentals

**Notes**
* Chunks
* HTTP 206 Partial Content
* [How to handle Partial Content in Node.js](https://medium.com/@vishal1909/how-to-handle-partial-content-in-node-js-8b0a5aea216)

# Enterprise Development Patterns

[What is DDD - Eric Evans - DDD Europe 2019](https://youtu.be/pMuiVlnGqjk?si=j3TyfWpb6iJsupq6)

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"extensions":[
"ms-python.python",
"njpwerner.autodocstring",
"rangav.vscode-thunder-client"
"rangav.vscode-thunder-client",
"charliermarsh.ruff"
]
}
}
Expand Down
7 changes: 6 additions & 1 deletion flask-dependency-injection-poc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ Experiment with alternative approaches to inject dependencies in a Flask applica



## Journal log
## Experimentation log
#### `@2024-11-19`: Experiment #3: Try FastDepends lib
Made good progress trying to inject depedencies, as well as validating requests with Pydantic.
Currently facing problems with dependency overriding.
See [app_3_fastdepends.py](./app_3_fastdepends.py).

#### `2024-11-19` Challenge #2: Flask-Pydantic conflict with Flask-Injector
Problem: Flask-Pydantic tries to validate the type of injected dependecy.
...
Expand Down
157 changes: 157 additions & 0 deletions flask-dependency-injection-poc/app_3_fastdepends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
from functools import wraps
from typing import Annotated, Callable
from flask import Blueprint, Flask
from pydantic import BaseModel
from fast_depends import inject, Depends
from fast_depends.library import CustomField

# schemas


class WebhookRequest(BaseModel):
payload: dict


# services
class WebhookService:
def process_webhook(self, payload):
print("Processing webhook:", payload)


# decorator that injects user_id into the view
def include_user_id():
def decorator(f: Callable) -> Callable:
"""
When you decorate a function with @wraps(original_function), it ensures that:
1. The wrapped function (wrapper) retains the metadata (such as the name, docstring, and attributes) of the original function (f).
2. The wrapped function looks like the original function in tools and frameworks that inspect function metadata, such as Flask’s app.view_functions.
"""

@wraps(f)
def wrapper(*args, **kwargs):
user_id = "123"
return f(user_id, *args, **kwargs)

return wrapper

return decorator


# blueprints
webhooks_api = Blueprint("webhooks_api", __name__)


def get_webhook_service():
return WebhookService()


# ✅ Experiment #1: Ability to inject the service into the view
@webhooks_api.route("/exp1", methods=["GET"])
@inject
def my_webhook(webhook_service=Depends(get_webhook_service)):
print(webhook_service.process_webhook({"foo": "bar"}))
return "Webhook processed successfully"


class Validate(CustomField):
def get_body_dict(self):
# inspired on flask_pydantic: https://github.com/bauerji/flask-pydantic/blob/8595fa8b5513a336c9c679829f49ddc20f56377d/flask_pydantic/core.py#L87
from flask import request

data = request.get_json()
if data is None:
return {}
return data

def use(self, /, **kwargs):
if self.param_name == "body":
kwargs = super().use(**kwargs)
kwargs["body"] = self.get_body_dict()
return kwargs


# ✅ Experiment #1.2: Request validation using Pydantic. Based on: https://lancetnik.github.io/FastDepends/#usage
# 💥 Challenge A: `@inject` (see source code) can't see the request body because Flask doesn't pass it to the view (see `flask/app.py/Flask.dispatch_request` source code)
# and (of course), the library doesn't have Flask as a dependency.
# ✅ Solved: we need to use a custom field that will extract the request body from Flask.request,
# implemented through the `Validate` class
@webhooks_api.route("/exp1.2", methods=["POST"])
@inject
def request_validation(body: WebhookRequest = Validate()):
"""
@inject decorator plays multiple roles at the same time:
- resolve Depends classes
- cast types according to Python annotation
- validate incoming parameters using pydantic
"""
print(body)
return "Webhook processed successfully"


# ✅ Experiment #2: DI injection + request validation
# Discovery A: can use type aliasing to make the code more readable, less verbose, and more maintainable!
# Eg: WebhooksService = Annotated[WebhookService, Depends(get_webhook_service)]
WebhooksService = Annotated[WebhookService, Depends(get_webhook_service)]


@webhooks_api.route("/exp2", methods=["POST"])
@inject
def my_webhook2(
# webhooks_service: WebhooksService, # 💥 see Challenge #3/A
webhooks_service: WebhookService = Depends(get_webhook_service),
body: WebhookRequest = Validate(),
):
webhooks_service.process_webhook(body.payload)
return "Webhook processed successfully"


# Experiment #3: Dependencies Overriding (Testing) - see test_app.py
# 💥 Challenge A: override seems to work with Exp#1, which uses `Depends(get_webhook_service)`, but not
# with alias: `WebhooksService = Annotated[WebhookService, Depends(get_webhook_service)]`, nor `webhooks_service: WebhookService = Depends(get_webhook_service)`
#
# Error: pydantic_core._pydantic_core.ValidationError: 1 validation error for my_webhook2
# webhooks_service
# Input should be an instance of WebhookService [type=is_instance_of, input_value=<test_app_3_fastdepends.g...bject at 0xffff9bc4eaa0>, input_type=get_fake_webhook_service.<locals>.FakeWebhookService]
# For further information visit https://errors.pydantic.dev/2.9/v/is_instance_of
# (The same issue doesn't happen on FastAPI)
#
# @2024-11-24: Opened issue: https://github.com/Lancetnik/FastDepends/issues/150
#

def create_app():
app = Flask(__name__)

@app.route("/")
def hello_world():
return """
<html>
<body>
<form id="myForm">
<input type="submit" value="Submit to /exp2">
</form>
<script>
document.getElementById('myForm').addEventListener('submit', function(event) {
event.preventDefault();
fetch('/exp2', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({"payload": {"foo": "bar"}})
}).then(response => response.json()).then(data => {
console.log(data);
});
});
</script>
</body>
</html>
"""

app.register_blueprint(webhooks_api)

return app


if __name__ == "__main__":
app = create_app()
app.run(debug=True)
Loading

0 comments on commit 86f4d4f

Please sign in to comment.