11##
2- # copyright 2009, James William Pye
3- # http://python.projects.postgresql.org
2+ # .lib - query libraries; manage SQL outside of Python.
43##
54"""
65PostgreSQL query libraries.
76
87The purpose of query libraries is provide a means to manage a mapping of symbols
9- to database operations. These operations can be simple statements, procedures ,
10- or something more complex.
8+ to database operations or objects . These operations can be simple statements,
9+ procedures, or something more complex.
1110
1211Libraries are intended to allow the programmer to isolate and manage SQL outside
1312of a system's code-flow. It provides a means to construct the basic Python
1413interfaces to a PostgreSQL-based application.
1514"""
1615import io
1716import os .path
18- import operator
19- get0 = operator .itemgetter (0 )
2017from types import ModuleType
2118from abc import abstractmethod , abstractproperty
2219from ..python .element import Element , ElementSet
2320from .. import api as pg_api
2421from .. import sys as pg_sys
2522from .. import exceptions as pg_exc
2623from ..python .itertools import find
24+ from itertools import chain
2725
2826try :
2927 libdir = os .path .abspath (os .path .dirname (__file__ ))
@@ -83,6 +81,7 @@ def __init__(self,
8381 method = None ,
8482 type = None ,
8583 parameters = None ,
84+ reference = False ,
8685 ):
8786 self .library = library
8887 self .source = source
@@ -94,6 +93,7 @@ def __init__(self,
9493 self .method = method
9594 self .type = type
9695 self .parameters = parameters
96+ self .reference = reference
9797
9898 def __str__ (self ):
9999 """
@@ -163,15 +163,22 @@ def __init__(self, symbols, preface = None):
163163 self ._name = None
164164 s = self .symbolsd = {}
165165 self .preload = set ()
166- for name , (typ , exe , doc , query ) in symbols :
166+ for name , (isref , typ , exe , doc , query ) in symbols :
167167 if typ and typ not in self .symtypes :
168- raise ValueError ("symbol %r has an invalid type: %r" % (name , typ ))
168+ raise ValueError (
169+ "symbol %r has an invalid type: %r" % (name , typ )
170+ )
169171 if typ == 'preload' :
170172 self .preload .add (name )
171173 typ = None
172174 elif typ == 'proc' :
173175 pass
174- SYM = Symbol (self , query , name = name , method = exe , type = typ )
176+ SYM = Symbol (self , query ,
177+ name = name ,
178+ method = exe ,
179+ type = typ ,
180+ reference = isref
181+ )
175182 s [name ] = SYM
176183
177184class ILF (SymbolCollection ):
@@ -196,27 +203,6 @@ def get_symbol(self, name):
196203 def symbols (self ):
197204 return self .symbolsd .keys ()
198205
199- def __init__ (self , symbols , preface = None ):
200- """
201- Given an iterable of (symtype, symexe, doc, sql) tuples, create an
202- anonymous ILF library.
203- """
204- self .preface = preface
205- self ._address = None
206- self ._name = None
207- s = self .symbolsd = {}
208- self .preload = set ()
209- for name , (typ , exe , doc , query ) in symbols :
210- if typ and typ not in self .symtypes :
211- raise ValueError ("symbol %r has an invalid type: %r" % (name , typ ))
212- if typ == 'preload' :
213- self .preload .add (name )
214- typ = None
215- elif typ == 'proc' :
216- pass
217- SYM = Symbol (self , query , name = name , method = exe , type = typ )
218- s [name ] = SYM
219-
220206 @classmethod
221207 def from_lines (typ , lines ):
222208 """
@@ -247,7 +233,9 @@ def from_lines(typ, lines):
247233 # symbol name
248234 # symbol type
249235 # how to execute symbol
250- name , styp , exe , * _ = (tuple (symdesc .strip ().strip ('[]' ).split (':' )) + (None , None ))
236+ name , styp , exe , * _ = (tuple (
237+ symdesc .strip ().strip ('[]' ).split (':' )
238+ ) + (None , None ))
251239 doc = ''
252240 endofcomment = 0
253241 # resolve any symbol references; only one per line.
@@ -266,7 +254,12 @@ def from_lines(typ, lines):
266254 query = '' .join (block [endofcomment :])
267255 if styp == 'proc' :
268256 query = query .strip ()
269- syms .append ((name , (styp , exe , doc , query )))
257+ if name .startswith ('&' ):
258+ name = name [1 :]
259+ isref = True
260+ else :
261+ isref = False
262+ syms .append ((name , (isref , styp , exe , doc , query )))
270263 return typ (syms , preface = preface )
271264
272265 @classmethod
@@ -303,9 +296,38 @@ def __init__(self, symbol, database):
303296 def __call__ (self , * args , ** kw ):
304297 return self .method (* args , ** kw )
305298
299+ class BoundReference (object ):
300+ """
301+ A symbol bound to a database whose results make up the source of a symbol
302+ that will be created upon the execution of this symbol.
303+
304+ A reference to a symbol.
305+ """
306+
307+ def __init__ (self , symbol , database ):
308+ self .symbol = symbol
309+ self .database = database
310+ self .method = database .prepare (symbol ).chunks
311+
312+ def __call__ (self , * args , ** kw ):
313+ chunks = chain .from_iterable (self .method (* args , ** kw ))
314+ # Join columns with a space, and rows with a newline.
315+ src = '\n ' .join ([' ' .join (row ) for row in chunks ])
316+ return BoundSymbol (
317+ Symbol (
318+ self .symbol .library , src ,
319+ name = self .symbol .name ,
320+ method = self .symbol .method ,
321+ type = self .symbol .type ,
322+ parameters = self .symbol .parameters ,
323+ reference = False ,
324+ ),
325+ self .database ,
326+ )
327+
306328class Binding (object ):
307329 """
308- Interface to a library bound to a database(connection).
330+ Library bound to a database(connection).
309331 """
310332 def __init__ (self , database , library ):
311333 self .__dict__ .update ({
@@ -328,8 +350,8 @@ def __dir__(self):
328350
329351 def __getattr__ (self , name ):
330352 """
331- Return a BoundSymbol against the Binding's database with the symbol named
332- ``name`` in the Binding's library.
353+ Return a BoundSymbol against the Binding's database with the
354+ symbol named ``name`` in the Binding's library.
333355 """
334356 d = self .__dict__
335357 s = d ['__symbol_cache__' ]
@@ -339,32 +361,43 @@ def __getattr__(self, name):
339361 bs = s .get (name )
340362 if bs is None :
341363 # No symbol cached with that name.
364+ # Everything is crammed in here because
365+ # we do *not* want methods on this object.
366+ # The namespace is primarily reserved for symbols.
342367 sym = lib .get_symbol (name )
343368 if sym is None :
344369 raise AttributeError (
345370 "symbol %r does not exist in library %r" % (
346371 name , lib .address
347372 )
348373 )
349- if not isinstance (sym , Symbol ):
350- # subjective symbol...
351- sym = sym (db )
352- if not isinstance (sym , Symbol ):
353- raise TypeError (
354- "callable symbol, %r, did not produce Symbol instance" % (
355- name ,
356- )
357- )
358- if sym .type == 'const' :
359- r = BoundSymbol (sym , db )()
360- if sym .method in ('chunks' , 'rows' , 'column' ):
361- # resolve the iterator
362- r = list (r )
363- bs = s [name ] = r
364- else :
365- bs = BoundSymbol (sym , db )
374+ if sym .reference :
375+ # Reference.
376+ bs = BoundReference (sym , db )
377+ if sym .type == 'const' :
378+ # Constant Reference means a BoundSymbol.
379+ bs = bs ()
366380 if sym .type != 'transient' :
367381 s [name ] = bs
382+ else :
383+ if not isinstance (sym , Symbol ):
384+ # subjective symbol...
385+ sym = sym (db )
386+ if not isinstance (sym , Symbol ):
387+ raise TypeError (
388+ "callable symbol, %r, did not produce " \
389+ "Symbol instance" % (name ,)
390+ )
391+ if sym .type == 'const' :
392+ r = BoundSymbol (sym , db )()
393+ if sym .method in ('chunks' , 'rows' , 'column' ):
394+ # resolve the iterator
395+ r = list (r )
396+ bs = s [name ] = r
397+ else :
398+ bs = BoundSymbol (sym , db )
399+ if sym .type != 'transient' :
400+ s [name ] = bs
368401 return bs
369402
370403class Category (pg_api .Category ):
0 commit comments