Architecture Logicielle

Michel VEDRINE

Site web

https://kathode.neocities.org

Objectifs

Un séance c’est …

Généralités

Architecture n-tier

Cas d’une bibliothèque

L’application

Gestion de budget

Planning

  1. Interface en ligne de commande
  2. Séance libre
    • template de remise à niveau
  3. Gestion des données
  4. Interface web
    • rendu intermédiaire
  1. Industrialisation
  2. Validation & Traçabilité
  3. Authentification & Autorisation
  4. API HTTP
    • rendu final

Technologies

Outils nécessaires

Installation

https://docs.astral.sh/uv/#installation

Utilisation

Attention à toujours se positionner au même niveau que le pyproject.toml avant de lancer une commande uv :

$ uv sync           # pour installer les dépendances
$ uv run archilog   # lancement de l'application

Plus d’info sur https://docs.astral.sh/uv/.

État d’esprit

Interface en ligne de commande

Click

Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary.

Déclaration

import click

@click.command()
@click.option("--count", default=1)
@click.option("--name", prompt="Your name")
def hello(count, name):
    print(name, count)

utilisation

$ uv run archilog hello --count=3
Your name: John
John 3

SQLite

SQLite is a C library that provides a lightweight disk-based database that doesn’t require a separate server process.

DB-API

Utilisation

import sqlite3

db = sqlite3.connect("tutorial.db")
db.execute("CREATE TABLE movie(title, year, score)")

result = db.execute("SELECT title FROM movie").fetchone()

Documentation

Objectifs

Gestion des données

Modèle actuel

Modèle cible

SQLAlchemy

SQLAlchemy est un toolkit open source SQL et un mapping objet-relationnel (ORM) écrit en Python et publié sous licence MIT.

Architecture de SQLAlchemy

Configuration

engine = create_engine("sqlite:///data.db", echo=True)
metadata = MetaData()

users_table = Table(
    "users",
    metadata,
    Column("id", Uuid, primary_key=True, default=uuid.uuid4),
    Column("login", String, nullable=False)
)

Utilisation

stmt = users_table.insert().values(login="john.doe")

with engine.begin() as conn:
    result = conn.execute(stmt)

Installation

$ uv add sqlalchemy

Documentation

Objectifs

Interface web

Architecture cible

Flask

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.

Application simple

# inside archilog/views.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

Lancement

$ uv run flask --app archilog.views --debug run
 * Serving Flask app 'archilog.views'
 * Debug mode: on
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit

Template HTML

from flask import render_template

@app.route("/hello/")
@app.route("/hello/<name>")
def hello(name=None):
    return render_template("hello.html", name=name)

Template HTML avec Flask & Jinja2

<!doctype html>
<title>Hello from Flask</title>
{% if name %}
  <h1>Hello {{ name }}!</h1>
{% else %}
  <h1>Hello, World!</h1>
{% endif %}

Arborescence des fichiers

.
├── pyproject.toml
├── README.md
└── src
    └── archilog
        ├── __init__.py
        ├── models.py
        ├── services.py
        ├── templates
        │   └── home.html
        └── views.py

Installation

$ uv add flask

Documentation

Objectifs

Industrialisation

Routeurs

Blueprints Flask

from flask import Blueprint, render_template

web_ui = Blueprint("web_ui", __name__)

@web_ui.route("/<page>")
def show(page):
    return render_template(f"pages/{page}.html")

Application factory

def create_app():
    app = Flask(__name__)

    from archilog.views import api, web_ui
    app.register_blueprint(web_ui, url_prefix="/")
    app.register_blueprint(api, url_prefix="/api")

    return app

Lancement

Rien ne change, Flask cherche une fonction create_app :

$ uv run flask --app archilog.views --debug run

Configuration centralisée : avant

engine = create_engine("sqlite:///data.db", echo=True)
metadata = MetaData()

Configuration centralisée : après

from archilog import config

engine = create_engine(config.DATABASE_URL, echo=config.DEBUG)
metadata = MetaData()

Configuration centralisée : déclaration

from dataclasses import dataclass

@dataclass
class Config:
    DATABASE_URL: str
    DEBUG: bool

config = Config(
    DATABASE_URL="sqlite:///data.db",
    DEBUG=True
)

Format dotenv

Collection de fichiers contenant la configuration pour chaque environnement :

dotenv : format

Dans le fichier dev.env :

ARCHILOG_DATABASE_URL=sqlite:///data.db
ARCHILOG_DEBUG=True
ARCHILOG_FLASK_SECRET_KEY=secret!

dotenv : utilisation manuelle

$ env $(cat dev.env | xargs) uv run flask --app ...

dotenv : utilisation avec uv & pyproject-runner

Dans le fichier pyproject.toml :

[tool.pyproject-runner.tasks]
start = {
    cmd = "flask --app archilog.views --debug run",
    env-file = "!/dev.env"
}

dotenv : utilisation dans l’application

import os

[...]

config = Config(
    DATABASE_URL=os.getenv(
        "ARCHILOG_DATABASE_URL", "sqlite:///data.db"
    ),
    DEBUG=os.getenv("ARCHILOG_DEBUG", "False") == "True"
)

dotenv : configurer Flask

app.config.from_prefixed_env(prefix="ARCHILOG_FLASK")

Installation

$ uv add --dev pyproject-runner

Documentation

Objectifs