Simpycity and Exceptable

486 views 72 slides Oct 04, 2010
Slide 1
Slide 1 of 72
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58
Slide 59
59
Slide 60
60
Slide 61
61
Slide 62
62
Slide 63
63
Slide 64
64
Slide 65
65
Slide 66
66
Slide 67
67
Slide 68
68
Slide 69
69
Slide 70
70
Slide 71
71
Slide 72
72

About This Presentation

Aurynn Shaw

Stored Procedures Made Easier

In this talk, we will be covering the Simpycity and Exceptable libraries, a Python-based database abstraction which provides an ORM-equivalent stack around stored procedure development.

We will be covering why stored procedures are an important tool in da...


Slide Content

Simpycity and
Exceptable A Q u e r y - F o c u s s e d
Database Abstraction Aurynn Shaw
PostgreSQL East, 2010 1Saturday, March 27, 2010

What is Simpycity? Query Mapping library
2Saturday, March 27, 2010

Whys and
Wherefores The humble tableLowest level of our data
design
3Saturday, March 27, 2010

Whys and
Wherefores The humble table
Best possible relational representationThis is our ideal -
normalized, good natural
keys, good constraints,
minimal data duplication.
4Saturday, March 27, 2010

Whys and
Wherefores The humble table
Best possible relational representation
Generally, ORMs model tablesAre tables the best
representation to model?
5Saturday, March 27, 2010

But wait... 6Saturday, March 27, 2010

But wait... Objects aren’t like relations
7Saturday, March 27, 2010

But wait... Objects aren’t like relations
A single, coherent conceptObjects encapsulate
attributes, properties,
methods, all the stuff
that constitutes a single
entity
8Saturday, March 27, 2010

But wait... Objects aren’t like relations
A single, coherent concept
Spanning multiple tablesProper normalization,
users are a good example.
9Saturday, March 27, 2010

So... Why are we using ORMs
to represent individual
rows in our tables? Our
objects aren’t relations. 10Saturday, March 27, 2010

Data Modesty Physical dataThey are our physical
data representation -
the lowest abstraction
we work with.
11Saturday, March 27, 2010

Data Modesty Physical data
Exposure is CommitmentExposing your underlying
tables means that you
end up being locked to
that representation -
even if you don’t want to
be.
We argue this is an
important reason for
object abstractions, why
is it different for
relational abstractions?
12Saturday, March 27, 2010

Data Modesty Physical data
Exposure is Commitment
Tables aren’t the logical interfaceWhat we should be
doing is aiming for a
logical
representation of
our data, as opposed
to matching the
physical
representation. We
use methods and
attributes on our
objects to
manipulate data, we
don’t directly fiddle
the private
attributes.
13Saturday, March 27, 2010

If we’re not
modelling tables... What are we modelling?
What should we be
modelling? 14Saturday, March 27, 2010

What is Simpycity? Query Mapping libraryWhy not queries?
Queries are the
mechanism by which
we interact with the
physical data layer,
and queries bind
multiple tables
together via JOIN, so,
are they not a good
representation for our
business layer?
15Saturday, March 27, 2010

What is Simpycity? Query Mapping library
Methods invoke QueriesThe core is arbitrary
methods invoking
arbitrary queries,
seamlessly and deliciously.
16Saturday, March 27, 2010

And now, some Code How to work with
Simpycity systems
firstly, setting up the
data connection 17Saturday, March 27, 2010

And now, some Code >>> from simpycity.context import Context
>>> ctx = Context(dsn=“dbname=‘foo’
username=‘bar’”)a Context is a single
point from which all
data constructs are
derived - the DB
connection can be
committed and closed
from this point.
18Saturday, March 27, 2010

Basic Queries >>> i = ctx.Raw(“““SELECT *
... FROM users.user
... WHERE id = 1”””)
...
>>> user = i()
>>> user[‘id’]
1
>>>Most absolutely basic
usage of Simpycity’s Raw
datatype.
19Saturday, March 27, 2010

