Skip to content

Commit 60a58d2

Browse files
committed
feat(rq): add section on task queues
1 parent 271e593 commit 60a58d2

File tree

319 files changed

+10548
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

319 files changed

+10548
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# How to send emails with Python and Mailgun
2+
3+
To send e-mails using Python, we are going to use Mailgun, a third party service which actually delivers the messages.
4+
5+
You could use [your own personal account and the built-in `email` and `smtp` libraries](https://blog.teclado.com/learn-python-send-emails/), but most personal e-mail providers will limit how many e-mails you can send per day. Plus, you won't get analytics and a host of other features that you can get with an email service like Mailgun.
6+
7+
There are two ways to use the Mailgun service: [via SMTP or via their API](https://www.mailgun.com/blog/email/difference-between-smtp-and-api/). I'll show you how to use the API since it's a bit easier and has the same functionality.
8+
9+
Sending an e-mail with Mailgun is just a matter of sending a request to their API. To do this, we'll use the `requests` library:
10+
11+
```bash
12+
pip install requests
13+
```
14+
15+
Remember to add it to your `requirements.txt` as well:
16+
17+
```text title="requirements.txt"
18+
requests
19+
```
20+
21+
## Setting up for Mailgun
22+
23+
Before we can send any emails, we need to set up our Mailgun account. First, register over at [https://mailgun.com](https://mailgun.com).
24+
25+
Once you have registered, select your sandbox domain. It's in [your dashboard](https://app.mailgun.com/app/dashboard), at the bottom. It looks like this: `sandbox847487f8g78.mailgun.org`.
26+
27+
Then at the top right, enter your personal email address under "Authorized recipients".
28+
29+
You will get an email to confirm. Click the button that you see in that email to add your personal email to the list of authorized recipients.
30+
31+
Next up, grab your API key. You can find it by clicking on this button (my domain and API key are blurred in this screenshot):
32+
33+
![Click the 'Select' button to reveal your Mailgun API key](./assets/mailgun-api-key.png)
34+
35+
## Sending emails with Mailgun
36+
37+
To make the API request which sends an email, we'll use a function that looks very much like this one (taken from their documentation):
38+
39+
```py
40+
def send_simple_message():
41+
return requests.post(
42+
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
43+
auth=("api", "YOUR_API_KEY"),
44+
data={"from": "Excited User <mailgun@YOUR_DOMAIN_NAME>",
45+
"to": ["bar@example.com", "YOU@YOUR_DOMAIN_NAME"],
46+
"subject": "Hello",
47+
"text": "Testing some Mailgun awesomness!"})
48+
```
49+
50+
So let's go into our User resource and add a couple of imports and this function. Make sure to replace "Your Name" with your actual name or that of your application:
51+
52+
```py title="resources/user.py"
53+
import os
54+
import requests
55+
56+
...
57+
58+
def send_simple_message(to, subject, body):
59+
domain = os.getenv("MAILGUN_DOMAIN")
60+
return requests.post(
61+
f"https://api.mailgun.net/v3/{domain}/messages",
62+
auth=("api", os.getenv("MAILGUN_API_KEY")),
63+
data={
64+
"from": f"Your Name <mailgun@{domain}>",
65+
"to": [to],
66+
"subject": subject,
67+
"text": body,
68+
},
69+
)
70+
```
71+
72+
Then let's go to the `.env` file and add your Mailgun API key and domain:
73+
74+
```text title=".env"
75+
MAILGUN_API_KEY="1f1ahfjhf4878797887187j-5ac54n"
76+
MAILGUN_DOMAIN="sandbox723b05d9.mailgun.org"
77+
```
78+
79+
With this, we're ready to actually send emails!
889 KB
Loading
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
DATABASE_URL=
2+
MAILGUN_API_KEY=
3+
MAILGUN_DOMAIN=
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FLASK_APP=app
2+
FLASK_ENV=development
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.env
2+
.venv
3+
.vscode
4+
__pycache__
5+
data.db
6+
*.pyc
7+
.DS_Store
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# CONTRIBUTING
2+
3+
## How to run the Dockerfile locally
4+
5+
```
6+
docker run -dp 5000:5000 -w /app -v "$(pwd):/app" IMAGE_NAME sh -c "flask run"
7+
```
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM python:3.10
2+
WORKDIR /app
3+
COPY requirements.txt .
4+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
5+
COPY . .
6+
CMD ["/bin/bash", "docker-entrypoint.sh"]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# REST APIs Recording Project
2+
3+
Nothing here yet!
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import os
2+
3+
from flask import Flask, jsonify
4+
from flask_smorest import Api
5+
from flask_jwt_extended import JWTManager
6+
from flask_migrate import Migrate
7+
from dotenv import load_dotenv
8+
9+
10+
from db import db
11+
from blocklist import BLOCKLIST
12+
import models
13+
14+
from resources.item import blp as ItemBlueprint
15+
from resources.store import blp as StoreBlueprint
16+
from resources.tag import blp as TagBlueprint
17+
from resources.user import blp as UserBlueprint
18+
19+
20+
def create_app(db_url=None):
21+
app = Flask(__name__)
22+
load_dotenv()
23+
24+
app.config["PROPAGATE_EXCEPTIONS"] = True
25+
app.config["API_TITLE"] = "Stores REST API"
26+
app.config["API_VERSION"] = "v1"
27+
app.config["OPENAPI_VERSION"] = "3.0.3"
28+
app.config["OPENAPI_URL_PREFIX"] = "/"
29+
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
30+
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
31+
app.config["SQLALCHEMY_DATABASE_URI"] = db_url or os.getenv("DATABASE_URL", "sqlite:///data.db")
32+
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
33+
db.init_app(app)
34+
migrate = Migrate(app, db)
35+
api = Api(app)
36+
37+
app.config["JWT_SECRET_KEY"] = "jose"
38+
jwt = JWTManager(app)
39+
40+
@jwt.token_in_blocklist_loader
41+
def check_if_token_in_blocklist(jwt_header, jwt_payload):
42+
return jwt_payload["jti"] in BLOCKLIST
43+
44+
@jwt.revoked_token_loader
45+
def revoked_token_callback(jwt_header, jwt_payload):
46+
return (
47+
jsonify(
48+
{"description": "The token has been revoked.", "error": "token_revoked"}
49+
),
50+
401,
51+
)
52+
53+
@jwt.needs_fresh_token_loader
54+
def token_not_fresh_callback(jwt_header, jwt_payload):
55+
return (
56+
jsonify(
57+
{
58+
"description": "The token is not fresh.",
59+
"error": "fresh_token_required",
60+
}
61+
),
62+
401,
63+
)
64+
65+
@jwt.additional_claims_loader
66+
def add_claims_to_jwt(identity):
67+
# Look in the database and see whether the user is an admin
68+
if identity == 1:
69+
return {"is_admin": True}
70+
return {"is_admin": False}
71+
72+
@jwt.expired_token_loader
73+
def expired_token_callback(jwt_header, jwt_payload):
74+
return (
75+
jsonify({"message": "The token has expired.", "error": "token_expired"}),
76+
401,
77+
)
78+
79+
@jwt.invalid_token_loader
80+
def invalid_token_callback(error):
81+
return (
82+
jsonify(
83+
{"message": "Signature verification failed.", "error": "invalid_token"}
84+
),
85+
401,
86+
)
87+
88+
@jwt.unauthorized_loader
89+
def missing_token_callback(error):
90+
return (
91+
jsonify(
92+
{
93+
"description": "Request does not contain an access token.",
94+
"error": "authorization_required",
95+
}
96+
),
97+
401,
98+
)
99+
100+
@app.before_first_request
101+
def create_tables():
102+
db.create_all()
103+
104+
api.register_blueprint(ItemBlueprint)
105+
api.register_blueprint(StoreBlueprint)
106+
api.register_blueprint(TagBlueprint)
107+
api.register_blueprint(UserBlueprint)
108+
109+
return app
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""
2+
blocklist.py
3+
4+
This file just contains the blocklist of the JWT tokens. It will be imported by
5+
app and the logout resource so that tokens can be added to the blocklist when the
6+
user logs out.
7+
"""
8+
9+
BLOCKLIST = set()

0 commit comments

Comments
 (0)