Récemment, j'ai eu l'occasion d'étudier le développement de microservices (appelés API REST) en utilisant Lightweight Language (LL). Il existe différents langages tels que Ruby, Go, Python, etc. même s'il s'appelle LL, mais comme de nombreuses connaissances utilisent Python dans le domaine de l'apprentissage automatique et de l'IoT, cette fois sur la façon d'implémenter des microservices à l'aide de Python Je voudrais le garder en mémoire. C'est la première fois que je lis Python, j'espère donc que vous le lirez dans cet esprit.
L'environnement utilisé dans cette vérification est le suivant. Je n'ai pas essayé l'environnement virtuel Python cette fois. (Car on dit que ça ne marche pas sous Windows)
Cette fois, il s'agit d'une bibliothèque utilisée pour créer un service de macro en Python. Toutes sont de simples bibliothèques faciles à utiliser et qui peuvent toutes être installées avec "pip install".
Numéro d'article | Bibliothèque | Aperçu | site |
---|---|---|---|
1 | flask | Cadre mvc léger | http://flask.pocoo.org/ |
2 | peewee | O/Cartographie R | http://docs.peewee-orm.com/en/latest/ |
3 | psycopg2 | Requis pour l'accès PostgreSQL | https://pypi.python.org/pypi/psycopg2 |
4 | cerberus | Bibliothèque de contrôle d'entrée | http://docs.python-cerberus.org/en/stable/ |
5 | requests | Client HTTP | http://docs.python-requests.org/en/master/ |
Je l'ai installé selon l'installateur.
La destination d'installation est la valeur par défaut C: \ Python27
.
Les deux emplacements suivants ont été ajoutés à la variable d'environnement path
.
(Pour référence, le délimiteur path
dans Windows est;
)
Si Proxy est requis pour l'accès Internet en raison de la configuration réseau de l'organisation, définissez la variable d'environnement pour dépasser Proxy.
Il peut s'agir d'une variable d'environnement utilisateur ou d'une variable d'environnement système.
Si vous souhaitez le définir uniquement lors de l'installation de pip
, nous vous recommandons de le définir temporairement avec set
.
set HTTPS_PROXY=userid:[email protected]:8080
set HTTP_PROXY=userid:[email protected]:8080
Faites correspondre l'hôte proxy, le numéro de port, l'ID utilisateur et le mot de passe à votre environnement. Si Proxy ne nécessite pas d'authentification de l'utilisateur, il n'a pas besoin de précéder «@».
Il peut y avoir des URL que vous ne souhaitez pas utiliser Proxy, par exemple lorsque vous souhaitez accéder à un serveur sur un réseau privé.
Dans ce cas, utilisez la variable d'environnement NO_PROXY
.
set NO_PROXY=127.0.0.1;localhost;apserver
Lorsque vous essayez d'accéder à un serveur préparé par vous-même à l'aide de requests
Pour une raison quelconque, je ne pouvais pas y accéder via Proxy, j'ai donc décidé de définir la variable d'environnement NO_PROXY
.
Encore une fois, je n'ai pas essayé l'environnement virtuel Python cette fois.
Alors je l'ai installé directement avec pip
.
pip install peewee
pip install psycopg2
pip install requests
pip install cerberus
pip install flask
Implémentons une application qui introduit l'utilisation minimale des bibliothèques (flask
, cerberus
, peewee
) introduites cette fois.
Cette fois, nous ne considérerons pas la division des fonctions ou la division des fichiers selon la responsabilité. Pour une meilleure visibilité, nous utiliserons un code source pour l'application Web du microservice et un code source pour le client qui utilise le microservice.
Il s'agit d'une application de microservice (ou API REST) qui possède une API qui ajoute uniquement des enregistrements à la table question
ci-dessous.
create_table.sql
DROP TABLE IF EXISTS question;
CREATE TABLE question (
question_code VARCHAR(10) NOT NULL,
category VARCHAR(10) NOT NULL,
message VARCHAR(100) NOT NULL,
CONSTRAINT question_pk PRIMARY KEY(question_code)
);
Les spécifications et restrictions de l'application sont les suivantes. Il semble que les spécifications aient des objectifs mixtes, mais voici les points de mise en œuvre.
/ questions
avec POST
, le contenu est enregistré dans la base de données.JSON
. (Ne cochez pas cette fois comme condition préalable)demoapp.py
# -*- coding: utf-8 -*-
import os
from flask import Flask, abort, request, make_response, jsonify
import peewee
from playhouse.pool import PooledPostgresqlExtDatabase
import cerberus
# peewee
db = PooledPostgresqlExtDatabase(
database = os.getenv("APP_DB_DATABASE", "demodb"),
host = os.getenv("APP_DB_HOST", "localhost"),
port = os.getenv("APP_DB_PORT", 5432),
user = os.getenv("APP_DB_USER", "postgres"),
password = os.getenv("APP_DB_PASSWORD", "postgres"),
max_connections = os.getenv("APP_DB_CONNECTIONS", 4),
stale_timeout = os.getenv("APP_DB_STALE_TIMEOUT", 300),
register_hstore = False)
class BaseModel(peewee.Model):
class Meta:
database = db
# model
class Question(BaseModel):
question_code = peewee.CharField(primary_key=True)
category = peewee.CharField()
message = peewee.CharField()
# validation schema for cerberus
question_schema = {
'question_code' : {
'type' : 'string',
'required' : True,
'empty' : False,
'maxlength' : 10,
'regex' : '^[0-9]+$'
},
'category' : {
'type' : 'string',
'required' : True,
'empty' : False,
'maxlength' : 10
},
'message' : {
'type' : 'string',
'required' : True,
'empty' : False,
'maxlength' : 100
}
}
# flask
app = Flask(__name__)
# rest api
@app.route('/questions', methods=['POST'])
def register_question():
#Vérifiez l'entrée
v = cerberus.Validator(question_schema)
v.allow_unknown = True
validate_pass = v.validate(request.json)
if not validate_pass:
abort(404)
#Appeler la logique métier
result = register(request.json)
#Retour du résultat du traitement (format JSON)
return make_response(jsonify(result))
# error handling
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'error' : 'Not Found'}), 404)
@app.errorhandler(500)
def server_error(error):
return make_response(jsonify({'error' : 'ERROR'}), 500)
#Logique métier
@db.atomic()
def register(input):
# create instance
question = Question()
question.question_code = input.get("question_code")
question.category = input.get("category")
question.message = input.get("message")
# insert record using peewee api
question.save(force_insert=True)
result = {
'result' : True,
'content' : question.__dict__['_data']
}
return result
# main
if __name__ == "__main__":
app.run(host=os.getenv("APP_ADDRESS", 'localhost'), \
port=os.getenv("APP_PORT", 3000))
Avec peewee, vous pouvez accéder à des bases de données telles que SQL Lite, MySQL et PostgreSQL. Voir http://docs.peewee-orm.com/en/latest/peewee/database.html pour plus d'informations.
Puisque les micro-services sont bien sûr des applications web, je pense que la fonction de pool de connexion sera essentielle.
Bien sûr, peewee prend également en charge les pools de connexions.
Les paramètres sont différents pour chaque base de données, et dans le cas de PostgreSQL, utilisez la classe PooledPostgresqlExtDatabase
.
Lors de l'utilisation de la classe PooledPostgresqlExtDatabase
, une erreur se produira lors de l'exécution si psycopg2
n'est pas installé.
Bien que cela n'ait rien à voir avec la fonction peewee, il est recommandé que les informations de connexion à la base de données (hôte, numéro de port, ID utilisateur, mot de passe, etc.) soient définies de l'extérieur du programme (variables d'environnement) car elles dépendent de l'environnement.
Lors de l'utilisation de PostgreSQL avec peewee, on suppose que la fonction appelée HSTORE de PostgreSQL est utilisée par défaut. Par conséquent, si HSTORE n'est pas activé dans la base de données à utiliser, une erreur se produira dans l'accès à la base de données de peewee.
En guise de contre-mesure, activez HSTORE avec CREATE EXTENSION hstore;
dans la base de données,
Vous devrez également définir register_hstore = False
pour empêcher peewee d'utiliser HSTORE.
Afin d'utiliser la fonction de mappage O / R de peewee, définissez une classe qui hérite du Model
de peewee.
Cette fois, il s'agit d'une classe appelée BaseModel, mais cette classe doit définir la classe Meta
en interne et définir l'objet de définition de base de données mentionné ci-dessus dans le champ database
.
La fonction de mappage O / R de peewee associe des tables à des classes et des colonnes à des champs. Ensuite, l'objet modèle de peewee est mappé à un enregistrement de la table.
Le modèle peewee est défini comme une classe qui hérite du BaseModel
mentionné ci-dessus.
Le champ définit le type de champ peewee en fonction du type de données de la table.
Voir http://docs.peewee-orm.com/en/latest/peewee/models.html#fields pour plus d'informations.
Définissez primary_key = True
dans le champ de clé primaire.
Si vous ne définissez pas primary_key
, peewee part du principe qu'il existe une colonne de clé primaire appelée id. Par conséquent, bien sûr, si l'ID de colonne n'existe pas dans la table, une erreur se produira.
Si le nom du champ et le nom de la colonne sont différents, définissez explicitement la colonne avec db_column = column name
.
Pour créer une application flask, créez d'abord une instance de flask avec __name__
comme argument.
Ensuite, utilisez le décorateur de fonction route ()
pour associer la requête HTTP à la fonction en définissant le mappage de requête par flask dans la fonction définie.
Pour ceux qui ont de l'expérience avec Spring Framework (Spring MVC) en Java, il peut être plus facile de penser à l'annotation @ RequestMapping
.
(Flask n'a pas de fonction de mappage dans les paramètres de requête ou les en-têtes)
Chemin de la demande
Le premier argument de route ()
est le chemin de la requête.
Contrairement aux applications Web JavaEE, il n'y a pas de chemin de contexte, il est reconnu comme le chemin immédiatement après le numéro de port dans l'URL. (Habituellement, le port HTTP bien connu 80 n'est pas spécifié)
Demande de chemin pour les variables intégrées
En tant que forme spéciale de chemin de requête, il existe une méthode de mappage qui peut recevoir une partie du chemin de requête en tant que variable.
Par exemple, lorsque vous accédez à / questions / 0000000001
ou / questions / 0000000999
avec GET, si vous souhaitez recevoir une valeur telle que 0000000001 ou 0000000999 comme argument d'une variable de chemin et mapper la demande, vous pouvez la définir comme suit. Je peux le faire.
@app.route('/questions/<string:question_code>', methods=['GET'])
def reference_question(question_code):
methods = ['GET', 'POST']
, il est possible de spécifier plusieurs méthodes.Voir http://flask.pocoo.org/docs/0.12/quickstart/#routing pour plus d'informations sur le mappage des demandes.
falsk fournit un décorateur de fonction ʻerrorhandler () `pour définir la gestion des erreurs. Définissez le code d'état HTTP à gérer dans l'argument. L'application de démonstration gère désormais à la fois 404 et 500.
Dans cerberus, les règles de vérification des entrées sont appelées schémas. (Peut-être que cela détermine la structure des données, donc je pense que cela s'appelle un schéma comme DB)
Le schéma est défini par le type de dictionnaire Python. Je ne connaissais pas le type de dictionnaire Python, donc quand je l'ai vu pour la première fois, j'ai pensé que je l'écrirais en JSON.
Décrivez le type de données («type»), requis / non requis («requis») et les règles de vérification d'entrée à appliquer («maxlength», «regex», etc.) pour chaque champ. Je pense que c'est assez simple pour avoir du sens en un coup d'œil.
Voir http://docs.python-cerberus.org/en/stable/validation-rules.html pour les règles de vérification d'entrée fournies par défaut.
La vérification d'entrée par cerberus crée d'abord une instance de Validator selon le schéma, et utilise cette instance pour effectuer la vérification d'entrée. (Il existe également une méthode à utiliser sans créer d'instance pour chaque schéma, mais je vais l'omettre cette fois)
Dans l'application de démonstration, nous avons défini un schéma appelé question_schema
, nous avons donc créé une instance de Validator avec ceci comme argument.
L'exécution de la vérification d'entrée est facile, et la méthode validate
est exécutée avec les données de type dictionnaire qui sont la cible de la vérification d'entrée comme argument.
En tant que valeur de retour, «True» est retourné s'il n'y a pas d'erreur dans la vérification d'entrée, et «False» est retourné s'il y a une erreur.
Lors de l'utilisation en coopération avec flask, les données de la requête sont accessibles au format dictionnaire (format JSON) avec la propriété request.json
, définissez-la comme argument de la méthode validate
.
Puisqu'il s'agit d'une application REST, elle ne sera pas utilisée cette fois, mais les données du formulaire d'entrée sont accessibles avec request.form
.
validate_pass = v.validate(request.json)
validate_pass = v.validate(request.form)
Si vous voulez juste vérifier l'instance indépendamment de flask, utilisez __dict__
car la méthode validate
ne peut prendre que des arguments de type dictionnaire.
question_ng = Question()
question_ng.question_code = "abc0123456789xyz"
question_ng.category = None
question_ng.message = ""
validate_pass = v.validate(question_ng.__dict__)
Par défaut, cerberus entraînera une erreur de vérification d'entrée si les données d'entrée contiennent des champs qui ne sont pas définis dans le schéma. Pour changer ce comportement, définissez la propriété ʻallow_unknown de l'instance du validateur sur
True. (ʻAllow_unknown = True
)
Comme vous pouvez le voir dans «À propos des détails des erreurs de vérification d'entrée» ci-dessous, ** les instances de cerberus Validator sont des instances avec état qui conservent leur état en interne. Pas apatride. ** **
C'est pourquoi l'application de démonstration crée une instance Validator pour chaque demande. Il faut envisager de rationaliser (réduire) le processus d'instanciation et le validateur thread-safe.
Dans cerberus, le contenu d'erreur de l'erreur de vérification d'entrée est stocké dans la propriété ʻerrors` de l'instance de Validator. Il est conservé dans les propriétés de l'instance, donc bien sûr, il sera mis à jour à chaque fois que vous le vérifiez. Vous savez maintenant que vous ne pouvez pas partager une instance de Validator avec plusieurs threads (ce n'est pas thread-safe).
if not validate_pass:
print v.errors
abort(404)
Afin de vérifier le contenu de la propriété des erreurs, essayez de vérifier les données d'entrée qui provoquent l'erreur suivante.
Données d'entrée qui provoquent une erreur
question = {
'question_code' : '99999999990000000',
'category' : '',
'message' : 'hello'
}
Le contenu des erreurs est un type de dictionnaire dans lequel le champ qui a causé l'erreur est la clé et le tableau des messages d'erreur est la valeur. Par conséquent, si plusieurs erreurs de vérification d'entrée sont appliquées à un champ, plusieurs messages d'erreur seront stockés.
V affiché.Contenu des erreurs
{u'category': ['empty values not allowed'], u'question_code': ['max length is 10']}
peewee fournit un décorateur de fonctions pour le contrôle des transactions.
Vous pouvez définir des limites de transaction simplement en ajoutant un décorateur ʻatomic () à la fonction. Cela ressemble à une transaction déclarative utilisant
@ Transactional` de Spring Framework, donc je pense que cela sera familier à ceux qui ont utilisé Spring.
(Le mécanisme est différent car il s'agit de la spécification du langage de développement et de l'AOP)
Bien sûr, vous pouvez également imbriquer les transactions et contrôler explicitement le début, la restauration et la validation. Voir http://docs.peewee-orm.com/en/latest/peewee/transactions.html pour plus d'informations sur le contrôle des transactions.
Il y a deux façons d'insérer dans peewee. Par conséquent, lors du développement avec plusieurs membres, il semble nécessaire d'organiser les méthodes.
Utilisez la méthode create ()
de la classe model pour enregistrer des enregistrements et créer des instances en même temps.
Définissez les données requises pour l'insertion (toutes les données de la colonne Not Null) comme argument de la méthode create
.
question = Question.create(question_code = input.get("question_code"), \
category = input.get("category"), \
message = input.get("message"))
L'application de démonstration n'utilise pas cette méthode car elle ne semble pas correspondre à la fonction de mappage O / R des instances et des enregistrements de mappage.
Voir http://docs.peewee-orm.com/en/latest/peewee/api.html#Model.create pour plus d'informations.
Après avoir créé l'instance que vous souhaitez enregistrer, enregistrez l'enregistrement en exécutant la méthode save ()
de l'instance à tout moment.
À ce stade, le point est de définir «True» sur la propriété «force_insert» de l'argument. (Force_insert = True
)
À l'origine, la méthode save ()
est pour la mise à jour (émission de mise à jour), mais en définissant force_insert = True
, l'insertion sera émise.
Voir http://docs.peewee-orm.com/en/latest/peewee/api.html#Model.save pour plus d'informations.
democlient.py
# -*- coding: utf-8 -*-
import requests
# if proxy pass error occurred, try "set NO_PROXY=127.0.0.1;localhost" at console
def register_question():
print("[POST] /questions")
question = {
'question_code' : '9999999999',
'category' : 'demo',
'message' : 'hello'
}
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
response = requests.post('http://localhost:3000/questions',
json=question, headers=headers)
print(response.status_code)
print(response.content)
# main
if __name__ == "__main__":
register_question()
Les requêtes ont des méthodes qui correspondent aux méthodes HTTP telles que «post» et «get». Cette fois, j'ai utilisé «post» pour envoyer la demande par POST.
Lors de l'envoi de données au format json
, définissez les données dans l'argument json
comme json = question
.
De même, si vous souhaitez spécifier l'en-tête HTTP au moment de la requête, définissez-le dans l'argument headers
.
En fait, lorsque l'argument json
est utilisé, ʻapplication / json est automatiquement défini dans
Content-type`.
Vous n'avez donc pas vraiment besoin de le définir explicitement comme le code source de la démo.
(Je voulais savoir comment définir l'en-tête HTTP, je l'ai donc défini cette fois)
C:\tmp>python demoapp.py
* Running on http://localhost:3000/ (Press CTRL+C to quit)
Lors de la mise à l'échelle, plusieurs processus peuvent s'exécuter sur le même ordinateur. A ce moment, il est nécessaire de changer pour que le port d'écoute ne soit pas couvert. L'application de démonstration vous permet d'écraser le port d'écoute à partir d'une variable d'environnement, afin que vous puissiez le modifier au démarrage.
C:\tmp>set APP_PORT=3001
C:\tmp>python demoapp.py
* Running on http://localhost:3001/ (Press CTRL+C to quit)
Exécutez le client de démonstration sans les données pertinentes dans la base de données. Je pense qu'il sera exécuté normalement et que les données enregistrées seront renvoyées au format JSON.
C:\tmp>python democlient.py
[POST] /questions
200
{
"content": {
"category": "demo",
"message": "hello",
"question_code": "9999999999"
},
"result": true
}
Ensuite, essayez à nouveau d'exécuter le client de démonstration dans cet état. Bien sûr, vous obtiendrez une erreur car vous serez pris dans une contrainte unique. Si l'état HTTP de «{'error': 'ERROR'} 'est renvoyé, la gestion des erreurs définie fonctionne également.
C:\tmp>python democlient.py
[POST] /questions
500
{
"error": "ERROR"
}
Enfin, supprimez les données pertinentes de la base de données et réessayez. Cela devrait fonctionner correctement car l'erreur de contrainte unique ne se produit plus.
C:\tmp>python democlient.py
[POST] /questions
200
{
"content": {
"category": "demo",
"message": "hello",
"question_code": "9999999999"
},
"result": true
}
J'ai expliqué comment créer un micro service avec Python en utilisant flask, cerberus et peewee. Je pense que j'ai été en mesure de présenter à quel point il est facile d'implémenter les fonctions de gestion des requêtes HTTP, de vérification des entrées et d'accès à la base de données qui sont le minimum requis pour une application Web en utilisant la bibliothèque introduite cette fois.
Comme je l'ai expliqué au début de l'application concept, nous ne considérons pas la division des fonctions et la division des fichiers en fonction des responsabilités qui sont importantes dans le développement réel du système. En outre, il est nécessaire de considérer séparément la certification, l'autorisation, le contrôle de flux, la surveillance, l'acquisition de journaux, etc., qui sont essentiels pour divulguer les micro-services à l'extérieur.
Fiche technique de notation Markdown Note d'écriture de Markdown Implémentation rapide de l'API REST en Python Essayez de travailler avec la base de données en utilisant Peewee de l'ORM de Python
Recommended Posts