Некоторое время назад мы научились работать из языка Python с PostgreSQL . Следующим логическим (для меня, во всяком случае) шагом было бы научиться делать REST API , и вперед, можно клепать микросервисы налево и направо! Обычно при изучении очередного микро веб-фреймворка я привожу пример с телефонной книгой. Но он мне уже надоел, так что напишем приложение для хранения тем к выпуску подкаста.
Схема базы данных будет очень простой:
CREATE TABLE themes ( id SERIAL PRIMARY KEY , title TEXT , url TEXT ) ;
Файл requirements.txt (см заметку про virtualenv ) у меня получился таким:
itsdangerous==0.24
Jinja2==2.8
MarkupSafe==0.23
py-postgresql==1.1.0
Werkzeug==0.11.9
Наконец, код самого приложения:
import postgresql
import flask
import json
app = flask. Flask ( __name__ )
# disables JSON pretty-printing in flask.jsonify
# app.config[‘JSONIFY_PRETTYPRINT_REGULAR’] = False
def db_conn ( ) :
return postgresql. open ( ‘pq://eax@localhost/eax’ )
def to_json ( data ) :
return json . dumps ( data ) + » n »
def resp ( code , data ) :
return flask. Response (
status = code ,
mimetype = «application/json» ,
response = to_json ( data )
)
def theme_validate ( ) :
errors = [ ]
json = flask. request . get_json ( )
if json is None :
errors. append (
«No JSON sent. Did you forget to set Content-Type header» +
» to application/json?» )
return ( None , errors )
for field_name in [ ‘title’ , ‘url’ ] :
if type ( json . get ( field_name ) ) is not str :
errors. append (
«Field ‘{}’ is missing or is not a string» . format (
field_name ) )
return ( json , errors )
def affected_num_to_code ( cnt ) :
code = 200
if cnt == 0 :
code = 404
return code
@ app. route ( ‘/’ )
def root ( ) :
return flask. redirect ( ‘/api/1.0/themes’ )
# e.g. failed to parse json
@ app. errorhandler ( 400 )
def page_not_found ( e ) :
return resp ( 400 , { } )
@ app. errorhandler ( 404 )
def page_not_found ( e ) :
return resp ( 400 , { } )
@ app. errorhandler ( 405 )
def page_not_found ( e ) :
return resp ( 405 , { } )
@ app. route ( ‘/api/1.0/themes’ , methods = [ ‘GET’ ] )
def get_themes ( ) :
with db_conn ( ) as db:
tuples = db. query ( «SELECT id, title, url FROM themes» )
themes = [ ]
for ( id , title , url ) in tuples:
themes. append ( { «id» : id , «title» : title , «url» : url } )
return resp ( 200 , { «themes» : themes } )
@ app. route ( ‘/api/1.0/themes’ , methods = [ ‘POST’ ] )
def post_theme ( ) :
( json , errors ) = theme_validate ( )
if errors: # list is not empty
return resp ( 400 , { «errors» : errors } )
with db_conn ( ) as db:
insert = db. prepare (
«INSERT INTO themes (title, url) VALUES ($1, $2) » +
«RETURNING id» )
[ ( theme_id , ) ] = insert ( json [ ‘title’ ] , json [ ‘url’ ] )
return resp ( 200 , { «theme_id» : theme_id } )
@ app. route ( ‘/api/1.0/themes/<int:theme_id>’ , methods = [ ‘PUT’ ] )
def put_theme ( theme_id ) :
( json , errors ) = theme_validate ( )
if errors: # list is not empty
return resp ( 400 , { «errors» : errors } )
with db_conn ( ) as db:
update = db. prepare (
«UPDATE themes SET title = $2, url = $3 WHERE id = $1» )
( _ , cnt ) = update ( theme_id , json [ ‘title’ ] , json [ ‘url’ ] )
return resp ( affected_num_to_code ( cnt ) , { } )
@ app. route ( ‘/api/1.0/themes/<int:theme_id>’ , methods = [ ‘DELETE’ ] )
def delete_theme ( theme_id ) :
with db_conn ( ) as db:
delete = db. prepare ( «DELETE FROM themes WHERE id = $1» )
( _ , cnt ) = delete ( theme_id )
return resp ( affected_num_to_code ( cnt ) , { } )
if __name__ == ‘__main__’ :
app. debug = True # enables auto reload during development
app. run ( )
Как видите, все предельно просто и понятно, ничего лишнего. Вот такой код я называю выразительным, красивым и декларативным. А не тот, что пытаются писать некоторые любители линз и scalaz 🙂
Пример взаимодействия с сервисом:
# {}
curl -XPOST -H ‘Content-Type: application/json’
-d ‘{«title»:»aaa»,»url»:»bbb»}’
‘localhost:5000/api/1.0/themes’
# {«theme_id»: 1}
curl -XGET ‘localhost:5000/api/1.0/themes’
# {«themes»: [{«url»: «bbb», «title»: «aaa», «id»: 1}]}
curl -XPUT -H ‘Content-Type: application/json’
-d ‘{«title»:»ccc»,»url»:»ddd»}’
‘localhost:5000/api/1.0/themes/1’
# {}
curl -XGET ‘localhost:5000/api/1.0/themes’
# {«themes»: [{«url»: «ddd», «title»: «ccc», «id»: 1}]}
curl -XDELETE ‘localhost:5000/api/1.0/themes/1’
# {}
curl -XGET ‘localhost:5000/api/1.0/themes’
# {«themes»: []}
В качестве домашнего задания можете проверить, как приложение обрабатывает ошибки, а также какие коды и заголовки оно возвращает в ответах.
Ссылки по теме:
- http://flask.pocoo.org/ ;
- http://shop.oreilly.com/product/0636920031116.do ;
- https://github.com/afiskon/py-flask-example ;
А с использованием каких фреймворков и/или библиотек вы пишите REST-сервисы? А также, чем парсите конфиги, пишите логи, и как все это хозяйство упаковываете?
Дополнение: Работа с HTML-шаблонами в Python при помощи Jinja