Parameterized
Queries >>> i = ctx.Raw(“““SELECT *
... FROM users.user
... WHERE id = %s”””,
... [‘id’])
...
>>> user = i(1)
>>> user[‘id’]
1
>>>Parameterization -
Methods suddenly look
like actual Python
functions!
20Saturday, March 27, 2010

Parameterized
Queries >>> i = ctx.Raw(“““SELECT *
... FROM users.user
... WHERE id = %s”””,
... [‘id’])
...
>>> user = i(1)
>>> user[‘id’]
1
>>> user = i(id=1)
>>> user[‘id’]
1
>>>Simpycity callables even
work with standard
keyword
parameterization.
21Saturday, March 27, 2010

Procedural Logic >>> inst = ctx.Function( “““get_user”””,
[‘id’])
>>> user_positional = inst( 1)
>>> user_positional[ ‘id’]
1
>>> user_keyword = inst( id=1)
>>> user_keyword[‘id’]
1
>>>Functions work exactly
the same - Same
arguments, syntax, and
parameterization
semantics.
22Saturday, March 27, 2010

Positional and
Keyword >>> i = ctx.Function( “““complex_get”””,
[‘id’,‘table’,‘schemaname’])
>>> rs = i(1, ‘some_table’, ‘public’)
- OR -
>>> rs = i(id=1, table=‘some_table’,
schemaname=‘public’);
- OR -
>>> rs = i(schemaname=‘public’, id=1,
table=‘some_table’);A huge advantage of Simpycity is treating the argument chain as positional or keyword arguments -
Allowing your APIs to be indistinguishable from normal Python.
23Saturday, March 27, 2010

Upshots 24Saturday, March 27, 2010

Upshots Running queries is calling a method.You’re dealing with
constructs that act and
respond like a Python
method should act and
respond - It allows for a
very consistent
interface
25Saturday, March 27, 2010

Upshots Running queries is calling a method.
Underlying DB is abstracted awayInstead of writing our
queries using a half-
assed badly-
implemented subset of
SQL, we write queries
directly in SQL, and
never have to worry
about the query
generator writing
ridiculous queries.
Instead, we write our
own ridiculous
queries. ;)
26Saturday, March 27, 2010

Downsides Insert/Update/Delete requires a procedure..Due to Simpycity’s
current architecture,
insert/update/delete
statements aren’t
really supported - it
definitely expects to
get something *Back*
from the DB.
27Saturday, March 27, 2010

Downsides Insert/Update/Delete requires a procedure..
..All arguments must be accounted for..Simpycity’s queries also
don’t have a concept of
defaults, as yet - all
arguments declared by
the definition *must* be
present in the call.
28Saturday, March 27, 2010

This won’t work. >>> inst = ctx.Function( “““complex_get”””,
[‘id’,‘table’,‘schemaname’])
>>> item = inst(1, ‘some_table’)
Traceback (most recent call last):
.. <SNIP> ..
Exception: Insufficient arguments: Expected
3, got 2So this isn’t going to work.
29Saturday, March 27, 2010

Or this. >>> inst = ctx.Function( “““complex_get”””,
[‘id’,‘table’,‘schemaname’])
>>> item = inst(id=1, table=‘some_table’)
Traceback (most recent call last):
.. <SNIP> ..
Exception: Insufficient arguments: Expected
3, got 2
30Saturday, March 27, 2010

Downsides Insert/Update/Delete requires a procedure..
..All arguments must be accounted for..
..Big resultsets will be entirely pulled into
memory..Another disadvantage is running a query that has a lot of results will pull them *all* into
memory, by default. This is a limitation of the underlying result set representation, for
reasons I’ll get into in a moment.
31Saturday, March 27, 2010

Downsides Insert/Update/Delete requires a procedure..
All arguments must be accounted for
Big resultsets will be entirely pulled into
memory
.. And, you’ll be writing a lot of SQL...Simpycity doesn’t do
*any* actual query
generation - the most it
will do is the select *
from function. None of
the more advanced query
generation exists here.
32Saturday, March 27, 2010

This is all it does >>> inst = ctx.Function( “““complex_get”””,
[‘id’,‘table’,‘schemaname’])
SELECT * FROM complex_get(%s, %s, %s)
Becomes
33Saturday, March 27, 2010

