Source code for anyblok.blok

# This file is a part of the AnyBlok project
#
#    Copyright (C) 2014 Jean-Sebastien SUZANNE <jssuzanne@anybox.fr>
#    Copyright (C) 2021 Jean-Sebastien SUZANNE <js.suzanne@gmail.com>
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file,You can
# obtain one at http://mozilla.org/MPL/2.0/.
from logging import getLogger
from os.path import dirname
from sys import modules
from time import sleep

from pkg_resources import iter_entry_points

from anyblok.environment import EnvironmentManager
from anyblok.imp import ImportManager

from .logging import log

logger = getLogger(__name__)


[docs]class BlokManagerException(LookupError): """Simple exception class for BlokManager""" def __init__(self, *args, **kwargs): EnvironmentManager.set("current_blok", None) super(BlokManagerException, self).__init__(*args, **kwargs)
[docs]class BlokManager: """Manage the bloks for one process A blok has a `setuptools` entrypoint, this entry point is defined by the ``entry_points`` attribute in the first load The ``bloks`` attribute is a dict with all the loaded entry points Use this class to import all the bloks in the entrypoint:: BlokManager.load() """ bloks = {} entry_points = None ordered_bloks = [] auto_install = []
[docs] @classmethod def list(cls): """Return the ordered bloks :rtype: list of blok name ordered by loading """ return cls.ordered_bloks
[docs] @classmethod def has(cls, blok): """Return True if the blok is loaded :param blok: the name of the blok :rtype: bool """ return blok and blok in cls.ordered_bloks or False
[docs] @classmethod def get(cls, blok): """Return the loaded blok :param blok: the name of the blok :rtype: blok instance :exception: BlokManagerException """ if not cls.has(blok): raise BlokManagerException("%r not found" % blok) return cls.bloks[blok]
[docs] @classmethod def set(cls, blokname, blok): """Add a new blok :param blokname: the name of the blok :param blok: blok instance :exception: BlokManagerException """ if cls.has(blokname): raise BlokManagerException("%r already present" % blokname) cls.bloks[blokname] = blok cls.ordered_bloks.append(blokname)
[docs] @classmethod @log(logger, level="debug") def reload(cls): """Reload the entry points Empty the ``bloks`` dict and use the ``entry_points`` attribute to load bloks :exception: BlokManagerException """ if cls.entry_points is None: raise BlokManagerException( """You must use the ``load`` classmethod before using """ """``reload``""" ) entry_points = [] entry_points += cls.entry_points cls.unload() cls.load(entry_points=entry_points)
[docs] @classmethod @log(logger, level="debug") def unload(cls): """Unload all the bloks but not the registry""" cls.bloks = {} cls.ordered_bloks = [] cls.entry_points = None cls.auto_install = [] from .registry import RegistryManager RegistryManager.unload()
[docs] @classmethod def get_needed_blok_dependencies(cls, blok): """Get all dependencies for the blok given :param blok: :return: """ for required in cls.bloks[blok].required: if not cls.get_needed_blok(required): cls.add_undefined_blok(required) cls.bloks[required].required_by.append(blok) for optional in cls.bloks[blok].optional: if cls.get_needed_blok(optional): cls.bloks[optional].optional_by.append(blok) for conditional in cls.bloks[blok].conditional: cls.bloks[conditional].conditional_by.append(blok) for conflicting in cls.bloks[blok].conflicting: cls.bloks[conflicting].conflicting_by.append(blok)
@classmethod def blok_importers(cls, blok): EnvironmentManager.set("current_blok", blok) if not ImportManager.has(blok): # Import only if the blok doesn't exists, do not reload here mod = ImportManager.add(blok) mod.imports() else: mod = ImportManager.get(blok) mod.reload()
[docs] @classmethod def get_needed_blok(cls, blok): """Get and import/load the blok given with dependencies :param blok: :return: """ if cls.has(blok): return True if blok not in cls.bloks: return False cls.get_needed_blok_dependencies(blok) cls.ordered_bloks.append(blok) cls.blok_importers(blok) if cls.bloks[blok].autoinstall: cls.auto_install.append(blok) return True
@classmethod def add_undefined_blok(cls, name): blok = type( "undefined-blok.%s" % name, (UndefinedBlok,), dict( name=name, required_by=[], optional_by=[], conditional_by=[], conflicting_by=[], ), ) blok.__doc__ = "Blok undefined" cls.set(name, blok) # here they are not python module to load, but this action # add the undefined blok in registryManager cls.blok_importers(name)
[docs] @classmethod @log(logger, level="debug") def load(cls, entry_points=("bloks",)): """Load all the bloks and import them :param entry_points: Used by ``iter_entry_points`` to get the blok :exception: BlokManagerException """ if not entry_points: raise BlokManagerException("The entry_points mustn't be empty") cls.entry_points = entry_points if EnvironmentManager.get("current_blok"): while EnvironmentManager.get("current_blok"): # pragma: no cover sleep(0.1) EnvironmentManager.set("current_blok", "start") bloks = [] for entry_point in entry_points: count = 0 for i in iter_entry_points(entry_point): count += 1 blok = i.load() blok.required_by = [] blok.optional_by = [] blok.conditional_by = [] blok.conflicting_by = [] cls.set(i.name, blok) blok.name = i.name bloks.append((blok.priority, i.name)) if not count: raise BlokManagerException( "Invalid bloks group %r" % entry_point ) # Empty the ordered blok to reload it depending on the priority cls.ordered_bloks = [] bloks.sort() try: while bloks: blok = bloks.pop(0)[1] cls.get_needed_blok(blok) finally: EnvironmentManager.set("current_blok", None)
[docs] @classmethod def getPath(cls, blok): """Return the path of the blok :param blok: blok name in ``ordered_bloks`` :rtype: absolute path """ blok = cls.get(blok) return dirname(modules[blok.__module__].__file__)
[docs]class Blok: """Super class for all the bloks define the default value for: * priority: order to load the blok * required: list of the bloks required to install this blok * optional: list of the bloks to be installed if present in the blok list * conditional: if all the bloks of this list are installed then install this blok """ autoinstall = False priority = 100 required = [] optional = [] conditional = [] conflicting = [] name = None # set by the BlokManager author = "" logo = "" def __init__(self, registry): self.anyblok = registry
[docs] @classmethod def import_declaration_module(cls): """Do the python import for the Declaration of the model or other"""
[docs] def update(self, latest_version): """Called on install or update :param latest_version: latest version installed, if the blok has never been installed, latest_version is None """
[docs] def pre_migration(self, latest_version): """Called on update, before the auto migration .. warning:: You can not use the ORM :param latest_version: latest version installed, if the blok has never been installed, latest_version is None """
[docs] def post_migration(self, latest_version): """Called on update, after the auto migration :param latest_version: latest version installed, if the blok has never been installed, latest_version is None """
[docs] def update_demo(self, latest_version): """Called on install or update to set or update demo data if `System.Parameter.get("with-demo", False) is True` :param latest_version: latest version installed, if the blok has never been installed, latest_version is None """
[docs] def uninstall_demo(self): """Called on uninstall demo data if `System.Parameter.get("with-demo", False) is True` """
[docs] def uninstall(self): """Called on uninstall"""
[docs] def load(self): """Called on start / launch"""
class UndefinedBlok(Blok): version = "0.0.0"