Usage¶
To use Connexion Sqlalchemy Utils in a project:
import connexion_sql_utils
This package is meant to be used in conjunction with Connexion
which
utilizes an API first approach to building REST API’s for micro-services.
See also
This package has an Sqlalchemy Mixin used with Postgresql to create a declarative base, which can then be used to declare your database models.
This package also has a set of utility functions that when combined with
functools.partial
can be used to quickly create the routes for the
api.
Connexion Sqlalchemy Utils Example¶
By cloning the repo:
git clone https://github.com/m-housh/connexion_sql_utils.git
cd ./connexion_sql_utils
docker-compose up
Without cloning the repo:
docker pull mhoush/connexion_sql_utils
docker pull postgres/alpine
docker run -d --name some_postgres \
-e POSTGRES_PASSWORD=postgres \
postgres:alpine
docker run --rm -it --link some_postgres:postgres \
-e DB_HOST=postgres \
-e DB_PASSWORD=postgres \
-p "8080:8080" \
mhoush/connexion_sql_utils
Check out the example api at http://localhost:8080/ui
app.py:
#!/usr/bin/env python
import os
from functools import partial
import logging
import connexion
from sqlalchemy import Column, String, Numeric, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
from connexion_sql_utils import BaseMixin, to_json, event_func, dump_method
from connexion_sql_utils import crud
# Most of this would typically be in a different module, but since
# this is just an example, I'm sticking it all into this module.
DB_USER = os.environ.get('DB_USER', 'postgres')
DB_PASSWORD = os.environ.get('DB_PASSWORD', 'postgres')
DB_HOST = os.environ.get('DB_HOST', 'localhost')
DB_PORT = os.environ.get('DB_PORT', 5432)
DB_NAME = os.environ.get('DB_NAME', 'postgres')
DB_URI = 'postgres+psycopg2://{user}:{password}@{host}:{port}/{db}'.format(
user=DB_USER,
password=DB_PASSWORD,
host=DB_HOST,
port=DB_PORT,
db=DB_NAME
)
engine = create_engine(DB_URI)
Session = scoped_session(
sessionmaker(autocommit=False, autoflush=False, bind=engine,
expire_on_commit=False)
)
# The only method required to complete the mixin is to add a staticmethod
# that should return a session. This is used in the queries.
class MyBase(BaseMixin):
# give the models an access to a session.
@staticmethod
def session_maker():
return Session()
# By attaching the ``session_maker`` method to the class we now create a
# ``declarative_base`` to be used. The ``BaseMixin`` class declares an
# ``id`` column, that is ``postgresql.UUID``. It also has an declared attr
# for the __tablename__. If you would to override these, they can be declared
# when create your database model
DbModel = declarative_base(cls=MyBase)
class Foo(DbModel):
bar = Column(String(40), nullable=False)
baz = Column(Numeric, nullable=True)
# a method to be called to help in the conversion to json.
@to_json('baz')
def convert_decimal(self, val):
if val is not None:
logging.debug('Converting baz...')
return float(val)
return val
# add a custom value when dumping to json
@dump_method
def add_bang(self, vals):
logging.debug('adding bang')
vals['bang'] = 'boom'
return vals
# attach an event listener to ensure ``bar`` is only saved
# as a lower case string.
@staticmethod
@event_func('before_insert', 'before_update')
def lower_baz(mapper, connection, target):
target.bar = str(target.bar).lower()
# CRUD methods used in ``opertionId`` field of ``swagger.yml``
# connexion needs named parameters in it's operitionId field, so you must
# declare them in the partial in order to work correctly.
get_foo = partial(crud.get, Foo, limit=None, bar=None)
post_foo = partial(crud.post, Foo, foo=None)
get_foo_id = partial(crud.get_id, Foo, foo_id=None)
put_foo = partial(crud.put, Foo, foo_id=None, foo_data=None)
delete_foo = partial(crud.delete, Foo, foo_id=None)
app = connexion.App(__name__)
app.add_api('swagger.yml')
if __name__ == '__main__':
port = os.environ.get('APP_PORT', 8080)
DbModel.metadata.create_all(bind=engine)
app.run(debug=True, port=int(port))
swagger.yml:
swagger: '2.0'
info:
title: Example Foo api
version: '0.1'
consumes:
- application/json
produces:
- application/json
paths:
/foo:
get:
tags: [Foo]
operationId: app.get_foo
summary: Get all the foo
parameters:
- name: limit
in: query
type: integer
minimum: 0
default: 100
- name: bar
in: query
type: string
pattern: "^[a-zA-Z0-9-_]*$"
responses:
200:
description: Return the foo's
schema:
type: array
items:
$ref: '#/definitions/Foo'
post:
tags: [Foo]
operationId: app.post_foo
summary: Create a new foo.
parameters:
- name: foo
in: body
schema:
$ref: '#/definitions/Foo'
responses:
201:
description: Foo created
schema:
$ref: '#/definitions/Foo'
400:
description: Failed to save foo
/foo/{foo_id}:
get:
tags: [Foo]
operationId: app.get_foo_id
summary: Get a single foo
parameters:
- $ref: '#/parameters/foo_id'
responses:
200:
description: Return the foo
schema:
$ref: '#/definitions/Foo'
404:
description: Foo does not exist
put:
tags: [Foo]
operationId: app.put_foo
summary: Update a foo
parameters:
- $ref: '#/parameters/foo_id'
- name: foo_data
in: body
schema:
$ref: '#/definitions/Foo'
responses:
200:
description: Updated foo
schema:
$ref: '#/definitions/Foo'
400:
description: Failed due to invalid data
404:
description: Foo does not exist
delete:
tags: [Foo]
operationId: app.delete_foo
summary: Delete a foo
parameters:
- $ref: '#/parameters/foo_id'
responses:
204:
description: Foo was deleted
404:
description: Foo does not exist
parameters:
foo_id:
name: foo_id
description: Foo's unique identifier
type: string
in: path
required: true
pattern: "^[a-zA-Z0-9-]+$"
definitions:
Foo:
type: object
required:
- bar
properties:
id:
type: string
description: Unique identifier
example: 44a288c1-829c-410d-9a6e-1fce1beb62d4
readOnly: true
bar:
type: string
description: The bar attribute
maxLength: 40
example: 'some bar'
baz:
type: number
description: The number of baz
minimum: 1
example: 10
bang:
type: string
description: A custom read-only variable added to every Foo
readOnly: true