Upshots Running queries is calling a method.
Underlying DB is abstracted away
...but you should be anyway.SQL is programming -
code. It’s as important
as the rest of your
application, and you’re
better at writing it
than a computer is.
Even more, computers
cannot extract
semantic meaning
from your relational
design, and cannot
build appropriate
representations - only
the programmer can.
34Saturday, March 27, 2010

Applications! While the queries are
useful on their own,
they don’t really provide
an easy way to manage
data at an application
level.
For that, 35Saturday, March 27, 2010

Applications! Need Logical RepresentationsThe reasoning behind that
is that applications require
logical abstractions that
make sense from the
application perspective -
36Saturday, March 27, 2010

Applications! Need Logical Representations
Business models!Divorcing us from the
underlying table
representations. Instead
of modelling tables, we
should be modelling
*objects*, concepts that
are complete unto
themselves.
37Saturday, March 27, 2010

Models in Simpycity Don’t model tablesModels in Simpycity
follow this logical chain -
we don’t model the tables.
Instead, we work to find
what the best
representation of a given
object is,
38Saturday, March 27, 2010

Models in Simpycity Don’t model tables
Aligned towards application requirementsthe one that most clearly fits what the
application itself requires, in terms of internal
architecture and logical consistency.
Instead of fighting to make relational concepts
fit into objects, we should be making our objects
accurately represent the concepts we need.
39Saturday, March 27, 2010

Models in Simpycity Don’t model tables
Aligned towards application requirements
Still allow for Active Record-style manipulationAt the same time, the Active Record pattern has a lot of useful concepts, like direct
instancing and .save() on dirty objects.
For these reasons, the model pattern that Simpycity uses is less Active Record, as
we’re not modelling result sets, but more
40Saturday, March 27, 2010

Active Object Active Object.
Let’s have a look at how
Simpycity handles
Active Object. 41Saturday, March 27, 2010

Basic Models >>> base = ctx.Model()
- OR -
>>> class base(ctx.Model()):
... passFirst, we create a base class that all our models
derive from - This allows us to add additional
functionality on a global level to our application
models.
For instance, Vertically Challenged creates a base
model with authentication tokens baked in.
42Saturday, March 27, 2010

Basic Models >>> base = ctx.Model()
>>> class ourUser(base):
... table = [“id”, “username”]Declaring the basic model - our instance, and the
table declares what our internal attributes are.
Note how we don’t really enforce data types -
this just declares what the business object looks
like.
43Saturday, March 27, 2010

Basic Models base = ctx.Model()
class ourUser(base):
table = [“id”, “username”]
__load__ = ctx.Function( “get_user”,[‘id’])__load__ is the basic instancing mechanism in a
Simpycity - under the covers, any arguments
passed to the instancing of a new object will be
passed to this function, and then mapped to the
object’s attributes.
44Saturday, March 27, 2010

Basic Models >>> base = ctx.Model()
>>> class ourUser(base):
... table = [“id”, “username”]
... __load__ = ctx.Function( “get_user”,
[‘id’])
>>> u = ourUser(1)
>>> u.id
1
>>> u.username
“Test User”
>>>From load, we instance our
models just as if we were
running the query directly -
only now, we have the model
attributes available, that we
didn’t have before.
45Saturday, March 27, 2010

Basic Models >>> u = ourUser(1)
>>> u.id
1
>>> u.username
“Test User”
>>> u.username = “PGEast Demo”
>>> u.username
“PGEast Demo”
>>>For instance, we can update the model
with new values.
But, they’re just a part of that
particular model instance. They’re not
persisted out to the database.
Fortunately, Simpycity can deal with
this, too:
46Saturday, March 27, 2010

Basic Models class ourUser(base):
table = [“id”, “username”]
__load__ = ctx.Function( “get_user”,[‘id’])
__save__ = ctx.Function( “save_user”,
[‘id’,‘username’])
>>> u.username = “PG Demo”
>>> u.username
“PG Demo”
>>> u.save()
>>> ctx.commit()Coming back to our model, we’ve added
another property - __save__.
By declaring __save__ on our model, we
get access to the dynamic .save() method.
This method will take the current model’s
state, test for any dirty values, and use
the provided callable to save those values.
The callable is allowed to be anything you
like - it doesn’t have to be a Simpycity
function.
47Saturday, March 27, 2010

