Top Banner
Simpycity and Exceptable A Query-Focussed Database Abstraction Aurynn Shaw PostgreSQL East, 2010 1 Saturday, March 27, 2010
72

Simpycity and Exceptable

May 19, 2015

Download

Documents

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 database application development, how to design business logic abstractions for your database, and how Exceptable aids in providing for cleaner client code.

Finally, we will cover example model code implementing the Simpycity abstractions, and cover how these abstractions isolate the underlying data model without damaging functionality or data access.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Simpycity and Exceptable

Simpycity and Exceptable

A Q u e r y - F o c u s s e d D a t a b a s e A b s t r a c t i o n

A u r y n n S h a w

P o s t g r e S Q L E a s t , 2 0 1 0

1Saturday, March 27, 2010

Page 2: Simpycity and Exceptable

What is Simpycity?

Query Mapping library

2Saturday, March 27, 2010

Page 3: Simpycity and Exceptable

Whys and Wherefores

The humble table

Lowest level of our data design

3Saturday, March 27, 2010

Page 4: Simpycity and Exceptable

Whys and Wherefores

The humble table

Best possible relational representation

This is our ideal - normalized, good natural keys, good constraints, minimal data duplication.

4Saturday, March 27, 2010

Page 5: Simpycity and Exceptable

Whys and Wherefores

The humble table

Best possible relational representation

Generally, ORMs model tables

Are tables the best representation to model?

5Saturday, March 27, 2010

Page 6: Simpycity and Exceptable

But wait...

6Saturday, March 27, 2010

Page 7: Simpycity and Exceptable

But wait...

Objects aren’t like relations

7Saturday, March 27, 2010

Page 8: Simpycity and Exceptable

But wait...

Objects aren’t like relations

A single, coherent concept

Objects encapsulate attributes, properties, methods, all the stuff that constitutes a single entity

8Saturday, March 27, 2010

Page 9: Simpycity and Exceptable

But wait...

Objects aren’t like relations

A single, coherent concept

Spanning multiple tables

Proper normalization, users are a good example.

9Saturday, March 27, 2010

Page 10: Simpycity and Exceptable

So...Why are we using ORMs to represent individual rows in our tables? Our objects aren’t relations.

10Saturday, March 27, 2010

Page 11: Simpycity and Exceptable

Data Modesty

Physical data

They are our physical data representation - the lowest abstraction we work with.

11Saturday, March 27, 2010

Page 12: Simpycity and Exceptable

Data Modesty

Physical data

Exposure is Commitment

Exposing 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

Page 13: Simpycity and Exceptable

Data Modesty

Physical data

Exposure is Commitment

Tables aren’t the logical interface

What 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

Page 14: Simpycity and Exceptable

If we’re not modelling tables...

What are we modelling? What should we be modelling?

14Saturday, March 27, 2010

Page 15: Simpycity and Exceptable

What is Simpycity?

Query Mapping library

Why 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

Page 16: Simpycity and Exceptable

What is Simpycity?

Query Mapping library

Methods invoke Queries

The core is arbitrary methods invoking arbitrary queries, seamlessly and deliciously.

16Saturday, March 27, 2010

Page 17: Simpycity and Exceptable

And now, some Code

How to work with Simpycity systems

firstly, setting up the data connection

17Saturday, March 27, 2010

Page 18: Simpycity and Exceptable

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

Page 19: Simpycity and Exceptable

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

Page 20: Simpycity and Exceptable

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

Page 21: Simpycity and Exceptable

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

Page 22: Simpycity and Exceptable

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

Page 23: Simpycity and Exceptable

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

Page 24: Simpycity and Exceptable

Upshots

24Saturday, March 27, 2010

Page 25: Simpycity and Exceptable

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

Page 26: Simpycity and Exceptable

Upshots

Running queries is calling a method.

Underlying DB is abstracted away

Instead 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

Page 27: Simpycity and Exceptable

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

Page 28: Simpycity and Exceptable

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

Page 29: Simpycity and Exceptable

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 2

