class Session(object): '''The middleware for persistent operations on the back-end. It is created via the :meth:`Router.session` method. .. attribute:: transaction A :class:`Transaction` instance. Not ``None`` if this :class:`Session` is in a :ref:`transactional state <transactional-state>` .. attribute:: router Instance of the :class:`Router` which created this :class:`Session`. ''' def __init__(self, router): self.transaction = None self._models = OrderedDict() self._router = router def __str__(self): return str(self._router) def __repr__(self): return '%s: %s' % (self.__class__.__name__, self._router) def __iter__(self): for sm in self._models.values(): yield sm def __len__(self): return len(self._models) @property def router(self): return self._router @property def dirty(self): '''The set of instances in this :class:`Session` which have been modified.''' return frozenset(chain(*tuple((sm.dirty for sm in itervalues(self._models))))) def begin(self, **options): '''Begin a new :class:`Transaction`. If this :class:`Session` is already in a :ref:`transactional state <transactional-state>`, an error will occur. It returns the :attr:`transaction` attribute. This method is mostly used within a ``with`` statement block:: with session.begin() as t: t.add(...) ... which is equivalent to:: t = session.begin() t.add(...) ... session.commit() ``options`` parameters are passed to the :class:`Transaction` constructor. ''' if self.transaction is not None: raise InvalidTransaction("A transaction is already begun.") else: self.transaction = Transaction(self, **options) return self.transaction def commit(self): """Commit the current :attr:`transaction`. If no transaction is in progress, this method open one. Rarely used directly, see the :meth:`begin` method for details on how to start and close a transaction using the `with` construct. """ if self.transaction is None: self.begin() return self.transaction.commit() def query(self, model, **kwargs): '''Create a new :class:`Query` for *model*.''' sm = self.model(model) query_class = sm.manager.query_class or Query return query_class(sm._meta, self, **kwargs) def empty(self, model): '''Returns an empty :class:`Query` for ``model``.''' return EmptyQuery(self.manager(model)._meta, self) def update_or_create(self, model, **kwargs): '''Update or create a new instance of ``model``. This method can raise an exception if the ``kwargs`` dictionary contains field data that does not validate. :param model: a :class:`StdModel` :param kwargs: dictionary of parameters. :returns: A two elements tuple containing the instance and a boolean indicating if the instance was created or not. ''' backend = self.model(model).backend return backend.execute(self._update_or_create(model, **kwargs)) def add(self, instance, modified=True, **params): '''Add an ``instance`` to the session. If the session is not in a :ref:`transactional state <transactional-state>`, this operation commits changes to the back-end server immediately. :parameter instance: a :class:`Model` instance. It must be registered with the :attr:`router` which created this :class:`Session`. :parameter modified: a boolean flag indicating if the instance was modified. :return: the ``instance``. If the instance is persistent (it is already stored in the database), an updated will be performed, otherwise a new entry will be created once the :meth:`commit` method is invoked. ''' sm = self.model(instance) instance.session = self o = sm.add(instance, modified=modified, **params) if modified and not self.transaction: transaction = self.begin() return transaction.commit(lambda: o) else: return o def delete(self, instance_or_query): '''Delete an ``instance`` or a ``query``. Adds ``instance_or_query`` to this :class:`Session` list of data to be deleted. If the session is not in a :ref:`transactional state <transactional-state>`, this operation commits changes to the backend server immediately. :parameter instance_or_query: a :class:`Model` instance or a :class:`Query`. ''' sm = self.model(instance_or_query) # not an instance of a Model. Assume it is a query. if is_query(instance_or_query): if instance_or_query.session is not self: raise ValueError('Adding a query generated by another session') sm._delete_query.append(instance_or_query) else: instance_or_query = sm.delete(instance_or_query, self) if not self.transaction: transaction = self.begin() return transaction.commit( lambda: transaction.deleted.get(sm._meta)) else: return instance_or_query def flush(self, model): '''Completely flush a :class:`Model` from the database. No keys associated with the model will exists after this operation.''' return self.model(model).flush() def clean(self, model): '''Remove empty keys for a :class:`Model` from the database. No empty keys associated with the model will exists after this operation.''' return self.model(model).clean() def keys(self, model): '''Retrieve all keys for a *model*.''' return self.model(model).keys() def __contains__(self, instance): sm = self.model(instance, False) return instance in sm if sm is not None else False def model(self, model, create=True): '''Returns the :class:`SessionModel` for ``model`` which can be :class:`Model`, or a :class:`MetaClass`, or an instance of :class:`Model`.''' manager = self.manager(model) sm = self._models.get(manager) if sm is None and create: sm = SessionModel(manager) self._models[manager] = sm return sm def expunge(self, instance=None): '''Remove ``instance`` from this :class:`Session`. If ``instance`` is not given, it removes all instances from this :class:`Session`.''' if instance is not None: sm = self._models.get(instance._meta) if sm: return sm.expunge(instance) else: self._models.clear() def manager(self, model): '''Retrieve the :class:`Manager` for ``model`` which can be any of the values valid for the :meth:`model` method.''' try: return self.router[model] except KeyError: meta = getattr(model, '_meta', model) if meta.type == 'structure': # this is a structure if hasattr(model, 'model'): structure_model = model.model if structure_model: return self.manager(structure_model) else: manager = self.router.structure(model) if manager: return manager raise InvalidTransaction('"%s" not valid in this session' % meta) def backends_data(self): backends = {} for sm in self: for backend, data in sm.backends_data(self): be = backends.get(backend) if be is None: backends[backend] = be = [] be.append(data) return backends.items() ####################################################################### # INTERNALS def _update_or_create(self, model, **kwargs): pkname = model._meta.pkname() pk = kwargs.pop(pkname, None) query = self.query(model) item = None # if pk: # primary key available items = yield query.filter(pkname=pk).all() if items: item = items[0] else: kwargs[pkname] = pk else: params = {} rest = {} fields = model._meta.dfields for field, value in iteritems(kwargs): if field in fields and fields[field].index: params[field] = value else: rest[field] = value if params: items = yield query.filter(**params).all() if len(items) == 1: item = items[0] kwargs = rest if item: if kwargs: for field, value in iteritems(kwargs): setattr(item, field, value) item = yield self.add(item) yield item, False # else: item = yield self.add(model(**kwargs)) yield item, True
class Session(object): '''The middleware for persistent operations on the back-end. It is created via the :meth:`Router.session` method. .. attribute:: transaction A :class:`Transaction` instance. Not ``None`` if this :class:`Session` is in a :ref:`transactional state <transactional-state>` .. attribute:: router Instance of the :class:`Router` which created this :class:`Session`. ''' def __init__(self, router): self.transaction = None self._models = OrderedDict() self._router = router def __str__(self): return str(self._router) def __repr__(self): return '%s: %s' % (self.__class__.__name__, self._router) def __iter__(self): for sm in self._models.values(): yield sm def __len__(self): return len(self._models) @property def router(self): return self._router @property def dirty(self): '''The set of instances in this :class:`Session` which have been modified.''' return frozenset( chain(*tuple((sm.dirty for sm in itervalues(self._models))))) def begin(self, **options): '''Begin a new :class:`Transaction`. If this :class:`Session` is already in a :ref:`transactional state <transactional-state>`, an error will occur. It returns the :attr:`transaction` attribute. This method is mostly used within a ``with`` statement block:: with session.begin() as t: t.add(...) ... which is equivalent to:: t = session.begin() t.add(...) ... session.commit() ``options`` parameters are passed to the :class:`Transaction` constructor. ''' if self.transaction is not None: raise InvalidTransaction("A transaction is already begun.") else: self.transaction = Transaction(self, **options) return self.transaction def commit(self): """Commit the current :attr:`transaction`. If no transaction is in progress, this method open one. Rarely used directly, see the :meth:`begin` method for details on how to start and close a transaction using the `with` construct. """ if self.transaction is None: self.begin() return self.transaction.commit() def query(self, model, **kwargs): '''Create a new :class:`Query` for *model*.''' sm = self.model(model) query_class = sm.manager.query_class or Query return query_class(sm._meta, self, **kwargs) def empty(self, model): '''Returns an empty :class:`Query` for ``model``.''' return EmptyQuery(self.manager(model)._meta, self) def update_or_create(self, model, **kwargs): '''Update or create a new instance of ``model``. This method can raise an exception if the ``kwargs`` dictionary contains field data that does not validate. :param model: a :class:`StdModel` :param kwargs: dictionary of parameters. :returns: A two elements tuple containing the instance and a boolean indicating if the instance was created or not. ''' backend = self.model(model).backend return backend.execute(self._update_or_create(model, **kwargs)) def add(self, instance, modified=True, **params): '''Add an ``instance`` to the session. If the session is not in a :ref:`transactional state <transactional-state>`, this operation commits changes to the back-end server immediately. :parameter instance: a :class:`Model` instance. It must be registered with the :attr:`router` which created this :class:`Session`. :parameter modified: a boolean flag indicating if the instance was modified. :return: the ``instance``. If the instance is persistent (it is already stored in the database), an updated will be performed, otherwise a new entry will be created once the :meth:`commit` method is invoked. ''' sm = self.model(instance) instance.session = self o = sm.add(instance, modified=modified, **params) if modified and not self.transaction: transaction = self.begin() return transaction.commit(lambda: o) else: return o def delete(self, instance_or_query): '''Delete an ``instance`` or a ``query``. Adds ``instance_or_query`` to this :class:`Session` list of data to be deleted. If the session is not in a :ref:`transactional state <transactional-state>`, this operation commits changes to the backend server immediately. :parameter instance_or_query: a :class:`Model` instance or a :class:`Query`. ''' sm = self.model(instance_or_query) # not an instance of a Model. Assume it is a query. if is_query(instance_or_query): if instance_or_query.session is not self: raise ValueError('Adding a query generated by another session') sm._delete_query.append(instance_or_query) else: instance_or_query = sm.delete(instance_or_query, self) if not self.transaction: transaction = self.begin() return transaction.commit( lambda: transaction.deleted.get(sm._meta)) else: return instance_or_query def flush(self, model): '''Completely flush a :class:`Model` from the database. No keys associated with the model will exists after this operation.''' return self.model(model).flush() def clean(self, model): '''Remove empty keys for a :class:`Model` from the database. No empty keys associated with the model will exists after this operation.''' return self.model(model).clean() def keys(self, model): '''Retrieve all keys for a *model*.''' return self.model(model).keys() def __contains__(self, instance): sm = self.model(instance, False) return instance in sm if sm is not None else False def model(self, model, create=True): '''Returns the :class:`SessionModel` for ``model`` which can be :class:`Model`, or a :class:`MetaClass`, or an instance of :class:`Model`.''' manager = self.manager(model) sm = self._models.get(manager) if sm is None and create: sm = SessionModel(manager) self._models[manager] = sm return sm def expunge(self, instance=None): '''Remove ``instance`` from this :class:`Session`. If ``instance`` is not given, it removes all instances from this :class:`Session`.''' if instance is not None: sm = self._models.get(instance._meta) if sm: return sm.expunge(instance) else: self._models.clear() def manager(self, model): '''Retrieve the :class:`Manager` for ``model`` which can be any of the values valid for the :meth:`model` method.''' try: return self.router[model] except KeyError: meta = getattr(model, '_meta', model) if meta.type == 'structure': # this is a structure if hasattr(model, 'model'): structure_model = model.model if structure_model: return self.manager(structure_model) else: manager = self.router.structure(model) if manager: return manager raise InvalidTransaction('"%s" not valid in this session' % meta) def backends_data(self): backends = {} for sm in self: for backend, data in sm.backends_data(self): be = backends.get(backend) if be is None: backends[backend] = be = [] be.append(data) return backends.items() ####################################################################### # INTERNALS def _update_or_create(self, model, **kwargs): pkname = model._meta.pkname() pk = kwargs.pop(pkname, None) query = self.query(model) item = None # if pk: # primary key available items = yield query.filter(pkname=pk).all() if items: item = items[0] else: kwargs[pkname] = pk else: params = {} rest = {} fields = model._meta.dfields for field, value in iteritems(kwargs): if field in fields and fields[field].index: params[field] = value else: rest[field] = value if params: items = yield query.filter(**params).all() if len(items) == 1: item = items[0] kwargs = rest if item: if kwargs: for field, value in iteritems(kwargs): setattr(item, field, value) item = yield self.add(item) yield item, False # else: item = yield self.add(model(**kwargs)) yield item, True
class Session(object): '''The manager of persistent operations on the backend data server for :class:`StdModel` classes. .. attribute:: backend the :class:`stdnet.BackendDataServer` instance .. attribute:: autocommit When ``True``, the :class:`Session`` does not keep a persistent transaction running, and will acquire connections from the engine on an as-needed basis, returning them immediately after their use. Flushes will begin and commit (or possibly rollback) their own transaction if no transaction is present. When using this mode, the :meth:`begin` method may be used to begin a transaction explicitly. Default: ``False``. .. attribute:: transaction A :class:`Transaction` instance. Not ``None`` if this :class:`Session` is in a transactional state. .. attribute:: query_class class for querying. Default is :class:`Query`. ''' _structures = {} def __init__(self, backend, autocommit = False, query_class = None): self.backend = getdb(backend) self.transaction = None self.autocommit = autocommit self._new = OrderedDict() self._deleted = OrderedDict() self._modified = OrderedDict() self._loaded = {} self.query_class = query_class or Query @property def new(self): "The set of all new instances within this ``Session``" return frozenset(self._new.values()) @property def modified(self): "The set of all modified instances within this ``Session``" return frozenset(self._modified.values()) @property def deleted(self): "The set of all instances marked as 'deleted' within this ``Session``" return frozenset(self._loaded.values()) @property def deleted(self): "The set of all instances marked as 'deleted' within this ``Session``" return frozenset(self._deleted.values()) def __str__(self): return str(self.backend) def __repr__(self): return '{0}({1})'.format(self.__class__.__name__,self) def begin(self): '''Begin a new class:`Transaction`. If this :class:`Session` is already within a transaction, an error is raised.''' if self.transaction is not None: raise InvalidTransaction("A transaction is already begun.") else: self.transaction = Transaction(self) return self.transaction def query(self, model): '''Create a new :class:`Query` for *model*.''' return self.query_class(model._meta, self) def get_or_create(self, model, **kwargs): '''Get an instance of *model* from the internal cache (only if the dictionary *kwargs* is of length 1 and has key given by ``id``) or from the server. If it the instance is not available, it tries to create one from the **kwargs** parameters. :parameter model: a :class:`StdModel` :parameter kwargs: dictionary of parameters. :rtype: an instance of two elements tuple containing the instance and a boolean indicating if the instance was created or not. ''' try: res = self.query(model).get(**kwargs) created = False except model.DoesNotExist: res = self.add(model(**kwargs)) created = True return res,created def get(self, model, id): uuid = model.get_uuid(id) if uuid in self._modified: return self._modified.get(uuid) elif uuid in self._deleted: return self._modified.get(uuid) def add(self, instance, modified = True): '''Add an *instance* to the session. :parameter instance: a class:`StdModel` or a :class:`Structure` instance. :parameter modified: a boolean flag indictaing if the instance was modified. ''' state = instance.state() if state.deleted: raise ValueError('State is deleted. Cannot add.') instance.session = self if state.persistent: if modified: self._loaded.pop(state,None) self._modified[instance] = instance elif state not in self._modified: self._loaded[instance] = instance else: self._new[instance] = instance return instance def delete(self, instance): self.expunge(instance) if instance.state().persistent: self._deleted[instance] = instance def flush(self, model): return self.backend.flush(model._meta) def __contains__(self, instance): return instance in self._new or state in self._deleted\ or state in self._modified def __iter__(self): """Iterate over all pending or persistent instances within this Session.""" for v in self._new: yield v for m in self._modified: yield m for d in self._deleted: yield d def commit(self): """Flush pending changes and commit the current transaction. If no transaction is in progress, this method raises an InvalidRequestError. By default, the :class:`.Session` also expires all database loaded state on all ORM-managed attributes after transaction commit. This so that subsequent operations load the most recent data from the database. This behavior can be disabled using the ``expire_on_commit=False`` option to :func:`.sessionmaker` or the :class:`.Session` constructor. If a subtransaction is in effect (which occurs when begin() is called multiple times), the subtransaction will be closed, and the next call to ``commit()`` will operate on the enclosing transaction. For a session configured with autocommit=False, a new transaction will be begun immediately after the commit, but note that the newly begun transaction does *not* use any connection resources until the first SQL is actually emitted. """ if self.transaction is None: if not self.autocommit: self.begin() else: raise InvalidTransaction('No transaction was started') self.transaction.commit() def expunge(self, instance): instance._dbdata.pop('state',None) for d in (self._new,self._modified,self._loaded,self._deleted): if instance in d: d.pop(instance) return True return False def server_update(self, instance, id = None): '''Callback by the :class:`stdnet.BackendDataServer` once the commit is finished. Remove the deleted instances and updated the modified and new instances.''' state = instance.state() self.expunge(instance) if not state.deleted: if id: instance.id = instance._meta.pk.to_python(id) instance._dbdata['id'] = instance.id self.add(instance, False) return instance @classmethod def clearall(cls): pass
class Session(object): '''The manager of persistent operations on the backend data server for :class:`StdModel` classes. .. attribute:: backend the :class:`stdnet.BackendDataServer` instance .. attribute:: transaction A :class:`Transaction` instance. Not ``None`` if this :class:`Session` is in a transactional state. .. attribute:: query_class class for querying. Default is :class:`Query`. ''' _structures = {} def __init__(self, backend, query_class=None): self.backend = getdb(backend) self.transaction = None self._models = OrderedDict() self.query_class = query_class or Query def __str__(self): return str(self.backend) def __repr__(self): return '{0}({1})'.format(self.__class__.__name__,self) def __iter__(self): for sm in self._models.values(): yield sm def __len__(self): return len(self._models) @property def dirty(self): '''set of all changed instances in the session''' return frozenset(chain(*tuple((sm.dirty for sm\ in itervalues(self._models))))) def session(self): '''Create a new session from this :class:`Session`''' return self.__class__(self.backend,self.query_class) def model(self, meta): if hasattr(meta, '_meta'): meta = meta._meta sm = self._models.get(meta) if sm is None: if meta.model._model_type == 'structure': sm = SessionStructure(meta, self) else: sm = SessionModel(meta, self) self._models[meta] = sm return sm def expunge(self, instance = None): if instance is not None: sm = self._models.get(instance._meta) if sm: return sm.expunge(instance) else: self._models.clear() def begin(self, **options): '''Begin a new :class:`Transaction`. If this :class:`Session` is already within a transaction, an error is raised. It returns the :attr:`transaction` attribute. It can be used in a `with` construct:: with session.begin() as t: t.add(... ... which is equivalent to:: t = session.begin() t.add(... ... session.commit() `options` parameter are passed to the :class:`Transaction` constructor. ''' if self.transaction is not None: raise InvalidTransaction("A transaction is already begun.") else: self.transaction = Transaction(self, **options) return self.transaction def commit(self): """Commit the current :attr:`transaction`. If no transaction is in progress, this method open one. Rarely used directly, see the :meth:`begin` method for details on how to start and close a transaction using the `with` construct.""" if self.transaction is None: self.begin() return self.transaction.commit() def query(self, model, query_class=None, **kwargs): '''Create a new :class:`Query` for *model*.''' query_class = query_class or self.query_class return query_class(model._meta, self, **kwargs) def empty(self, model): return EmptyQuery(model._meta, self) def get_or_create(self, model, **kwargs): '''Get an instance of *model* from the internal cache (only if the dictionary *kwargs* is of length 1 and has key given by ``id``) or from the server. If it the instance is not available, it tries to create one from the **kwargs** parameters. :parameter model: a :class:`StdModel` :parameter kwargs: dictionary of parameters. :rtype: an instance of two elements tuple containing the instance and a boolean indicating if the instance was created or not. ''' try: res = self.query(model).get(**kwargs) created = False except model.DoesNotExist: res = self.add(model(**kwargs)) created = True return res,created def get(self, model, id): sm = self._models.get(model._meta) if sm: return sm.get(id) @commit_element_when_no_transaction def add(self, instance, modified=True): '''Add an *instance* to the session. :parameter instance: a class:`StdModel` or a :class:`Structure` instance. :parameter modified: a boolean flag indicating if the instance was modified. ''' sm = self.model(instance._meta) instance.session = self return sm.add(instance, modified) @commit_element_when_no_transaction def delete(self, instance, **kwargs): '''Add an *instance* to the session instances to be deleted. :parameter instance: a class:`StdModel` or a :class:`Structure` instance. ''' sm = self.model(instance._meta) # not an instance of a Model. Assume it is a query. if is_query(instance): if instance.session is not self: raise ValueError('Adding a query generated by another session') sm._delete_query.append(instance) return instance else: return sm.delete(instance, self) def flush(self, model): '''Completely flush a :class:`Model` from the database. No keys associated with the model will exists after this operation.''' return self.backend.flush(model._meta) def clean(self, model): '''Remove empty keys for a :class:`Model` from the database. No empty keys associated with the model will exists after this operation.''' return self.backend.clean(model._meta) def keys(self, model): '''Retrieve all keys for a *model*.''' return self.backend.model_keys(model._meta) def __contains__(self, instance): sm = self._models.get(instance._meta) return instance in sm if sm is not None else False def structure(self, instance): '''Return a :class:`stdnet.BackendStructure` for a given :class:`Structure` *instance*.''' return self.backend.structure(instance) @classmethod def clearall(cls): pass