Basic Models class ourUser(base):
table = [“id”, “username”, “password”]
__load__ = ctx.Function( “get_user”,[‘id’])
__save__ = ctx.Function( “save_user”,
[‘id’,‘username’])

delete = ctx.Function( “delete_user”,
[‘id’])
Additionally, models need custom methods, and Simpycity
covers that too - assigning a Simpycity function to an
attribute will be automatically converted into a bound
callable, and will map any arguments named in the table as
that value.
In this case, we’ve provided a delete method on the model,
which Simpycity does not currently support, and anything else
you can conceive works in this way.
48Saturday, March 27, 2010

Other Instance
Patterns So, at this point, we have a model that can be instanced, manipulated, and
deleted, similar to a normal ORM. All we know about the underlying
database is there’s a couple of functions, and what their arguments are.
Unfortunately, we only have a single instancing mechanism. This works for
limited cases, but is really lacking in flexibility.
What we need is multiple instancing patterns, a variety of ways to pull data
from the database.
Simpycity can handle that, too - with a nifty little pattern. 49Saturday, March 27, 2010

Other Instance
Patterns class ourUser(base):
... # Remember the Code.
by_id = ctx.Function( “get_user”,[‘id’],
returns_type=base)
by_username = ctx.Function( “get_user”,
[‘username’], return_type=ourUser)Here, we’re stepping back to the core Raw and Function mechanisms, and
passing a new argument, return_type.
Return type takes the provided class and will map the results of the query to
that object.
If the query returned multiple rows, then you’ll get a normal list of objects, all
correctly mapped.
This functionality even allows for the easy creation of new generators, such as
50Saturday, March 27, 2010

Newly Minted
Objects class ourUser(base):
... # Code was here.
new = ctx.Function(“new_user”,[‘username’,
‘password’], return_type=ourUser)with this new method.
By declaring the database function to return the new user object, we both
insert our new record and get our working model in a single call.
And, since it’s already persisted to our database, any methods hanging on the
model that require database backing will still work.
Any single aspect goes wrong, and it all rolls back, just as it should.
51Saturday, March 27, 2010

How about Search? >>> search = ctx.Function( “user_search”,
[‘content’], return_type=ourUser)
>>> u = search(“content in their profile” )
>>> A search method would even work great in this format - any
function that can generate the target object type is a great fit.
52Saturday, March 27, 2010

It’s all about good
abstractions So far, everything that we’ve talked about has been discussing building good
abstractions at the business logic level of our app - abstracting the
underlying able design into views and stored procedures, as necessary.
But this does overlook a single critical aspect of building good APIs, and that
is Exceptions. 53Saturday, March 27, 2010

Exceptions Stored Procedures get RAISE EXCEPTIONBy default, exceptions in plpgsql are very generic - a single
exception type, with a text argument.
While functional, this does not provide an abundance of
manipulatable error types.
54Saturday, March 27, 2010

Exceptions In stored procedures, RAISE EXCEPTION
In Python, this becomes an InternalErrorWhen this single exception type reaches the Python
layer, psycopg2 will convert it into an InternalError -
the string is preserved.
This is somewhat useful, but,
55Saturday, March 27, 2010

Current Exceptions CREATE OR REPLACE FUNCTION except_test()
RETURNS VOID AS $$
BEGIN
RAISE EXCEPTION 'Test!';
END;
$$ LANGUAGE PLPGSQL;
>>> c.Function("except_test")()
Traceback (most recent call last):
... <SNIP> ...
psycopg2.InternalError: Test!but, this is what we
currently have to work
with from the DB.
Not all that useful,
right?
56Saturday, March 27, 2010

