REST in peace

Python

Roadmap

  1. Python
  2. Requests
  3. Django
  4. Django REST Framework
    1. REST et OpenAPI
    2. sécurité des WS

 

ATTENTION!

Ceci n'est qu'un itinéraire prévu...

Python

Le langage

Objectifs

  • Maîtrise suffisante du langage pour les WS
  • éviter les slides sur la syntaxe arithmétique
  • Montrer les qualités du langage
  • donner les sources nécessaires à plus de (auto) formation

Historique

  • Created by Guido Van Rossum in 1990
    • Promoted himself as BDFL : Benevolent Dictator For Life
  • First release of the backward incompatible release 3.0 in 2008
  • Last release of the 2.7 version in 2010

(Mais très court! Promis...)

Version

La version du langage utilisé

(Aujourd'hui, nous utilisons la version 3.10)

Interpréteur

L'implémentation du langage.

Il en existe plusieurs:

  • CPython
  • IronPython
  • PyPy
  • Jython
  • ...

Distribution

Certaines librairies sont difficiles à installer sous Windows.

Une distribution comme Anaconda permet de gagner du temps.

Anatomie

  • Multiparadigme
    • impératif, fonctionnel, orienté objet, orienté aspect
  • Interprété (OS Portable)
  • Typage Dynamique
  • Garbage collector
  • Lisible...

Sinon, c'est mieux perçu par la pratique...

L'interpréteur

  • python.exe
  • utilisation du -m
  • gestion des variables / objets
  • utilisation de "_"
  • pas besoin de "print()"

Types primitifs

  • typage dynamique mais fort!: type()
  • int, float, bool, None
  • str (+raw)
    • print()
    • [] omniprésent!
  • bytes
    • decode()

NB: TOUT est objet en Python

Les listes

  • instanciation
  • indexation
    • slices (get,set), copy
    • négatifs, steps
  • compréhension
  • méthodes
    • append(), sort[ed]()
    • opérateurs
      • in
      • +,*,...
  • idiome: range(len()), enumerate()

Les dictionnaires

  • instanciation
  • indexation
  • compréhension
  • méthodes
    • .items(), .setdefault()
    • opérateurs
      • in, |, ...
  • idiome: dict(zip())

Consonnes / voyelles

Les tuples et les sets

  • instanciation
  • indexation
  • compréhension
  • comparaison avec list et dict...

utilisation de "for" pour tous

Python est basé sur les "protocoles" => duck-typing

Les fonctions

def say_hello():
    """
    documentation optionnelle (donc indispensable)
    appelée 'docstring'.
    """
    return "Hello world!"
  
print(say_hello())

Déclaration

  • snake-case
  • retourne "None" par défaut.
  • "pass"

Scope

(visibilité des variables)

l'appel d'une fonction génère une nouvelle table des symboles contenant:

  • symboles "built-in"
  • variables globales (du module)
  • variables des scopes supérieurs
    • (une fonction peut être définie dans une autre => closure)
  • paramètres de la fonction
  • variables locales

Paramètres

déclarations dans l'ordre:

  • paramètres positionnels obligatoires
  • paramètres positionnels optionnels
    • (avec valeur par défaut)
  • paramètres positionnels additionnels
    • *args
  • paramètres nommés additionnels
    • **kwargs

Lors de l'appel => list+dict unpacking

documentation

Exercice product()

Ecrire une fonction de multiplication vérifiant:

  • multiply([8, 2, 3, -1, 7])  == -336

  • multiply([8, 2, 3, -1, 7]) == multiply(8, 2, 3, -1, 7)

open()

  • encoding
  • str <=> bytes
  • module io
  • utilisation de "with"
private String open(String path) throws IOException {

  InputStream inputStream = null;
  try {
      File file = new File(classLoader.getResource(path).getFile());
      inputStream = new FileInputStream(file);
      BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
      String line;
      while ((line = br.readLine()) != null) {
      	resultStringBuilder.append(line).append("\n");
      }
      return resultStringBuilder.toString();
  }     
  finally {
      if (inputStream != null) {
          try {
              inputStream.close();
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
  }
}

Les lambdas

Très utiles comme paramètres d'autres fonctions:

  • sorted
  • filter
  • map (préférez une compréhension)
  • reduce (désormais dans functools)

 

pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
sorted(pairs, key=lambda pair: pair[1]) 

Les exceptions

  • sont des objets...
  • try/except/finally
  • with
    • (context managers)
  • "gérées le plus tard possible."

Les modules

  • "batteries included"
    • math, os,  sys, logging, argparse, ...
  • package manager (pip)
  • import, from, as
  • packages
    • __init__.py
  • "__main__" + sys.argv
  • sont des objets (attributs dynamiques).
    • mais le "monkey patching" est un grand pouvoir qui implique...

Exercice "prime"

Ecrire un module de manipulation des nb premiers:

  • chargement d'une liste de nombres premiers (depuis un fichier)
  • écriture d'un fichier au même format (csv ou autre)
  • recherche de nombres premiers depuis le dernier de la liste

POO

(mais pas trop)

Les classes

class Bird:
  
    def __init__(self, mood="Happy", size=0):
        self.mood = mood
        self.size = size
        
    def fly(self):
        self.mood = "Happy"
        print("Look at me! I'm flying!")
        
    def kick(self):
        self.mood = "Angry"

Les définitions de classes sont exécutées pour produire une classe

(qui est un objet...)

Les classes

woody = Bird(mood="neutral")
type(woody) == Bird # True

Les classes sont appelées pour créer des instances.

Ca ressemble pas mal à des fonctions... 

Héritage

class Duck(Bird):
  
    def quack(self):
        print "Quack, quack!"
        
    def kick(self):
        self.quack()
        super(Duck, self).kick()
        
daffy = Duck()
type(daffy) == Duck

# Polymorphism
isinstance(daffy, Bird) == True
daffy.fly()

Héritage

# Let's adopt another duck:
donald = Duck(size=10)

# How is it linked to daffy?
donald.nephews = ["Huey", "Dewey", "Louie"]
daffy.nephews #NameError
donald.__class__.face = "Duck-billed"
donald.face # "Duck-billed"
daffy.face  # "Duck-billed"!!!

# How is it linked to woody?
woody.face # NameError
woody.__class__.personality = "funny"
woody.personality  # "funny"
donald.personality # "funny"

# What happens if I do:
daffy.personality = "evil" #???

Duck-typing

class Goose(Bird): # A Goose is NOT a Duck
  
    def quack(self):
        print("Quack like a Goose")

def make_it_quack(o):
    o.quack()

sauvage = Goose()
make_it_quack(sauvage)
make_it_quack(donald) #It works!
  • Duck-typing := pas de vérification de type
  • => définition de "protocoles" plutôt que d'interfaces

Méthodes magiques

des protocoles standards,  définis par le langage.

Exercice magique

  • Choisissez un thème:
    • fruits, formes géométriques, véhicules…
  • définir ce que signifie d'itérer, d'ajouter, etc... des objets de ce type

c'est magique aussi parce que ça marche avec tout…

Et la performance!

On en parle?

Ce que nous ne verrons pas cette fois...

  • générateurs
  • décorateurs
  • context managers
  • meta classes
  • coroutines
  • typage statique
  • descripteurs
  • toute la lib standard

concepts

  • Dataclasses
  • async def, with, for + await
    • __await__ -> awaitable (returns an iterable)
    • async def -> coroutine (are awaitable!)
    • __aiter__ -> async iterable (return async iterator)
    • __anext__ -> async iterator (returns awaitable)
    • __aenter/exit__-> async ctxt mng (return awaitables)
  • positional , / , pos-or-kw , * , kw
  • single dispatch
  • slice: [:] -> slice obj in __getitem__
  • type hints
    • Generic[], TypeVar()

Requests

(HTTP for humans)

#pip install requests
response = requests.get(
  "http://www.google.fr", 
  proxies={"http": "http://192.168.3.3:8080"},
)

# OU (avec cookies jar)
session = requests.Session()
session.proxies={"http": "http://192.168.3.3:8080"}
response = session.get("http://www.google.fr")

#pip install beautifulsoup4
from bs4 import BeautifulSoup
soup = BeautifulSoup(response.content)
soup.find_all("a")

Django

Un framework pour les gouverner tous

Les concepts

Possède des modules pour gérer:

  • projets / applications
  • vues / URLs
  • modèles / migrations
  • administration (backoffice)
  • tests

 

  • templates
  • formulaires
  • ...

+ Sécurité par défaut

Installation

# creation environement virtuel
python -m venv .env
. .env/Scripts/activate

# installation
pip install Django

# Vérification
python -m django --version

Vues

# création projet
django-admin startproject demodj .

# création app
python manage.py startapp demo
from django.http import HttpResponse


def index(request):
    return HttpResponse("Hello, world!")
from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
]

# add to server's urls.py:
# path('demo/', include('demo.urls')),

Modèles, ORM, Migrations

python manage.py startapp drvapp
# register in settings.py

# 'default': {
#     'ENGINE': 'django.db.backends.mysql',
#     'NAME': 'DRVAPP_CENTRAL_DATAS',
#     'USER': 'inspect',
#     'PASSWORD': '1nspect',
#     'HOST': '10.34.2.8',
#     'PORT': '3306',
# },

python manage.py inspectdb > drvapp/models.py
# edit generated models.py
python -Xutf8 manage.py dumpdata drvapp -o data.json

# back to default db
python manage.py makemigrations
python manage.py migrate
python manage.py loaddata data.json

python manage.py shell

Admin (backoffice)

from .models import Tournee, Point, Colis

admin.site.register(Tournee)
admin.site.register(Point)
admin.site.register(Colis)

# Et beaucoup plus:
# https://docs.djangoproject.com/en/4.0/intro/tutorial07/
python manage.py createsuperuser

Vues (classes)

from django.views import generic

class IndexView(generic.ListView):
    template_name = 'wsdrvapp/index.html'

    def get_queryset(self):
        return Tournee.objects.order_by('-damj')[:5]
      
class DetailView(generic.DetailView):
    model = Tournee
    template_name = 'wsdrvapp/detail.html'
app_name = "wsdrvapp"
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
]

Django REST Framework

REST

(Representational State Transfer)

Contraintes

  • client/serveur
  • sans état
    • on transfère une représentation de la ressource à un instant donné
  • identification des ressources
  • HATEOAS (navigation)

Avantages

  • découplage
  • réactivité
  • standardisation
  • accessibilité (HTTP)
  • scalabilité horizontale

Les concepts DRF

Possède des modules pour gérer:

  • parsing / rendering
  • serializers / validators
  • viewsets
  • routeurs

 

  • authentication
  • permissions
  • pagination
  • filtering
  • throttling...

Installation

# Dans un projet django

pip install djangorestframework
pip install markdown       # Markdown support for the browsable API.
pip install django-filter  # Filtering support
# 'rest_framework' dans INSTALLED_APPS
# settings specifiques dans REST_FRAMEWORK = {}


# path('api-auth/', include('rest_framework.urls'))

Serializers

from rest_framework import serializers
from .models import Tournee

class TourneeSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Tournee
        fields = '__all__'
from wsdrvapp.models import Tournee
from wsdrvapp.serializers import TourneeSerializer
from pprint import pprint

t = Tournee.objects.first()
s = TourneeSerializer(t, context={'request':None})
pprint(s.data)

Viewsets

from rest_framework import viewsets
from .models import Point, Tournee
from .serializers import TourneeSerializer


class TourneeViewSet(viewsets.ModelViewSet):
    queryset = Tournee.objects.all()
    serializer_class = TourneeSerializer

Router

from wsdrvapp.views import TourneeViewSet
from rest_framework import routers

router = routers.DefaultRouter()
router.register('tournee', TourneeViewSet)

urlpatterns = [
    # ...
    path('api/', include(router.urls)),
]

Permissions

(authentication)

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
    ],
}
from rest_framework import schemas

