Skip to content

Commit 57d3604

Browse files
author
James William Pye
committed
Refactor type I/O to avoid importing all the type I/O dependencies.
decimal, datetime, xml.etree, and other stdlib modules are not particularly lightweight. Add an additional layer to resolution to avoid unnecessary imports. Things done while at it: - Converge all the C optimizations into a single module and relocate it into the port package. - Relocate typio into types.io - Split typstruct into python.structlib and types.io.lib - Move geometry types into types.geometry and *bit into types.bitwise - Move the TypeIO class into driver.pg_type. Some random cleanup is still necessary. driver.pg_type may need to be renamed to driver.stdio or something along those lines(driver.stdtypio?). Fixes #15
1 parent 40ce52d commit 57d3604

40 files changed

Lines changed: 2741 additions & 2515 deletions

postgresql/driver/pg_type.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
##
2+
# driver.pg_type - Standard Database Type I/O
3+
##
4+
from codecs import lookup as lookup_codecs
5+
from abc import ABCMeta, abstractmethod
6+
from operator import itemgetter
7+
from itertools import count
8+
from ..encodings.aliases import get_python_name
9+
from ..python.functools import Composition as compose
10+
from ..string import quote_ident
11+
from .. import types as pg_types
12+
from ..types.io import resolve
13+
from ..types.io.pg_container import record_io_factory, array_io_factory
14+
from ..types import Row, Array, oid_to_sql_name, oid_to_name
15+
16+
class TypeIO(object, metaclass = ABCMeta):
17+
"""
18+
A class that manages I/O for a given configuration. Normally, a connection
19+
would create an instance, and configure it based upon the version and
20+
configuration of PostgreSQL that it is connected to.
21+
"""
22+
strio = (None, None, str)
23+
24+
@abstractmethod
25+
def lookup_type_info(self, typid):
26+
"""
27+
"""
28+
29+
@abstractmethod
30+
def lookup_composite_type_info(self, typid):
31+
"""
32+
"""
33+
34+
def set_encoding(self, value):
35+
"""
36+
Set a new client encoding.
37+
"""
38+
self.encoding = value.lower().strip()
39+
enc = get_python_name(self.encoding)
40+
ci = lookup_codecs(enc or self.encoding)
41+
self._encode, self._decode, *_ = ci
42+
43+
def encode(self, string_data):
44+
return self._encode(string_data)[0]
45+
46+
def decode(self, bytes_data):
47+
return self._decode(bytes_data)[0]
48+
49+
def encodes(self, iter, get0 = itemgetter(0)):
50+
"""
51+
Encode the items in the iterable in the configured encoding.
52+
"""
53+
return map(compose((self._encode, get0)), iter)
54+
55+
def decodes(self, iter, get0 = itemgetter(0)):
56+
"""
57+
Decode the items in the iterable from the configured encoding.
58+
"""
59+
return map(compose((self._decode, get0)), iter)
60+
61+
def resolve_pack(self, typid):
62+
return self.resolve(typid)[0] or self.encode
63+
64+
def resolve_unpack(self, typid):
65+
return self.resolve(typid)[1] or self.decode
66+
67+
def attribute_map(self, pq_descriptor):
68+
return zip(self.decodes(pq_descriptor.keys()), count())
69+
70+
def __init__(self):
71+
strio = self.strio
72+
self.encoding = None
73+
self._cache = {
74+
# Encoded character strings
75+
pg_types.ACLITEMOID : strio, # No binary functions.
76+
pg_types.NAMEOID : strio,
77+
pg_types.BPCHAROID : strio,
78+
pg_types.VARCHAROID : strio,
79+
pg_types.CSTRINGOID : strio,
80+
pg_types.TEXTOID : strio,
81+
pg_types.REGTYPEOID : strio,
82+
pg_types.REGPROCOID : strio,
83+
pg_types.REGPROCEDUREOID : strio,
84+
pg_types.REGOPEROID : strio,
85+
pg_types.REGOPERATOROID : strio,
86+
pg_types.REGCLASSOID : strio,
87+
}
88+
self.typinfo = {}
89+
90+
def sql_type_from_oid(self, oid, qi = quote_ident):
91+
if oid in oid_to_sql_name:
92+
return oid_to_sql_name[oid]
93+
if oid in self.typinfo:
94+
nsp, name, *_ = self.typinfo[oid]
95+
return qi(nsp) + '.' + qi(name)
96+
return 'pg_catalog.' + pg_types.oid_to_name.get(oid)
97+
98+
def type_from_oid(self, oid):
99+
if oid in self._cache:
100+
typ = self._cache[oid][2]
101+
return typ
102+
103+
def resolve_descriptor(self, desc, index):
104+
'create a sequence of I/O routines from a pq descriptor'
105+
return [
106+
(self.resolve(x[3]) or (None, None))[index] for x in desc
107+
]
108+
109+
# lookup a type's IO routines from a given typid
110+
def resolve(self,
111+
typid : "The Oid of the type to resolve pack and unpack routines for.",
112+
from_resolution_of : \
113+
"Sequence of typid's used to identify infinite recursion" = (),
114+
builtins : "types.io.resolve" = resolve,
115+
quote_ident = quote_ident
116+
):
117+
if from_resolution_of and typid in from_resolution_of:
118+
raise TypeError(
119+
"type, %d, is already being looked up: %r" %(
120+
typid, from_resolution_of
121+
)
122+
)
123+
typid = int(typid)
124+
typio = None
125+
126+
if typid in self._cache:
127+
typio = self._cache[typid]
128+
else:
129+
typio = builtins(typid)
130+
if typio is not None:
131+
if typio.__class__ is not tuple:
132+
typio = typio(typid, self)
133+
self._cache[typid] = typio
134+
135+
if typio is None:
136+
# Lookup the type information for the typid as it's not cached.
137+
##
138+
ti = self.lookup_type_info(typid)
139+
if ti is not None:
140+
typnamespace, typname, typtype, typlen, typelem, typrelid, \
141+
ae_typid, ae_hasbin_input, ae_hasbin_output = ti
142+
self.typinfo[typid] = (
143+
typnamespace, typname, typrelid, int(typelem) if ae_typid else None
144+
)
145+
if typrelid:
146+
# Row type
147+
#
148+
# The attribute name map,
149+
# column I/O,
150+
# column type Oids
151+
# are needed to build the packing pair.
152+
attmap = {}
153+
cio = []
154+
typids = []
155+
attnames = []
156+
i = 0
157+
for x in self.lookup_composite_type_info(typrelid):
158+
attmap[x[1]] = i
159+
attnames.append(x[1])
160+
typids.append(x[0])
161+
pack, unpack, typ = self.resolve(
162+
x[0], list(from_resolution_of) + [typid]
163+
)
164+
cio.append((pack or self.encode, unpack or self.decode))
165+
i += 1
166+
self._cache[typid] = typio = record_io_factory(
167+
cio, typids, attmap, list(
168+
map(self.sql_type_from_oid, typids)
169+
), attnames,
170+
quote_ident(typnamespace) + '.' + \
171+
quote_ident(typname),
172+
)
173+
elif ae_typid is not None:
174+
# Array Type
175+
te = self.resolve(
176+
int(typelem),
177+
from_resolution_of = list(from_resolution_of) + [typid]
178+
) or (None, None)
179+
typio = array_io_factory(
180+
te[0] or self.encode,
181+
te[1] or self.decode,
182+
typelem,
183+
ae_hasbin_input,
184+
ae_hasbin_output
185+
)
186+
self._cache[typid] = typio
187+
else:
188+
self._cache[typid] = typio = self.strio
189+
else:
190+
# Throw warning about type without entry in pg_type?
191+
typio = self.strio
192+
return typio

0 commit comments

Comments
 (0)