|
1 |
| -""":py:mod:`postgres` implements an object-relational mapper based on |
2 |
| -:py:mod:`psycopg2` composite casters. The fundamental technique, introduced by |
3 |
| -`Michael Robbelard at PyOhio 2013`_, is to write SQL queries that typecast |
4 |
| -results to table types, and then use a :py:mod:`psycopg2` |
5 |
| -:py:class:`~psycopg2.extra.CompositeCaster` to map these to Python objects. |
6 |
| -This means we get to define our schema in SQL, and we get to write our queries |
7 |
| -in SQL, and we get to explicitly indicate in our SQL queries how Python should |
8 |
| -map the results to objects, and then we can write Python objects that contain |
9 |
| -only business logic and not schema definitions. |
| 1 | +""" |
| 2 | +
|
| 3 | +It's somewhat of a fool's errand to introduce a Python ORM in 2013, with |
| 4 | +`SQLAlchemy`_ ascendant (`Django's ORM`_ not-withstanding). And yet here we |
| 5 | +are. SQLAlchemy is mature and robust and full-featured. This makes it complex, |
| 6 | +difficult to learn, and kind of scary. The ORM we introduce here is simpler: it |
| 7 | +targets PostgreSQL only, it depends on raw SQL (it has no object model for |
| 8 | +schema definition nor one for query construction), and it never updates your |
| 9 | +database for you. You are in full, direct control of your application's |
| 10 | +database usage. |
| 11 | +
|
| 12 | +.. _SQLAlchemy: http://www.sqlalchemy.org/ |
| 13 | +.. _Django's ORM: http://www.djangobook.com/en/2.0/chapter05.html |
| 14 | +
|
| 15 | +The fundamental technique we employ, introduced by `Michael Robbelard at PyOhio |
| 16 | +2013`_, is to write SQL queries that typecast results to table types, and then |
| 17 | +use a :py:mod:`psycopg2` :py:class:`~psycopg2.extra.CompositeCaster` to map |
| 18 | +these to Python objects. This means we get to define our schema in SQL, and we |
| 19 | +get to write our queries in SQL, and we get to explicitly indicate in our SQL |
| 20 | +queries how Python should map the results to objects, and then we can write |
| 21 | +Python objects that contain only business logic and not schema definitions. |
10 | 22 |
|
11 | 23 | .. _Michael Robbelard at PyOhio 2013: https://www.youtube.com/watch?v=Wz1_GYc4GmU#t=25m06s
|
12 | 24 |
|
| 25 | +
|
| 26 | +Introducing Table Types |
| 27 | +----------------------- |
| 28 | +
|
13 | 29 | Every table in PostgreSQL has a type associated with it, which is the column
|
14 | 30 | definition for that table. These are composite types just like any other
|
15 | 31 | composite type in PostgreSQL, meaning we can use them to cast query results.
|
|
72 | 88 | :py:mod:`postgres.orm`. We map based on types, not tables.
|
73 | 89 |
|
74 | 90 |
|
| 91 | +.. _orm-tutorial: |
| 92 | +
|
75 | 93 | ORM Tutorial
|
76 | 94 | ------------
|
77 | 95 |
|
|
106 | 124 | >>> rec.bar
|
107 | 125 | 'blam'
|
108 | 126 |
|
109 |
| -As usual, if your query only returns one column, then |
| 127 | +And as usual, if your query only returns one column, then |
110 | 128 | :py:meth:`~postgres.Postgres.all` and :py:meth:`~postgres.Postgres.one_or_zero`
|
111 | 129 | will do the dereferencing for you:
|
112 | 130 |
|
| 131 | + >>> [foo.bar for foo in db.all("SELECT foo.*::foo FROM foo")] |
| 132 | + ['blam', 'whit'] |
113 | 133 | >>> foo = db.one_or_zero("SELECT foo.*::foo FROM foo WHERE bar='blam'")
|
114 | 134 | >>> foo.bar
|
115 | 135 | 'blam'
|
116 |
| - >>> [foo.bar for foo in db.all("SELECT foo.*::foo FROM foo")] |
117 |
| - ['blam', 'whit'] |
| 136 | +
|
| 137 | +To update your database, add a method to your model: |
| 138 | +
|
| 139 | + >>> db.unregister_model(Foo) |
| 140 | + >>> class Foo(Model): |
| 141 | + ... |
| 142 | + ... typname = "foo" |
| 143 | + ... |
| 144 | + ... def set_baz(self, baz): |
| 145 | + ... self.db.run( "UPDATE foo SET baz=%s WHERE bar=%s" |
| 146 | + ... , (baz, self.bar) |
| 147 | + ... ) |
| 148 | + ... self.update_attributes(baz=baz) |
| 149 | + ... |
| 150 | + >>> db.register_model(Foo) |
| 151 | +
|
| 152 | +Then use that method to update the database: |
| 153 | +
|
| 154 | + >>> db.one_or_zero("SELECT baz FROM foo WHERE bar='blam'") |
| 155 | + >>> foo = db.one_or_zero("SELECT foo.*::foo FROM foo WHERE bar='blam'") |
| 156 | + 42 |
| 157 | + >>> foo.set_baz(90210) |
| 158 | + >>> foo.baz |
| 159 | + 90210 |
| 160 | + >>> db.one_or_zero("SELECT baz FROM foo WHERE bar='blam'") |
| 161 | + 90210 |
| 162 | +
|
| 163 | +We never update your database for you. We also never sync your objects for you: |
| 164 | +note the use of the :py:meth:`~postgres.orm.Model.update_attributes` method to |
| 165 | +sync our instance after modifying the database. |
118 | 166 |
|
119 | 167 |
|
120 | 168 | The Model Base Class
|
|
0 commit comments