So this isn’t going to work.

29Saturday, March 27, 2010

Page 30: Simpycity and Exceptable

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

Page 31: Simpycity and Exceptable

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

Page 32: Simpycity and Exceptable

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

Page 33: Simpycity and Exceptable

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

Page 34: Simpycity and Exceptable

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

Page 35: Simpycity and Exceptable

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

Page 36: Simpycity and Exceptable

Applications!

Need Logical Representations The reasoning behind that is that applications require logical abstractions that make sense from the application perspective -

36Saturday, March 27, 2010

Page 37: Simpycity and Exceptable

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

Page 38: Simpycity and Exceptable

Models in Simpycity

Don’t model tables

Models 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

Page 39: Simpycity and Exceptable

Models in Simpycity

Don’t model tables

Aligned towards application requirements

the 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

Page 40: Simpycity and Exceptable

Models in Simpycity

Don’t model tables

Aligned towards application requirements

Still allow for Active Record-style manipulation

At 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

Page 41: Simpycity and Exceptable

Active Object

Active Object.

Let’s have a look at how Simpycity handles Active Object.

41Saturday, March 27, 2010

Page 42: Simpycity and Exceptable

Basic Models

>>> base = ctx.Model()- OR ->>> class base(ctx.Model()):... pass

First, 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

Page 43: Simpycity and Exceptable

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

Page 44: Simpycity and Exceptable

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

Page 45: Simpycity and Exceptable

Basic Models

>>> base = ctx.Model()>>> class ourUser(base):... table = [“id”, “username”]... __load__ = ctx.Function(“get_user”,[‘id’])

>>> u = ourUser(1)>>> u.id1>>> 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

Page 46: Simpycity and Exceptable

Basic Models

>>> u = ourUser(1)>>> u.id1>>> 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

Page 47: Simpycity and Exceptable

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

Page 48: Simpycity and Exceptable

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

Page 49: Simpycity and Exceptable

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

Page 50: Simpycity and Exceptable

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

Page 51: Simpycity and Exceptable

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

Page 52: Simpycity and Exceptable

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

Page 53: Simpycity and Exceptable

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

Page 54: Simpycity and Exceptable

Exceptions

Stored Procedures get RAISE EXCEPTION

By 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

Page 55: Simpycity and Exceptable

Exceptions

In stored procedures, RAISE EXCEPTION

In Python, this becomes an InternalError

When 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

Page 56: Simpycity and Exceptable

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

Page 57: Simpycity and Exceptable

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

Page 58: Simpycity and Exceptable

Exceptable

Consistency of exception text

The 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

Page 59: Simpycity and Exceptable

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

Page 60: Simpycity and Exceptable

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

Page 61: Simpycity and Exceptable

and in Python

>>> c.Function("except_test")()Traceback (most recent call last): ... <SNIP> ...psycopg2.InternalError: YourException:: This is the Error Text

This is what the error string looks like, now!

61Saturday, March 27, 2010

Page 62: Simpycity and Exceptable

Adding new Exceptions

your_database=> SELECT exceptable.register(‘YourException’,‘This is our custom exception!’, NULL); register---------- t

The 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

Page 63: Simpycity and Exceptable

Exceptable

Consistency of exception text

Simple DB API

Good Application Exceptions

All 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

Page 64: Simpycity and Exceptable

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

Page 65: Simpycity and Exceptable

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

Page 66: Simpycity and Exceptable

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

Page 67: Simpycity and Exceptable

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

Page 68: Simpycity and Exceptable

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

Page 69: Simpycity and Exceptable

And the Python

>>> try:... c.Function(“ex_test”)()... except Exception, e:... print e.pgcode... EX111 And our ex111 exception gets properly

propagated upwards, and Exceptable would be able to catch it and map it correctly.

69Saturday, March 27, 2010

Page 70: Simpycity and Exceptable

And, thusA n y Q u e s t i o n s ?

70Saturday, March 27, 2010

Page 72: Simpycity and Exceptable

And finally,Thank you.

72Saturday, March 27, 2010