def __init__(self, name, ANS, Root_Type_Name=None): assert bool(name) assert name not in self.Table self.Table[name] = self self.name = name self.ANS = ANS self.Root_Type_Name = Root_Type_Name self.derived = {} self.init_callback = TFL.Ordered_Set() self.kill_callback = TFL.Ordered_Set() self.PNS_Map = {} self.PNS_Set = {}
def __init__(self, sync_dir, mode, default_mode, bufsize, backup_name): self.sync_dir = sync_dir self.name = sync_dir.file_name self.stamp = None self.default_mode = default_mode + mode[1:] self.bufsize = bufsize self.last_file = None self.files = TFL.Ordered_Set() if backup_name: self.backup_name = Filename \ (backup_name, self.sync_dir.file_name).name else: self.backup_name = None
def __init__(self, parent, EMS, DBW): assert parent self.name = "__".join \ ((parent.name, EMS.type_name, DBW.type_name)) self.ANS = parent.ANS self.Root_Type_Name = parent.Root_Type_Name self.EMS = EMS self.DBW = DBW self.etypes = {} self.etypes_by_pns = TFL.mm_list() self._T_Extension = [] self.derived = None self.parent = parent self.init_callback = TFL.Ordered_Set() self.kill_callback = TFL.Ordered_Set() self.PNS_Map = parent.PNS_Map self.PNS_Set = parent.PNS_Set self.surrogate_map = {} self.surrogate_t_map = {} self.finalized = False MOM.Entity.m_setup_etypes(self) self.finalized = True
class Scope(TFL.Meta.Object): active = None ilk = "S" playback_p = False Table = {} after_commit_callback = TFL.Ordered_Set() init_callback = TFL.Ordered_Set() kill_callback = TFL.Ordered_Set() _cleaned_url = Re_Replacer(r"(://\w+:)(\w+)@", r"\1<elided>@") _deprecated_type_names = {} _pkg_ns = None __id = 0 root = None _roots = None changes = property(TFL.Getter.historian.total_changes) changes_to_save = property \ (lambda s : len (s.ems.uncommitted_changes)) db_meta_data = property(TFL.Getter.ems.db_meta_data) etypes = property(TFL.Getter.app_type.etypes) Fatal_Exceptions = property(TFL.Getter.ems.pm.dbs.Fatal_Exceptions) max_cid = property(TFL.Getter.ems.max_cid) max_pid = property(TFL.Getter.ems.max_pid) max_surrs = property(TFL.Getter.ems.max_surrs) name = property(lambda s: s.qname) readonly = property(TFL.Getter.ems.db_meta_data.readonly) reserve_surrogates = True T_Extension = property(TFL.Getter.app_type._T_Extension) uncommitted_changes = property(TFL.Getter.ems.uncommitted_changes) PNS_Proxy = None @property def qname(self): return self.bname or self.app_type.name # end def qname @TFL.Meta.Once_Property def relevant_roots(self): """Return the relevant roots of the application.""" Top = self.MOM.Id_Entity.E_Type return sorted \ (pyk.itervalues (Top.relevant_roots), key = Top.m_sorted_by) # end def relevant_roots @property def root_pid(self): try: return self.db_meta_data.root_pid except AttributeError: pass # end def root_pid @root_pid.setter def root_pid(self, value): root_pid = self.root_pid if not (root_pid is None or root_pid == value): raise TypeError \ ( "Root pid of scope %s was already set to %r; " "cannot change to %r" % (self, root_pid, value) ) try: dbmd = self.db_meta_data except AttributeError: self._root_pid = value else: dbmd.root_pid = value # end def root_pid @TFL.Meta.Once_Property def Root_Type(self): RTN = self.app_type.Root_Type_Name if RTN: return self.etypes[RTN] # end def Root_Type @TFL.Meta.Once_Property def _Example(self): return _Example_(self) # end def _Example class Pkg_NS: """Just a container for the scope-specific etype-proxies for the essential classes of a package-namespace (delegates everything else to the original package namespace). """ def __init__(self, scope, pns, qn): self._scope = scope self._pns = pns self._qname = qn # end def __init__ def __getattr__(self, name): if name.startswith("__") and name.endswith("__"): ### Placate inspect.unwrap of Python 3.5, ### which accesses `__wrapped__` and eventually throws ### `ValueError` return getattr(self.__super, name) scope = self._scope etypes = scope.etypes pkg_ns = scope._pkg_ns qname = ".".join((self._qname, name)) result = None if qname in pkg_ns: result = pkg_ns[qname] elif qname in etypes: etype = etypes[qname] result = scope._etm[qname] = etype.Manager(etype, scope) setattr(self, name, result) else: result = getattr(self._pns, name) return result # end def __getattr__ # end class Pkg_NS ### Scope creation methods @classmethod def load(cls, app_type, db_url, user=None): """Load a scope for `app_type` from `db_url`. Depending on `app_type.EMS`, `load` might load all instances from `db_url` into the application or it might just connect to the database and load instances on demand in answer to queries. """ db_url = app_type.Url(db_url) with cls._init_context(app_type, db_url, user) as self: app_type = self.app_type self.ems = ems = app_type.EMS.connect(self, db_url) with self._init_root_context(): self._register_root(ems.load_root()) return self # end def load @classmethod def new(cls, app_type, db_url, user=None, root_spec=None): """Create a scope for `app_type` for a new database `db_url`. If `app_type` has a :attr:`~_MOM.App_Type.App_Type.Root_Type`, `new` requires a `root_spec` passed in. `root_spec` must be one of: * a proper epk-tuple for :attr:`~_MOM.App_Type.App_Type.Root_Type` * a callable that takes the scope as its single parameter and returns the root object. """ db_url = app_type.Url(db_url) with cls._init_context(app_type, db_url, user, root_spec) as self: app_type = self.app_type self.guid = self._new_guid() self.ems = ems = app_type.EMS.new(self, db_url) with self._init_root_context(root_spec): self._setup_root(app_type, root_spec) ems.register_scope() return self # end def new @classmethod @TFL.Contextmanager def _init_context(cls, app_type, db_url, user, root_spec=None): self = cls.__new__(cls) self.app_type = app_type self.db_url = db_url self.user = user self.bname = "" self.id = self._new_id() self.root = None self.historian = MOM.SCM.Tracker(self) self.db_errors = [] self._attr_errors = [] self._etm = {} self._roots = {} self._setup_pkg_ns(app_type) ### copy `*_callback` from cls to self self.after_commit_callback = self.after_commit_callback.copy() self.init_callback = self.init_callback.copy() self.kill_callback = self.kill_callback.copy() old_active = Scope.active try: Scope.active = self yield self self._run_init_callbacks() self.start_change_recorder() Scope.Table[self.id] = self except: Scope.active = old_active raise # end def _init_context @TFL.Contextmanager def _init_root_context(self, root_spec=None): yield # end def _init_root_context def __init__(self): raise TypeError \ ( "Use {name}.new or {name}.load to create new scopes".format (name = self.__class__.__name__) ) # end def __init__ ### Scope methods def add(self, entity, pid=None): """Adds `entity` to scope `self`.""" if entity._home_scope is None: entity.home_scope = self with self.ems.save_point(): self.ems.add(entity, pid=pid) if not entity.init_finished: entity._finish__init__() self.record_change(MOM.SCM.Change.Create, entity) # end def add def add_from_pickle_cargo(self, type_name, cargo, pid): """Add an entity defined by (`type_name`, `pid`, `cargo`).""" Type = self.entity_type(type_name) if Type: try: result = Type.from_attr_pickle_cargo(self, cargo) except Exception as exc: self.db_errors.append((type_name, pid, cargo)) if __debug__: traceback.print_exc() print(repr(exc), file=sys.stderr) print \ ( " add_from_pickle_cargo: couldn't restore" " %s %s %s (app-type %s)" % (type_name, pid, cargo, self.app_type) , file = sys.stderr ) else: self.ems.add(result, pid=pid) if pid == self.root_pid: self._register_root(result) if not result.init_finished: result._finish__init__() return result # end def add_from_pickle_cargo @TFL.Meta.Class_and_Instance_Method def add_after_commit_callback(soc, *callbacks): """Add all `callbacks` to `after_commit_callback`. These callbacks are executed after each `.commit` of a scope (the scope and the MOM.SCM.Summary instance of the just commited changes are passed as arguments to each callback). """ soc.after_commit_callback.extend(callbacks) # end def add_after_commit_callback @TFL.Meta.Class_and_Instance_Method def add_init_callback(soc, *callbacks): """Add all `callbacks` to `init_callback`. These callbacks are executed whenever a scope is created (the new scope is passed as the single argument to each callback). """ soc.init_callback.extend(callbacks) # end def add_init_callback @TFL.Meta.Class_and_Instance_Method def add_kill_callback(soc, *callbacks): """Add all `callbacks` to `kill_callback` of the scope class or instance. These callbacks` are executed whenever the scope is destroyed (the scope to be destroyed is passed as the single argument to each callback). """ soc.kill_callback.extend(callbacks) # end def add_kill_callback @TFL.Contextmanager def as_active(self): """Provide context with `self` as active scope.""" with Scope.LET(active=self): yield # end def as_active def canonical_type_name(self, type_name): return self._deprecated_type_names.get(type_name, type_name) # end def canonical_type_name def commit(self): """Commit all outstanding changes to the database.""" ems = self.ems ucc = ems.uncommitted_changes with ems.commit_context(): if ucc: errs = self.r_incorrect(eiter=ucc.entities_transitive(ems)) if errs: exc = MOM.Error.Invariants(errs.errors) raise exc ems.commit() for cb in self.after_commit_callback: cb(self, ucc) # end def commit def copy(self, app_type, db_url): """Copy all entities and change-history of `self` into a new scope for `app_type` and `db_url`. """ assert self.app_type.parent is app_type.parent db_url = app_type.Url(db_url) assert (db_url is None or not db_url.path or self.db_url.path != db_url.path) with self.as_active(): result = self.__class__.new(app_type, db_url, user=self.user) result.root_pid = self.root_pid for e in sorted(self, key=TFL.Getter.pid): result.add_from_pickle_cargo(*e.as_pickle_cargo()) for c in self.query_changes().order_by(TFL.Sorted_By("cid")): result.ems.register_change(c) result.ems.compact() return result # end def copy def count_change(self): self.historian.count_change() # end def count_change def close_connections(self): self.ems.close_connections() # end def close_connections def destroy(self): """Close connection to database and destroy all cached instances.""" self.ems.close() if self.id in Scope.Table: del Scope.Table[self.id] self.stop_change_recorder() self.app_type.run_kill_callbacks(self) for c in self.kill_callback: c(self) del self.kill_callback self.root = None for d in (self._roots, self._pkg_ns): d.clear() ### XXX break circular links (references to this scope from ### importers... ) if Scope.active == self: Scope.active = None self.__dict__.clear() # end def destroy @classmethod def destroy_all(cls): """Destroy all scopes.""" for i, s in sorted(pyk.iteritems(Scope.Table), reverse=True): try: s.destroy() except Exception: pass # end def destroy_all def entity_iter(self): """Yields all objects and links alive in `self` in unspecified order. """ return iter(self.ems) # end def entity_iter def entity_iter_gauge(self, gauge=Gauge_Logger(), sort_key=None, label=None): """Yields all entities alive in `self` in the order specified by `sort_key`. """ gauge.reset \ ( g_delta = 100 , g_range = self.ems.count (self.MOM.Id_Entity, strict = False) , label = label ) entities = iter(self.ems) if sort_key: entities = sorted(entities, key=sort_key) i = 1 for e in entities: yield e if i == 100: gauge.inc(100) i = 0 i += 1 # end def entity_iter_gauge def entity_type(self, entity): """Return scope specific entity type for `entity` (-name).""" if isinstance(entity, pyk.string_types): name = entity else: name = entity.type_name return self.app_type.entity_type(self.canonical_type_name(name)) # end def entity_type @TFL.Contextmanager def example_etm(self, etm): """Return a E_Type_Manager for the E_Type of `etm` in an example scope.""" with self._Example.context(self) as x_scope: x_etm = x_scope[etm.type_name] yield x_etm # end def example_etm @TFL.Meta.Lazy_Method_RLV def g_incorrect(self, gauge=Gauge_Logger()): """Returns all objects which are globally incorrect (i.e., violating the object's `system` predicates). """ with self.as_active(): return self._check_inv(gauge, "system") # end def g_incorrect def has_changed(self): """Indicates whether something saveworthy has changed, i.e., there if there are outstanding changes to be commited. """ return bool(self.ems.uncommitted_changes) # end def has_changed @TFL.Meta.Lazy_Method_RLV def i_incorrect(self, gauge=Gauge_Logger()): """Returns all objects which are object-wise incorrect (i.e., violating the object's `object` predicates). """ with self.as_active(): return self._check_inv(gauge, "object") # end def i_incorrect @TFL.Contextmanager def nested_change_recorder(self, Change, *args, **kw): """Return context with `Change (* args, ** kw)` acting as nested change recorder. """ with self.historian.nested_recorder(Change, *args, **kw) as c: yield c if c: c.user = self.user self.ems.register_change(c) c.do_callbacks(self) # end def nested_change_recorder def pid_query(self, pid, allow_zombie=False): """Returns entity with permanent id `pid`, if any.""" result = self.ems.pid_query(pid) if (not allow_zombie and isinstance(result, MOM._Id_Entity_Destroyed_Mixin_)): raise LookupError(pid) return result # end def pid_query def query_changes(self, *filter, **kw): """Return changes matching `filter` and `kw`""" return self.ems.changes(*filter, **kw) # end def query_changes @TFL.Meta.Lazy_Method_RLV def r_incorrect(self, gauge=Gauge_Logger(), eiter=None): """Returns all objects which are region-wise incorrect (i.e., violating the object's `region` predicates). """ with self.as_active(): return self._check_inv(gauge, "region", eiter) # end def i_incorrect def record_change(self, Change, *args, **kw): """Record the `Change` specified by `args` and `kw`""" with self.ems.save_point(): result = self.historian.record(Change, *args, **kw) if result is not None: result.user = self.user self.ems.register_change(result) result.do_callbacks(self) return result # end def record_change def remove(self, entity): """Remove `entity` from scope `self`""" assert (entity != self.root) Change = MOM.SCM.Change.Destroy with self.nested_change_recorder(Change, entity): entity._destroy() self.ems.remove(entity) # end def remove def rename(self, entity, new_epk, renamer): self.ems.rename(entity, new_epk, renamer) # end def rename def rollback(self, keep_zombies=False): """Rollback and discard the outstanding changes.""" self.ems.rollback(keep_zombies) self.count_change() # end def rollback def rollback_pending_change(self): """Rollback the last, not yet recorded, change but keep all earlier outstanding changes. """ changes = tuple(self.uncommitted_changes.changes) self.rollback(keep_zombies=True) for c in changes: c.redo(self) # end def rollback_pending_change def start_change_recorder(self): if not self.historian._rec_stack: self.historian.push_recorder(MOM.SCM.Tracker.Preferred_Recorder) # end def start_change_recorder def stop_change_recorder(self): if self.historian._rec_stack: self.historian.pop_recorder() # end def stop_change_recorder @TFL.Contextmanager def temp_change_recorder(self, Recorder): with self.historian.temp_recorder(Recorder): yield # end def temp_change_recorder def user_diff(self, other, ignore=()): """Return differences of entities `self` and `other` concerning user attributes.""" result = {} seen = set() def diff(lhs, rhs): for e in lhs: k = e.epk_raw t = e.type_name if k not in seen: seen.add(k) o = rhs[t].instance(*k, raw=True) if o is None: diff = "Present in %s, missing in %s" % (lhs, rhs) else: diff = e.user_diff(o, ignore) if diff: result[(t, k)] = diff diff(self, other) diff(other, self) return result # end def user_diff def user_equal(self, other): """Compare entities of `self` and `other` regarding user attributes.""" s_count = self.ems.count(self.MOM.Id_Entity.E_Type, strict=False) o_count = other.ems.count(other.MOM.Id_Entity.E_Type, strict=False) if s_count == o_count: for e in self: o = other[e.type_name].instance(*e.epk_raw, raw=True) if not (o and e.user_equal(o)): break else: return True return False # end def user_equal def _check_inv(self, gauge, kind, eiter=None): err_result = [] wrn_result = [] sk = self.MOM.Id_Entity.sort_key if eiter is None: eiter = self.entity_iter_gauge \ (gauge, label = "Checking %s invariants" % kind) for e in eiter: try: ews = e._pred_man.check_kind(kind, e) if ews.errors: err_result.append(e) if ews.warnings: wrn_result.append(e) except Exception: print \ ( "Error during evaluation of", kind, "invariant for ", e , file = sys.stderr ) traceback.print_exc() err_result.append(e) return MOM.Pred.Err_and_Warn_List \ (sorted (err_result, key = sk), sorted (wrn_result, key = sk)) # end def _check_inv def _get_etm(self, name): try: result = self._etm[name] except KeyError: pn, _, rest = split_hst(name, ".") try: result = self._pkg_ns[pn] except KeyError: raise AttributeError(name) for k in rest.split("."): result = getattr(result, k) self._etm[name] = result return result # end def _get_etm def _new_guid(self): return str(uuid.uuid4()) # end def _new_guid def _new_id(self): result = Scope.__id Scope.__id += 1 return result # end def _new_id def _outer_pgk_ns(self, outer, pns, _pkg_ns): while True: outer, _, name = rsplit_hst(outer, ".") if (not outer) or outer in _pkg_ns: break pns = pns._Outer yield outer, pns # end def _outer_pgk_ns def _register_root(self, root): if root is not None: if self.root is None: self.root = self._roots[root.type_base_name] = root self.root_pid = root.pid self.bname = root.ui_display else: raise TypeError("Root was already set to %r" % (self.root, )) # end def _register_root def _run_init_callbacks(self): for c in self.init_callback: c(self) self.app_type.run_init_callbacks(self) # end def _run_init_callbacks def _setup_pkg_ns(self, app_type): _pkg_ns = self._pkg_ns = {} Pkg_NS = self.Pkg_NS for name, pns in sorted \ (pyk.iteritems (app_type.PNS_Map), key = TFL.Getter [0]) : _pkg_ns[name] = Pkg_NS(self, pns, name) for outer, pns in self._outer_pgk_ns(name, pns, _pkg_ns): _pkg_ns[outer] = Pkg_NS(self, pns, outer) # end def _setup_pkg_ns def _setup_root(self, app_type, root_spec): RT = self.Root_Type if root_spec and RT: if callable(root_spec): result = root_spec(self) if not isinstance(result, RT.Essence): raise TypeError \ ( "%s returned %s %r, expected %s" % (root_spec, result.__class__, result, RT) ) else: result = RT(*root_spec) self._register_root(result) return result # end def _setup_root def __getattr__(self, name): if name.startswith("__") and name.endswith("__"): ### Placate inspect.unwrap of Python 3.5, ### which accesses `__wrapped__` and eventually throws `ValueError` return getattr(self.__super, name) if "." in name: if name in self._etm: return self._etm[name] else: return self._get_etm(name) else: for dict in self._roots, self._pkg_ns: try: result = dict[name] except KeyError: pass else: setattr(self, name, result) return result return getattr(self.app_type, name) # end def __getattr__ def __getitem__(self, name): if not isinstance(name, pyk.string_types): name = name.type_name try: return self._get_etm(name) except AttributeError: raise KeyError(name) # end def __getitem__ def __iter__(self): """Generate all essential instances stored in database""" return iter(self.ems) # end def __iter__ def __str__(self): url = self._cleaned_url(str(self.db_url)) return "%s %s<%s>" % (self.__class__.__name__, self.bname, url)