|
15 | 15 | This module is used to define the PG-API. It creates a set of ABCs |
16 | 16 | that makes up the basic interfaces used to work with a PostgreSQL. |
17 | 17 | """ |
18 | | -from abc import ABCMeta, abstractproperty, abstractmethod |
| 18 | +import os |
| 19 | +import warnings |
19 | 20 | import collections |
20 | | -from operator import attrgetter, methodcaller |
| 21 | +from abc import ABCMeta, abstractproperty, abstractmethod |
| 22 | +from operator import attrgetter, methodcaller, itemgetter |
| 23 | + |
| 24 | +class Receptor(collections.Callable): |
| 25 | + """ |
| 26 | + A receptor is a type of callable used by `InterfaceElement`'s `ife_emit` |
| 27 | + method. |
| 28 | + """ |
| 29 | + |
| 30 | + @abstractmethod |
| 31 | + def __call__(self, |
| 32 | + source_ife : "The element whose `ife_emit` method was called.", |
| 33 | + receiving_ife : "The element that included the `Receptor`('self').", |
| 34 | + obj : "The object that was given to `ife_emit`" |
| 35 | + ) -> bool: |
| 36 | + """ |
| 37 | + This is the type signature of receptor capable functions. |
| 38 | +
|
| 39 | + If the receptor returns `True`, further propagation will be halted if the |
| 40 | + `allow_consumption` parameter given to `ife_emit` is `True`(default). |
| 41 | + """ |
21 | 42 |
|
22 | 43 | class InterfaceElement(metaclass = ABCMeta): |
23 | 44 | """ |
@@ -59,6 +80,14 @@ class InterfaceElement(metaclass = ABCMeta): |
59 | 80 | CURSOR: <cursor_id> |
60 | 81 | <parameters> |
61 | 82 | ERROR: <message> |
| 83 | + |
| 84 | +
|
| 85 | + Receptors |
| 86 | + --------- |
| 87 | +
|
| 88 | + Reception is a faculty created to support PostgreSQL message and warning |
| 89 | + propagation in a context specific way. For instance, the NOTICE emitted by |
| 90 | + PostgreSQL when creating a table with a PRIMARY KEY might be |
62 | 91 | """ |
63 | 92 | @abstractproperty |
64 | 93 | def ife_ancestor(self): |
@@ -106,7 +135,7 @@ def ife_ancestry(self) -> "Sequence of IFE ancestors": |
106 | 135 | if ife in ancestors or ife is self: |
107 | 136 | raise TypeError("recursive element ancestry detected") |
108 | 137 | if isinstance(ife, InterfaceElement): |
109 | | - stack.append(ife) |
| 138 | + ancestors.append(ife) |
110 | 139 | else: |
111 | 140 | break |
112 | 141 | ife = getattr(ife, 'ife_ancestor', None) |
@@ -141,6 +170,62 @@ def ife_descend(self, |
141 | 170 | for x in args: |
142 | 171 | x.ife_ancestor = self |
143 | 172 |
|
| 173 | + def ife_emit(self, |
| 174 | + obj : "object to emit", |
| 175 | + allow_consumption : "whether or not receptors are allowed to stop further propagation" = True, |
| 176 | + ) -> (False, (collections.Callable)): |
| 177 | + """ |
| 178 | + Send an arbitrary object through the ancestry. |
| 179 | +
|
| 180 | + This is used in situations where the effects of an element result in an |
| 181 | + object that is not returned by the element's interaction(method call, |
| 182 | + property get, etc). |
| 183 | +
|
| 184 | + To handle these additional results, the object is passed up through the |
| 185 | + ancestry. Any ancestor that has receptors will see the object |
| 186 | +
|
| 187 | + If `obj` was consumed by a receptor, the receptor that consumed it will be |
| 188 | + returned |
| 189 | + """ |
| 190 | + a = self.ifa_ancestry() |
| 191 | + a.insert(0, self) |
| 192 | + for ife in a: |
| 193 | + for recep in getattr(ife, '_ife_receptors', ()): |
| 194 | + # (emit source element, reception element, object) |
| 195 | + r = recep(self, ife, obj) |
| 196 | + if r is True and allow_consumption: |
| 197 | + # receptor indicated halt |
| 198 | + return (recep, ife) |
| 199 | + return False |
| 200 | + |
| 201 | + def ife_connect(self, |
| 202 | + *args : (Receptor,) |
| 203 | + ) -> None: |
| 204 | + """ |
| 205 | + Add the `Receptor`s to the element. "Connecting" a receptor allows it to |
| 206 | + receive objects "emitted" by descendent elements. |
| 207 | +
|
| 208 | + Whenever an object is given to `ife_emit`, the given `Receptor`s will be |
| 209 | + called with the `obj`. |
| 210 | + """ |
| 211 | + if not hasattr(self, '_ife_receptors'): |
| 212 | + recept = self._ife_receptors = list(args) |
| 213 | + return |
| 214 | + # Prepend the list. Newer receptors are given priority. |
| 215 | + new = list(recept) |
| 216 | + self._ife_receptors = new.extend(self._ife_receptors) |
| 217 | + |
| 218 | + def ife_sever(self, |
| 219 | + *args : (Receptor,) |
| 220 | + ) -> None: |
| 221 | + """ |
| 222 | + Remove the `Receptor`s from the element. |
| 223 | + """ |
| 224 | + if hasattr(self, '_ife_receptors'): |
| 225 | + for x in args: |
| 226 | + if x in self._ife_receptors: |
| 227 | + self._ife_receptors.remove(x) |
| 228 | + |
144 | 229 | class Message(InterfaceElement): |
145 | 230 | "A message emitted by PostgreSQL" |
146 | 231 | ife_label = 'MESSAGE' |
@@ -185,17 +270,20 @@ def __str__(self): |
185 | 270 | ] |
186 | 271 | locstr = ( |
187 | 272 | "" if tuple(loc) == ('?', '?', '?') |
188 | | - else "LOCATION: File {0!r}, line {1!s}, in {2!s}".format(*loc) + os.linesep |
| 273 | + else os.linesep + "LOCATION: File {0!r}, line {1!s}, in {2!s}".format(*loc) |
189 | 274 | ) |
190 | 275 |
|
191 | 276 | sev = details.get('severity') |
| 277 | + sevmsg = os.linesep |
192 | 278 | if sev: |
193 | | - sevmsg = "SEVERITY: " + sev.upper() + os.linesep |
194 | | - return self.message + os.linesep + sevmsg + \ |
195 | | - os.linesep.join(( |
196 | | - ': '.join((k.upper(), v)) for k, v in sorted(details.items(), key = itemgetter(0)) |
197 | | - if k not in ('message', 'severity', 'file', 'function', 'line') |
198 | | - )) + locstr |
| 279 | + sevmsg = os.linesep + "SEVERITY: " + sev.upper() |
| 280 | + detailstr = os.linesep.join(( |
| 281 | + ': '.join((k.upper(), v)) for k, v in sorted(details.items(), key = itemgetter(0)) |
| 282 | + if k not in ('message', 'severity', 'file', 'function', 'line') |
| 283 | + )) |
| 284 | + if detailstr: |
| 285 | + detailstr = os.linesep + detailstr |
| 286 | + return self.message + sevmsg + detailstr + locstr |
199 | 287 |
|
200 | 288 | class Cursor( |
201 | 289 | InterfaceElement, |
@@ -933,29 +1021,39 @@ class Driver(InterfaceElement): |
933 | 1021 | ife_label = "DRIVER" |
934 | 1022 | ife_ancestor = None |
935 | 1023 |
|
936 | | - @abstractproperty |
937 | | - def Connector(self): |
938 | | - """ |
939 | | - The `Connector` implementation for the driver. |
940 | | - """ |
941 | | - |
| 1024 | + @abstractmethod |
942 | 1025 | def connect(**kw): |
943 | 1026 | """ |
944 | 1027 | Create a connection using the given parameters for the Connector. |
945 | 1028 |
|
946 | | - This caches the `Connector` instance for re-use when the same parameters |
| 1029 | + This should cache the `Connector` instance for re-use when the same parameters |
947 | 1030 | are given again. |
948 | 1031 | """ |
949 | | - id = set(kw.items()) |
950 | | - cr = self._connectors.get(id) |
951 | | - if cr is None: |
952 | | - cr = self.Connector(**kw) |
953 | | - c = cr() |
954 | | - self._connectors[id] = cr |
955 | | - return c |
| 1032 | + |
| 1033 | + def print_message(self, msg, file = None): |
| 1034 | + """ |
| 1035 | + Standard message printer. |
| 1036 | + """ |
| 1037 | + file = sys.stderr if not file else file |
| 1038 | + if file and not file.closed: |
| 1039 | + file.write(str(msg)) |
| 1040 | + else: |
| 1041 | + warnings.warn("sys.stderr unavailable for printing messages") |
| 1042 | + |
| 1043 | + def handle_warnings_and_messages(self, source, this, obj): |
| 1044 | + """ |
| 1045 | + Send warnings to `warnings.warn` and print `Message`s to standard error. |
| 1046 | + """ |
| 1047 | + if isinstance(obj, Message): |
| 1048 | + self.print_message(obj) |
| 1049 | + elif isinstance(obj, warnings.Warning): |
| 1050 | + warnings.warn(obj) |
956 | 1051 |
|
957 | 1052 | def __init__(self): |
958 | | - self._connectors = {} |
| 1053 | + """ |
| 1054 | + The driver, by default will emit warnings and messages. |
| 1055 | + """ |
| 1056 | + self.ife_connect(self.handle_warnings_and_messages) |
959 | 1057 |
|
960 | 1058 | class Cluster(InterfaceElement): |
961 | 1059 | """ |
|
0 commit comments