urlpatterns = [
    # ...
    path('api-auth/', include('rest_framework.urls')),
]

Pagination

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 5,
}

Filtering (1/2)

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': (
        'django_filters.rest_framework.DjangoFilterBackend',
        'rest_framework.filters.SearchFilter',
        'rest_framework.filters.OrderingFilter',
    ),
}
class TourneeViewSet(viewsets.ModelViewSet):
    queryset = Tournee.objects.all()
    serializer_class = TourneeSerializer

    filterset_fields = ('num', 'bl')
    search_fields = ['num', 'date', 'bl', 'loccode']

Filtering (2/2)

class PointViewSet(viewsets.ModelViewSet):
    queryset = Point.objects.all()
    serializer_class = PointSerializer

    filterset_class = PointFilter
    search_fields = ['cp', 'tournee__num']
from django_filters import rest_framework as filters
from .models import Point

class PointFilter(filters.FilterSet):
    min_lat = filters.NumberFilter(field_name="lat", lookup_expr='gte')
    max_lat = filters.NumberFilter(field_name="lat", lookup_expr='lte')
    min_lon = filters.NumberFilter(field_name="lon", lookup_expr='gte')
    max_lon = filters.NumberFilter(field_name="lon", lookup_expr='lte')

    class Meta:
        model = Point
        fields = ['cp']