Exceptions In stored procedures, RAISE EXCEPTION
This becomes an InternalError
But what was it *really*?Unless you’re deeply adhering to a consistent style in your exception text - not always
easily done - you’re going to end up with inconsistencies.
Because of this, you’ll end up in a position where you’re parsing error strings, looking for
specific errors. Not the best design practise.
57Saturday, March 27, 2010

Exceptable Consistency of exception textThe first advantage that
Exceptable in Simpycity
brings is significantly more
consistent exception text -
allowing for Exceptable to
consistently parse and re-
raise exceptions.
58Saturday, March 27, 2010

Exceptable Consistency of exception text
Simple DB APIWorking with Exceptable at
the DB level is also incredibly
easy: there’s really only 2
queries that need to be
remembered.
59Saturday, March 27, 2010

Easy! CREATE OR REPLACE FUNCTION except_test()
RETURNS VOID AS $$
SELECT exceptable.raise(
‘YourException’,
‘This is the Error Text’ );
$$ LANGUAGE SQL;
Firstly, raising exceptions
- easily handled, though
slightly more verbose
than a standard
exception.
60Saturday, March 27, 2010

and in Python >>> c.Function("except_test")()
Traceback (most recent call last):
... <SNIP> ...
psycopg2.InternalError: YourException::
This is the Error TextThis is what the
error string looks
like, now!
61Saturday, March 27, 2010

Adding new
Exceptions your_database=> SELECT exceptable.register
(‘YourException’,‘This is our custom
exception!’, NULL);
register
----------
tThe second aspect is adding new exceptions to the Exceptable
tables - without this, attempting to use an exception will
throw an error.
This is done solely so that, even though it’s not yet
implemented, Exceptable can introspect the exceptions table
and automatically generate exceptions.
It also allows for enforced consistency - Typos happen to even
the best of us.
62Saturday, March 27, 2010

Exceptable Consistency of exception text
Simple DB API
Good Application ExceptionsAll of this is trying to work
to bringing *good*
exceptions to the
application fabric - An
easily parsed exception
from the database, and the
Exceptable integration in
Simpycity means, we can
have first-class Python
exceptions from our stored
procedures.
Here’s how:
63Saturday, March 27, 2010

Register the
Exception.. >>> from simpycity.exceptions import base
>>> class YourException(Exception):
... pass
...
>>> base.register(“YourException”,
YourException)The syntax for registering a new exception
at the Python layer is similar to the DB
layer, with the first argument providing the
same text value as the exception type in the
DB.
The second argument is the class definition,
and instances will be used any time your
exception is raised from the database.
64Saturday, March 27, 2010

And then use it. >>> c.Function(“except_test”)()
Traceback (most recent call last):
... SNIP ...
YourException: This is the Error text
And using it is identical to any other
Python exception. Simple and easy.
65Saturday, March 27, 2010

PG Error Codes But wait! 8.4 supports custom error codes!A cool feature of 8.4 and
higher is the ability to raise an
exception inside a given error
code. There’s a large list of
error codes, and a sub-feature
of that is the ability to raise
custom error codes.
66Saturday, March 27, 2010

PG Error Codes But wait! 8.4 supports custom error codes
But Exceptable doesn’t! (yet)This is one of those features
that Exceptable *will* be
supporting, eventually.
Instead of using a regex to
map exceptions, exceptable
will just compare a list of pg
error codes - much simpler
to implement.
67Saturday, March 27, 2010

See! Custom! CREATE OR REPLACE FUNCTION ex_test()
RETURNS void
AS $body$
BEGIN
RAISE EXCEPTION 'testing'
USING ERRCODE= 'EX111';
END;
$body$ LANGUAGE PLPGSQL;
68Saturday, March 27, 2010

And the Python >>> try:
... c.Function(“ex_test”)()
... except Exception, e:
... print e.pgcode
...
EX111And our ex111 exception gets properly
propagated upwards, and Exceptable would
be able to catch it and map it correctly.
69Saturday, March 27, 2010

And, thus Any Questions? 70Saturday, March 27, 2010

Get it! https://
projects.commandprompt.
com/public/simpycity https://
projects.commandprompt.
com/public/exceptable and 71Saturday, March 27, 2010

And finally,
Thank you. 72Saturday, March 27, 2010