État d’esprit et méthodologie
2024
[…] décrit d’une manière abstraite et schématique les différents éléments d’un système informatique, leurs interrelations et leurs interactions.
import click
@click.command()
@click.option("--count", default=1)
@click.option("--name", prompt="Your name")
def hello(count, name):
for x in range(count):
f"Hello {name}!") click.echo(
$ python hello.py --count=3
Your name: John
Hello John!
Hello John!
Hello John!
import sqlite3
= sqlite3.connect("tutorial.db")
db "CREATE TABLE movie(title, year, score)")
db.execute(
= db.execute("SELECT title FROM movie").fetchone() result
SQLAlchemy est un toolkit open source SQL et un mapping objet-relationnel (ORM) écrit en Python et publié sous licence MIT.
= create_engine("sqlite:///todos.db", echo=True)
engine = MetaData()
metadata
= Table(
todos_table "todos",
metadata,"id", Uuid, primary_key=True, default=uuid.uuid4),
Column("task", String, nullable=False),
Column("complete", Boolean, nullable=False),
Column("due", DateTime, nullable=True)
Column( )
= todos_table.insert().values(
stmt =task,
task=complete,
complete=due
due
)
with engine.begin() as conn:
= conn.execute(stmt) result
Flask is a lightweight WSGI web application framework. It is designed to make getting started quick and easy, with the ability to scale up to complex applications.
from flask import Flask
= Flask(__name__)
app
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
$ flask --app hello run
* Serving Flask app 'hello'
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
from flask import render_template
@app.route("/hello/")
@app.route("/hello/<name>")
def hello(name=None):
return render_template("hello.html", name=name)
<!doctype html>
<title>Hello from Flask</title>
{% if name %}<h1>Hello {{ name }}!</h1>
{% else %}<h1>Hello, World!</h1>
{% endif %}
.
├── pyproject.toml
├── README.md
└── src
└── toudou
├── __init__.py
├── models.py
├── services.py
├── templates
│ └── home.html
└── views.py
Après avoir configuré une application Flask :
$ pdm add flask
$ pdm run flask --app toudou.views --debug run
Puis se rendre sur http://localhost:5000.
from flask import Blueprint, render_template
= Blueprint("web_ui", __name__, url_prefix="/")
web_ui
@web_ui.route("/<page>")
def show(page):
return render_template(f"pages/{page}.html")
def create_app():
= Flask(__name__)
app
from toudou.views import api, web_ui
app.register_blueprint(api)
app.register_blueprint(web_ui)
return app
Rien ne change, Flask cherche une fonction create_app
:
$ pdm run flask --app toudou.views --debug run
= create_engine("sqlite:///todos.db", echo=True)
engine = MetaData() metadata
from toudou import config
= create_engine(config["DATABASE_URL"], echo=config["DEBUG"])
engine = MetaData() metadata
Dans le fichier src/toudou/__init__.py
:
= dict(
config ="sqlite:///todos.db",
DATABASE_URL=True
DEBUG )
Collection de fichiers contenant la configuration pour chaque environnement :
dev.env
testing.env
demo.env
prod.env
Dans le fichier dev.env
:
TOUDOU_DATABASE_URL=sqlite:///todos.db
TOUDOU_DEBUG=True
TOUDOU_FLASK_SECRET_KEY=secret!
$ env $(cat dev.env | xargs) pdm run flask --app ...
Dans le fichier pyproject.toml
:
[tool.pdm.scripts]
_.env_file = "dev.env"
start = "flask --app toudou.views --debug run"
Dans le fichier src/toudou/__init__.py
:
import os
= dict(
config =os.getenv("TOUDOU_DATABASE_URL", ""),
DATABASE_URL=os.getenv("TOUDOU_DEBUG", "False") == "True"
DEBUG )
="TOUDOU_FLASK") app.config.from_prefixed_env(prefix
@app.errorhandler(500)
def handle_internal_error(error):
"Erreur interne du serveur", "error")
flash(return redirect(url_for(".home"))
from flask import abort, render_template
@app.get("/todos/create")
def todo_create_form():
500)
abort(return render_template("todo_create_form.html")
Syntaxique : le type de la donnée est respecté (date, entier, texte, etc).
Sémantique : les données ont du sens (date de début inférieur à la date de fin, prix supérieur à 0, etc).
WTForms is a flexible forms validation and rendering library for Python web development.
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired
class MyForm(FlaskForm):
= StringField("name", validators=[DataRequired()]) name
@app.route("/submit", methods=["GET", "POST"])
def submit():
= MyForm()
form if form.validate_on_submit():
return redirect("/success")
return render_template("submit.html", form=form)
<form method="POST" action="/">
{{ form.csrf_token }}
{{ form.name.label }} {{ form.name(size=20) }}<input type="submit" value="Go">
</form>
$ pdm add flask-wtf
import logging
"Watch out!") # will print to the console
logging.warning("I told you so") # will not print anything logging.info(
import logging
logging.basicConfig(=logging.INFO,
levelformat="%(asctime)s [%(levelname)s] %(message)s",
=[
handlers"toudou.log"),
logging.FileHandler(
logging.StreamHandler()
] )
import logging
@web_ui.errorhandler(500)
def handle_internal_error(error):
"Erreur interne du serveur", "error")
flash(
logging.exception(error)return redirect(url_for(".home"))
from flask import Flask
from flask_httpauth import HTTPBasicAuth
from werkzeug.security import generate_password_hash
= Flask(__name__)
app = HTTPBasicAuth()
auth
= {
users "john": generate_password_hash("hello"),
"susan": generate_password_hash("bye")
}
from werkzeug.security import check_password_hash
@auth.verify_password
def verify_password(username, password):
if username in users and \
check_password_hash(users.get(username), password):return username
@app.route("/")
@auth.login_required
def index():
return f"Hello, {auth.current_user()}!"
@auth.get_user_roles
def get_user_roles(user):
return user.roles
@app.route("/admin")
@auth.login_required(role="admin")
def admins_only():
return f"Hello {auth.current_user()}, you are an admin!"
$ pdm add flask-httpauth
admin
qui accède à tout ;user
qui n’est qu’en lecture seule.Pour se déconnecter avec HTTP Auth :
ctrl+shift+del
REST (representational state transfer) est un style d’architecture logicielle définissant un ensemble de contraintes à utiliser pour créer des services web.
Les API RESTful basées sur HTTP sont définies par :
URI | Verb | Action |
---|---|---|
/todos | GET | Récupère la collection de ressources Todo |
/todos | POST | Crée une ressource dans la collection |
/todos | PUT | Remplace toutes les ressources de la collection |
/todos | PATCH | Met à jour une partie des ressources de la collection |
/todos | DELETE | Supprime la collection |
URI | Verb | Action |
---|---|---|
/todos/13 | GET | Récupère la ressource 13 de la collection Todo |
/todos/13 | POST | Crée une ressource avec l’id 13 dans la collection |
/todos/13 | PUT | Remplace toutes les données de la ressource 13 |
/todos/13 | PATCH | Met à jour une partie des données de la ressource 13 |
/todos/13 | DELETE | Supprime la ressource 13 |
URI | Verb | Action |
---|---|---|
/todos?complete=true | GET | Filtre la collection sur la donnée complete |
/users?role=admin | GET | Filtre la collection sur la donnée role |
$ pdm add spectree
from spectree import SpecTree
= SpecTree("flask", annotations=True)
spec
@app.route("/api/user", methods=["POST"])
@spec.validate(tags=["api"])
def user_profile(json: Profile):
print(json)
return {"text": "it works"}
# attention de bien importer depuis la v1
from pydantic.v1 import BaseModel, Field, constr
class Profile(BaseModel):
=2, max_length=40) # constrained str
name: constr(min_lengthint = Field(gt=0, lt=150) age:
from flask import Flask
from flask_httpauth import HTTPTokenAuth
= Flask(__name__)
app = HTTPTokenAuth(scheme='Bearer')
auth
= {
tokens "secret-token-1": "john",
"secret-token-2": "susan"
}
@auth.verify_token
def verify_token(token):
if token in tokens:
return tokens[token]
@app.route('/')
@auth.login_required
def index():
return "Hello, {}!".format(auth.current_user())
$ curl https://localhost:5000/api/todos
-H "Accept: application/json"
-H "Authorization: Bearer {token}"
= Flask(__name__)
app = SpecTree("flask", annotations=True)
spec spec.register(app)
Puis se rendre sur /apidoc/swagger
.
from spectree import SpecTree, SecurityScheme
= SpecTree(
spec "flask",
=[
security_schemes
SecurityScheme(="bearer_token",
name={"type": "http", "scheme": "bearer"}
data
)
],=[{"bearer_token": []}]
security )
api
;Créer src/toudou/wsgi.py
avec :
from toudou.views import create_app
= create_app() app
$ pdm add gunicorn
$ pdm run gunicorn toudou.wsgi:app
$ pdm build
$ ls dist/
toudou-0.1-py3-none-any.whl toudou-0.1.tar.gz
FROM python:3.10.10-slim-buster
WORKDIR /app
COPY dist/toudou-*-py3-none-any.whl /app/
RUN pip install *.whl gunicorn
CMD ["gunicorn", "toudou.wsgi"]