OpenAPI / Schemas

from rest_framework import schemas

urlpatterns = [
    # ...
    path('openapi', schemas.get_schema_view(
        title="WSDriverApp",
        description="API for all things …",
        version="1.0.0"
    ), name='openapi-schema'),
]

Swagger UI

from django.views.generic import TemplateView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api-auth/', include('rest_framework.urls')),
    path('api/', include(router.urls)),
    
    path('openapi', schemas.get_schema_view(
        title="WSDriverApp",
        description="API for all things …",
        version="1.0.0"
    ), name='openapi-schema'),
    
    path('swagger-ui', TemplateView.as_view(
        template_name='wsdrvapp/swagger-ui.html',
        extra_context={'schema_url':'openapi-schema'}
    ), name='swagger-ui'),
]

+ un template

Throttling

REST_FRAMEWORK = {

    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle'
    ],
    
    'DEFAULT_THROTTLE_RATES': {
        'anon': '5/min',
        'user': '1000/day'
    }
}

Parsing / Rendering

REST_FRAMEWORK = {
    
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser',
        'rest_framework_yaml.parsers.YAMLParser',
    ],
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
        'rest_framework_yaml.renderers.YAMLRenderer',
    ],
}
pip install djangorestframework-yaml

Merci ^^

Merci ^^

Python

By Olivier DUPOUY

Python

a generic but complete introduction to python programming

  • 74