83 lines
4.0 KiB
Python
83 lines
4.0 KiB
Python
import logging
|
|
from contextlib import contextmanager
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class Engine:
|
|
'''
|
|
Engine base class. Used primarily as a Database connection manager, with general
|
|
methods that can be defined for several kinds of value stores.
|
|
|
|
Note that this is where the connection hierarchy is supposed to stop. While some
|
|
derivative Engines, like SQLEngine, mostly just wrap another engine-like object, this
|
|
is not the rule. That is, inheriting Engine subtypes shouldn't necessarily expect to
|
|
rely on another object per se, and if such an object is required, _this_ is the class
|
|
meant to be the skeleton that supports its creation (and not merely a wrapper for some
|
|
other type, although it may appear that way when such a type is in fact readily
|
|
available).
|
|
|
|
.. admonition:: why is this object necessary?
|
|
|
|
More specifically, why not just have all the functionality here packed into the
|
|
Database by default? The answer is that, realistically, it could be. The type
|
|
separation between the Engine and Database is perhaps the least substantiated in
|
|
CO3. That being said, it still serves a purpose: to make composition of subtypes
|
|
easier. The Engine is a very lightweight abstraction, but some Engine subtypes
|
|
(e.g., FileEngines) may be used across several sibling Database types. In this
|
|
case, we'd have to repeat the Engine-related functionality for such sibling types.
|
|
Depending instead on a singular, outside object keeps things DRY. If Databases and
|
|
Engines were uniquely attached type-wise 1-to-1 (i.e., unique Engine type per
|
|
unique Database type), a separate object here would indeed be a waste, as is the
|
|
case for any compositional typing scheme.
|
|
|
|
.. admonition:: dev note
|
|
|
|
This class is now non-generic. It was originally conceived as a generic, depending
|
|
on a "resource spec type" to be help define expected types on initialization.
|
|
This simply proved too messy, required generic type propagation to the Database
|
|
definition, and muddied the otherwise simple args and kwargs forwarding for
|
|
internal manager creation.
|
|
'''
|
|
def __init__(self, *manager_args, **manager_kwargs):
|
|
self._manager = None
|
|
self._manager_args = manager_args
|
|
self._manager_kwargs = manager_kwargs
|
|
|
|
@property
|
|
def manager(self):
|
|
'''
|
|
Return Engine's singleton manager, initializing when the first call is made.
|
|
'''
|
|
if self._manager is None:
|
|
self._manager = self._create_manager()
|
|
|
|
return self._manager
|
|
|
|
def _create_manager(self):
|
|
'''
|
|
Create the session manager needed for connection contexts. This method is called
|
|
once by the ``.manager`` property function when it is first accessed. This method is
|
|
separated to isolate the creation logic in inheriting types.
|
|
|
|
Note that this method takes no explicit arguments. This is primarily because the
|
|
standard means of invocation (the manager property) is meant to remain generally
|
|
useful here in the base class, and can't be aware of any specific properties that
|
|
might be extracted in subtype initialization. As a result, we don't even try to
|
|
pass args, although it would just look like a forwarding of the readily manager
|
|
args and kwargs anyhow. As such, this method should make direct use of these
|
|
instance variables as needed.
|
|
'''
|
|
raise NotImplementedError
|
|
|
|
@contextmanager
|
|
def connect(self, timeout=None):
|
|
'''
|
|
Open a connection to the database specified by the resource. Exactly what the
|
|
returned connection looks like remains relatively unconstrained given the wide
|
|
variety of possible database interactions. This function should be invoked in
|
|
with-statement contexts, constituting an "interaction session" with the database
|
|
(i.e., allowing several actions to be performed using the same connection).
|
|
'''
|
|
raise NotImplementedError
|