class Version(TCMS): """ Product version """ # Local cache of Version _cache = {} # List of all object attributes (used for init & expiration) _attributes = ["name", "product"] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Version Properties # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Read-only properties id = property(_getter("id"), doc="Version id") name = property(_getter("name"), doc="Version name") product = property(_getter("product"), doc="Version product") @classmethod def _cache_lookup(cls, id, **kwargs): """ Look up cached objects, return found instance and search key """ # Search cache by the version name and product if "product" in kwargs and ("version" in kwargs or "name" in kwargs): product = kwargs.get("product") if isinstance(product, Product): product = product.name name = kwargs.get("name", kwargs.get("version")) return cls._cache["{0}---in---{1}".format(name, product)], name # Default search by id otherwise return super(Version, cls)._cache_lookup(id, **kwargs) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Version Special # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def __init__(self, id=None, name=None, product=None, **kwargs): """ Initialize by version id or product and version """ # Backward compatibility for 'version' argument (now called 'name') name = name if name is not None else kwargs.get("version") # Initialize (unless already done) id, ignore, inject, initialized = self._is_initialized(id) if initialized: return TCMS.__init__(self, id) # If inject given, fetch tag data from it if inject: self._fetch(inject) # Initialize by version name and product elif name is not None and product is not None: self._name = name # Convert product into object if necessary if isinstance(product, Product): self._product = product else: self._product = Product(product) # Index by name/product (but only when the product name is known) if self.product._name is not TCMSNone: self._index("{0}---in---{1}".format(self.name, self.product.name)) # Otherwise just make sure the version id was provided elif not id: raise TCMSError( "Need either version id or both product " "and version name to initialize the Version object.") def __str__(self): """ Version name for printing """ return self.name # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Version Methods # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _fetch(self, inject=None): """ Fetch version data from the server """ TCMS._fetch(self, inject) # Directly fetch from the initial object dict if inject is not None: log.debug("Processing Version ID#{0} inject".format(inject["id"])) # Search by version id elif self._id is not TCMSNone: try: log.info("Fetching version {0}".format(self.identifier)) inject = self._server.Product.filter_versions({'id': self.id})[0] except IndexError: raise TCMSError("Cannot find version for {0}".format( self.identifier)) # Search by product and name else: try: log.info("Fetching version '{0}' of '{1}'".format( self.name, self.product.name)) inject = self._server.Product.filter_versions({ 'product': self.product.id, 'value': self.name })[0] except IndexError: raise TCMSError("Cannot find version for '{0}'".format( self.name)) # Initialize data from the inject and index into cache log.debug("Initializing Version ID#{0}".format(inject["id"])) log.data(pretty(inject)) self._inject = inject self._id = inject["id"] self._name = inject["value"] self._product = Product(inject["product_id"]) # Index by product name & version name (if product is cached) if self.product._name is not TCMSNone: self._index("{0}---in---{1}".format(self.name, self.product.name)) # Otherwise index by id only else: self._index()
class Product(TCMS): """ Product """ # Local cache of Product _cache = {} # List of all object attributes (used for init & expiration) _attributes = ["name"] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Product Properties # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Read-only properties id = property(_getter("id"), doc="Product id") name = property(_getter("name"), doc="Product name") @classmethod def _cache_lookup(cls, id, **kwargs): """ Look up cached objects, return found instance and search key """ # Search the cache by product name if "name" in kwargs: name = kwargs.get("name") return cls._cache[name], name return super(Product, cls)._cache_lookup(id, **kwargs) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Product Special # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def __init__(self, id=None, name=None): """ Initialize the Product by id or name Examples: Product(60) Product(id=60) Product("Red Hat Enterprise Linux 6") Product(name="Red Hat Enterprise Linux 6") """ # Initialize (unless already done) id, name, inject, initialized = self._is_initialized(id or name) if initialized: return TCMS.__init__(self, id) # If inject given, fetch test case data from it if inject: self._fetch(inject) # Initialize by name elif name is not None: self._name = name self._index(name) # Otherwise just check that the product id was provided elif not id: raise TCMSError("Need id or name to initialize Product") def __str__(self): """ Product name for printing """ return self.name @staticmethod def search(**query): """ Search for products """ return [ Product(hash["id"]) for hash in TCMS()._server.Product.filter(dict(query)) ] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Product Methods # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _fetch(self, inject=None): """ Fetch product data from the server """ TCMS._fetch(self, inject) # Directly fetch from the initial object dict if inject is not None: log.debug("Initializing Product ID#{0}".format(inject["id"])) log.data(pretty(inject)) self._id = inject["id"] self._name = inject["name"] # Search by product id elif self._id is not TCMSNone: try: log.info("Fetching product " + self.identifier) inject = self._server.Product.filter({'id': self.id})[0] log.debug("Initializing product " + self.identifier) log.data(pretty(inject)) self._inject = inject self._name = inject["name"] except IndexError: raise TCMSError("Cannot find product for " + self.identifier) # Search by product name else: try: log.info("Fetching product '{0}'".format(self.name)) inject = self._server.Product.filter({'name': self.name})[0] log.debug("Initializing product '{0}'".format(self.name)) log.data(pretty(inject)) self._inject = inject self._id = inject["id"] except IndexError: raise TCMSError("Cannot find product for '{0}'".format( self.name)) # Index the fetched object into cache self._index(self.name)
class User(TCMS): """ User """ # Local cache of User objects indexed by user id _cache = {} # List of all object attributes (used for init & expiration) _attributes = ["name", "login", "email"] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # User Properties # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Read-only properties id = property(_getter("id"), doc="User id.") login = property(_getter("login"), doc="Login username.") email = property(_getter("email"), doc="User email address.") name = property(_getter("name"), doc="User first name and last name.") # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # User Decorated # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @classmethod def _cache_lookup(cls, id, **kwargs): """ Look up cached objects, return found instance and search key """ # Return current user if id is None and 'login' not in kwargs and 'email' not in kwargs: return cls._cache["i-am-current-user"], "current user" # Search by login & email if "login" in kwargs: return cls._cache[kwargs["login"]], kwargs["login"] if "email" in kwargs: return cls._cache[kwargs["email"]], kwargs["email"] # Default search by id return super(User, cls)._cache_lookup(id, **kwargs) @staticmethod def search(**query): """ Search for users """ return [User(hash) for hash in TCMS()._server.User.filter(dict(query))] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # User Special # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def __new__(cls, id=None, *args, **kwargs): """ Create a new object, handle caching if enabled """ # Convert login or email into name for better logging if "login" in kwargs or "email" in kwargs: name = kwargs.get("login", kwargs.get("email")) return TCMS.__new__(cls, id=id, name=name, *args, **kwargs) else: return TCMS.__new__(cls, id=id, *args, **kwargs) def __init__(self, id=None, login=None, email=None): """ Initialize by user id, login or email Defaults to the current user if no id, login or email provided. If xmlrpc initial object dict provided as the first argument, data are initialized directly from it. """ # Initialize (unless already done) id, name, inject, initialized = self._is_initialized(id or login or email) if initialized: return TCMS.__init__(self, id, prefix="UID") # If inject given, fetch data from it if inject: self._fetch(inject) # Otherwise initialize by login or email elif name is not None: if "@" in name: self._email = name else: self._login = name self._index(name) def __str__(self): """ User login for printing """ return self.name if self.name is not None else "No Name" # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # User Methods # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _fetch(self, inject=None): """ Fetch user data from the server """ TCMS._fetch(self, inject) if inject is None: # Search by id if self._id is not TCMSNone: try: log.info("Fetching user " + self.identifier) inject = self._server.User.filter({"id": self.id})[0] except IndexError: raise TCMSError("Cannot find user for " + self.identifier) # Search by login elif self._login is not TCMSNone: try: log.info("Fetching user for login '{0}'".format( self.login)) inject = self._server.User.filter({"username": self.login})[0] except IndexError: raise TCMSError("No user found for login '{0}'".format( self.login)) # Search by email elif self._email is not TCMSNone: try: log.info("Fetching user for email '{0}'".format( self.email)) inject = self._server.User.filter({"email": self.email})[0] except IndexError: raise TCMSError("No user found for email '{0}'".format( self.email)) # Otherwise initialize to the current user else: log.info("Fetching the current user") inject = self._server.User.get() self._index("i-am-current-user") # Initialize data from the inject and index into cache log.debug("Initializing user UID#{0}".format(inject["id"])) log.data(pretty(inject)) self._inject = inject self._id = inject["id"] self._login = inject["username"] self._email = inject["email"] if inject["first_name"] and inject["last_name"]: self._name = inject["first_name"] + " " + inject["last_name"] else: self._name = None self._index(self.login, self.email)
class PlanType(TCMS): """ Plan type """ # Local cache of PlanType objects indexed by plan type id _cache = {} # By default we cache PlanType objects for ever _expiration = config.NEVER_EXPIRE # List of all object attributes (used for init & expiration) _attributes = ["name"] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # PlanType Properties # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Read-only properties id = property(_getter("id"), doc="Test plan type id") name = property(_getter("name"), doc="Test plan type name") # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # PlanType Decorated # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @classmethod def _cache_lookup(cls, id, **kwargs): """ Look up cached objects, return found instance and search key """ # Search cache by plan type name if "name" in kwargs: return cls._cache[kwargs["name"]], kwargs["name"] # Othewise perform default search by id return super(PlanType, cls)._cache_lookup(id, **kwargs) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # PlanType Special # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def __init__(self, id=None, name=None): """ Initialize by test plan type id or name """ # Initialize (unless already done) id, name, inject, initialized = self._is_initialized(id or name) if initialized: return TCMS.__init__(self, id) # If inject given, fetch data from it if inject: self._fetch(inject) # Initialize by name elif name is not None: self._name = name self._index(name) # Otherwise just check that the test plan type id was provided elif not id: raise TCMSError( "Need either id or name to initialize the PlanType object") def __str__(self): """ PlanType name for printing """ return self.name # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # PlanType Methods # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _fetch(self, inject=None): """ Get the missing test plan type data """ TCMS._fetch(self, inject) # Directly fetch from the initial object dict if inject is not None: log.info("Processing PlanType ID#{0} inject".format(inject["id"])) # Search by test plan type id elif self._id is not TCMSNone: try: log.info("Fetching test plan type " + self.identifier) inject = self._server.TestPlan.get_plan_type(self.id) except xmlrpc.client.Fault as error: log.debug(error) raise TCMSError("Cannot find test plan type for " + self.identifier) # Search by test plan type name else: try: log.info("Fetching test plan type '{0}'".format(self.name)) inject = self._server.TestPlan.check_plan_type(self.name) except xmlrpc.client.Fault as error: log.debug(error) raise TCMSError("PlanType '{0}' not found".format(self.name)) # Initialize data from the inject and index into cache log.debug("Initializing PlanType ID#{0}".format(inject["id"])) log.data(pretty(inject)) self._inject = inject self._id = inject["id"] self._name = inject["name"] self._index(self.name)
class Build(TCMS): """ Product build """ # Local cache of Build _cache = {} # List of all object attributes (used for init & expiration) _attributes = ["name", "product"] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Build Properties # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Read-only properties id = property(_getter("id"), doc="Build id.") name = property(_getter("name"), doc="Build name.") product = property(_getter("product"), doc="Relevant product.") @classmethod def _cache_lookup(cls, id, **kwargs): """ Look up cached objects, return found instance and search key """ # Name and product check if "product" in kwargs and ("name" in kwargs or "build" in kwargs): product = kwargs.get("product") if isinstance(product, Product): product = product.name name = kwargs.get("name", kwargs.get("build")) return cls._cache["{0}---in---{1}".format(name, product)], name return super(Build, cls)._cache_lookup(id, **kwargs) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Build Special # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def __init__(self, id=None, name=None, product=None, **kwargs): """ Initialize by build id or product and build name """ # Backward compatibility for 'build' argument (now called 'name') name = name if name is not None else kwargs.get("build") # Initialize (unless already done) id, ignore, inject, initialized = self._is_initialized(id or name) if initialized: return TCMS.__init__(self, id) # If inject given, fetch build data from it if inject: self._fetch(inject) # Initialized by build name and product elif name is not None and product is not None: self._name = name # Detect product format if isinstance(product, Product): self._product = product else: self._product = Product(product) # Index by name-product (only when the product name is known) if self.product._name is not TCMSNone: self._index("{0}---in---{1}".format(self.name, self.product.name)) # Otherwise just check that the id was provided elif not id: raise TCMSError("Need either build id or both build name " "and product to initialize the Build object.") def __str__(self): """ Build name for printing """ return self.name # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Build Methods # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _fetch(self, inject=None): """ Get the missing build data """ TCMS._fetch(self, inject) # Directly fetch from the initial object dict if inject is not None: log.info("Processing build ID#{0} inject".format( inject["build_id"])) # Search by build id elif self._id is not TCMSNone: try: log.info("Fetching build " + self.identifier) inject = self._server.Build.get(self.id) except xmlrpc.client.Fault as error: log.debug(error) raise TCMSError("Cannot find build for " + self.identifier) # Search by build name and product else: try: log.info("Fetching build '{0}' of '{1}'".format( self.name, self.product.name)) inject = self._server.Build.check_build( self.name, self.product.id) self._id = inject["build_id"] except xmlrpc.client.Fault as error: log.debug(error) raise TCMSError("Build '{0}' not found in '{1}'".format( self.name, self.product.name)) except KeyError: if "args" in inject: log.debug(inject["args"]) raise TCMSError("Build '{0}' not found in '{1}'".format( self.name, self.product.name)) # Initialize data from the inject and index into cache log.debug("Initializing Build ID#{0}".format(inject["build_id"])) log.data(pretty(inject)) self._inject = inject self._id = inject["build_id"] self._name = inject["name"] self._product = Product({ "id": inject["product_id"], "name": inject["product"] }) self._index("{0}---in---{1}".format(self.name, self.product.name))
class Tag(TCMS): """ Tag Class """ # List of all object attributes (used for init & expiration) _attributes = ["name"] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Tag Properties # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Read-only properties id = property(_getter("id"), doc="Tag id") name = property(_getter("name"), doc="Tag name") # Local cache for Tag _cache = {} # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Tag Special # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def __init__(self, id=None, name=None): """ Initialize by tag id or tag name """ # Initialize (unless already done) id, name, inject, initialized = self._is_initialized(id or name) if initialized: return TCMS.__init__(self, id) # If inject given, fetch tag data from it if inject: self._fetch(inject) # Initialize by name elif name is not None: self._name = name self._index(name) # Otherwise just check that the tag name or id was provided elif not id: raise TCMSError("Need either tag id or tag name " "to initialize the Tag object.") def __str__(self): """ Tag name for printing """ return self.name def __hash__(self): """ Use tag name for hashing """ # This is necessary until BZ#1084301 is fixed return hash(self.name) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Tag Methods # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _fetch(self, inject=None): """ Fetch tag data from the server """ TCMS._fetch(self, inject) # Directly fetch from the initial object dict if inject is not None: log.debug("Initializing Tag ID#{0}".format(inject["id"])) log.data(pretty(inject)) self._id = inject["id"] self._name = inject["name"] # Search by tag id elif self._id is not TCMSNone: try: log.info("Fetching tag " + self.identifier) inject = self._server.Tag.get_tags({'ids': [self.id]}) log.debug("Initializing tag " + self.identifier) log.data(pretty(inject)) self._inject = inject self._name = inject[0]["name"] except IndexError: raise TCMSError("Cannot find tag for {0}".format( self.identifier)) # Search by tag name else: try: log.info("Fetching tag '{0}'".format(self.name)) inject = self._server.Tag.get_tags({'names': [self.name]}) log.debug("Initializing tag '{0}'".format(self.name)) log.data(pretty(inject)) self._inject = inject self._id = inject[0]["id"] except IndexError: raise TCMSError("Cannot find tag '{0}'".format(self.name)) # Index the fetched object into cache self._index(self.name)
class Category(TCMS): """ Test case category """ # Local cache of Category objects indexed by category id _cache = {} # List of all object attributes (used for init & expiration) _attributes = ["name", "product", "description"] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Category Properties # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Read-only properties id = property(_getter("id"), doc="Category id.") name = property(_getter("name"), doc="Category name.") product = property(_getter("product"), doc="Relevant product.") description = property(_getter("description"), doc="Category description.") @property def synopsis(self): """ Short category summary (including product info) """ return "{0}, {1}".format(self.name, self.product) @classmethod def _cache_lookup(cls, id, **kwargs): """ Look up cached objects, return found instance and search key """ # Name and product check if "product" in kwargs and ("name" in kwargs or "category" in kwargs): product = kwargs.get("product") if isinstance(product, Product): product = product.name name = kwargs.get("name", kwargs.get("category")) return cls._cache["{0}---in---{1}".format(name, product)], name return super(Category, cls)._cache_lookup(id, **kwargs) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Category Special # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def __init__(self, id=None, name=None, product=None, **kwargs): """ Initialize by category id or category name and product """ # Backward compatibility for 'category' argument (now called 'name') name = name if name is not None else kwargs.get("category") # Initialize (unless already done) id, ignore, inject, initialized = self._is_initialized(id or name) if initialized: return TCMS.__init__(self, id) # If inject given, fetch tag data from it if inject: self._fetch(inject) # Initialized by category name and product elif name is not None and product is not None: self._name = name # Detect product format if isinstance(product, Product): self._product = product else: self._product = Product(product) # Index by name-product (only when the product name is known) if self.product._name is not TCMSNone: self._index("{0}---in---{1}".format(self.name, self.product.name)) # Otherwise just check that the id was provided elif not id: raise TCMSError( "Need either category id or both category " "name and product to initialize the Category object.") def __str__(self): """ Category name for printing """ return self.name # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Category Methods # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _fetch(self, inject=None): """ Get the missing category data """ TCMS._fetch(self, inject) # Directly fetch from the initial object dict if inject is not None: log.info("Processing category ID#{0} inject".format(inject["id"])) # Search by category id elif self._id is not TCMSNone: try: log.info("Fetching category {0}".format(self.identifier)) inject = self._server.Product.get_category(self.id) except xmlrpc.client.Fault as error: log.debug(error) raise TCMSError("Cannot find category for " + self.identifier) # Search by category name and product else: try: log.info("Fetching category '{0}' of '{1}'".format( self.name, self.product.name)) inject = self._server.Product.check_category( self.name, self.product.id) except xmlrpc.client.Fault as error: log.debug(error) raise TCMSError("Category '{0}' not found in" " '{1}'".format(self.name, self.product.name)) # Initialize data from the inject and index into cache log.debug("Initializing category ID#{0}".format(inject["id"])) log.data(pretty(inject)) self._inject = inject self._id = inject["id"] self._name = inject["name"] self._product = Product({ "id": inject["product_id"], "name": inject["product"] }) self._index("{0}---in---{1}".format(self.name, self.product.name))
class Component(TCMS): """ Test case component """ # Local cache of Component objects indexed by component id plus # additionaly by name-in-product pairs _cache = {} # List of all object attributes (used for init & expiration) _attributes = ["name", "product"] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Component Properties # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Read-only properties id = property(_getter("id"), doc="Component id.") name = property(_getter("name"), doc="Component name.") product = property(_getter("product"), doc="Relevant product.") @property def synopsis(self): """ Short component summary (including product info) """ return "{0}, {1}".format(self.name, self.product) @classmethod def _cache_lookup(cls, id, **kwargs): """ Look up cached objects, return found instance and search key """ # Name and product check if 'product' in kwargs and 'name' in kwargs: product = kwargs.get("product") if isinstance(product, Product): product = product.name name = kwargs.get("name") return cls._cache["{0}---in---{1}".format(name, product)], name return super(Component, cls)._cache_lookup(id, **kwargs) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Component Special # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def __init__(self, id=None, name=None, product=None, **kwargs): """ Initialize by component id or component name and product """ # Initialize (unless already done) id, ignore, inject, initialized = self._is_initialized(id) if initialized: return TCMS.__init__(self, id) # If inject given, fetch component data from it if inject: self._fetch(inject) # Initialized by product and component name elif name is not None and product is not None: # Detect product format if isinstance(product, Product): self._product = product else: self._product = Product(product) self._name = name # Index by name-product (only when the product name is known) if self.product._name is not TCMSNone: self._index("{0}---in---{1}".format(self.name, self.product.name)) # Otherwise just check that the id was provided elif id is None: raise TCMSError( "Need either component id or both product " "and component name to initialize the Component object.") def __str__(self): """ Component name for printing """ return self.name # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Component Methods # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _fetch(self, inject=None): """ Get the missing component data """ TCMS._fetch(self, inject) # Directly fetch from the initial object dict if inject is not None: log.info("Processing component ID#{0} inject".format(inject["id"])) # Search by component id elif self._id is not TCMSNone: try: log.info("Fetching component " + self.identifier) inject = self._server.Product.get_component(self.id) except xmlrpc.client.Fault as error: log.debug(error) raise TCMSError("Cannot find component for " + self.identifier) # Search by component name and product else: try: log.info("Fetching component '{0}' of '{1}'".format( self.name, self.product.name)) inject = self._server.Product.check_component( self.name, self.product.id) except xmlrpc.client.Fault as error: log.debug(error) raise TCMSError("Component '{0}' not found in" " '{1}'".format(self.name, self.product.name)) # Initialize data from the inject and index into cache log.debug("Initializing component ID#{0}".format(inject["id"])) log.data(pretty(inject)) self._inject = inject self._id = inject["id"] self._name = inject["name"] self._product = Product({ "id": inject["product_id"], "name": inject["product"] }) self._index("{0}---in---{1}".format(self.name, self.product.name)) @staticmethod def search(**query): """ Search for components """ return [ Component(hash) for hash in TCMS()._server.Product.filter_components(dict(query)) ]
class Bug(TCMS): """ Bug related to a test case or a case run """ # Local cache of Bug objects indexed by internal bug id _cache = {} # List of all object attributes (used for init & expiration) _attributes = ["bug", "system", "testcase", "caserun"] # Prefixes for bug systems, identifier width _prefixes = {1: "BZ"} _identifier_width = 7 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Bug Properties # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Read-only properties id = property(_getter("id"), doc="Bug id (internal).") bug = property(_getter("bug"), doc="Bug (external id).") system = property(_getter("system"), doc="Bug system.") testcase = property(_getter("testcase"), doc="Test case.") caserun = property(_getter("caserun"), doc="Case run.") @property def synopsis(self): """ Short summary about the bug """ # Summary in the form: BUG#123456 (BZ#123, TC#456, CR#789) return "{0} ({1})".format( self.identifier, ", ".join([str(self)] + [ obj.identifier for obj in (self.testcase, self.caserun) if obj is not None ])) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Bug Special # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def __init__(self, id=None, bug=None, system=1, **kwargs): """ Initialize the bug Provide external bug id, optionally bug system (Bugzilla by default). """ # Initialize (unless already done) id, ignore, inject, initialized = self._is_initialized(id) if initialized: return TCMS.__init__(self, id, prefix="BUG") # If inject given, fetch bug data from it if inject: self._fetch(inject) # Initialized by bug id and system id elif bug is not None and system is not None: self._bug = bug self._system = system # Otherwise just check that the id was provided elif id is None: raise TCMSError("Need bug id to initialize the Bug object.") def __eq__(self, other): """ Custom bug comparison Primarily decided by id. If unknown, compares by bug id & bug system. """ # Decide by internal id if self._id is not TCMSNone and other._id is not TCMSNone: return self.id == other.id # Compare external id and bug system id return self.bug == other.bug and self.system == other.system def __str__(self): """ Bug name for printing """ try: prefix = self._prefixes[self.system] except KeyError: prefix = "BZ" return "{0}#{1}".format( prefix, str(self.bug).rjust(self._identifier_width, "0")) def __hash__(self): """ Construct the uniqe hash from bug id and bug system id """ return _idify([self.system, self.bug]) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Bug Methods # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _fetch(self, inject=None): """ Fetch bug info from the server """ TCMS._fetch(self, inject) # No direct xmlrpc function for fetching so far if inject is None: raise NotImplementedError("Direct bug fetching not implemented") # Process provided inject self._id = int(inject["id"]) self._bug = int(inject["bug_id"]) self._system = int(inject["bug_system_id"]) self._testcase = TestCase(int(inject["case_id"])) if inject["case_run_id"] is not None: self._caserun = CaseRun(int(inject["case_run_id"])) # Index the fetched object into cache self._index()
class TestPlan(Mutable): """ Test plan. Provides test plan attributes and 'testruns' and 'testcases' properties, the latter as the default iterator. """ _cache = {} _identifier_width = 5 # List of all object attributes (used for init & expiration) _attributes = ["author", "children", "name", "owner", "parent", "product", "status", "tags", "testcases", "testruns", "type", "version"] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # TestPlan Properties # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Read-only properties id = property(_getter("id"), doc="Test plan id.") author = property(_getter("author"), doc="Test plan author.") children = property(_getter("children"), doc="Child test plans.") tags = property(_getter("tags"), doc="Attached tags.") testcases = property(_getter("testcases"), doc="Container with test cases linked to this plan.") testruns = property(_getter("testruns"), doc="Test runs related to this plan.") # Read-write properties name = property(_getter("name"), _setter("name"), doc="Test plan name.") owner = property(_getter("owner"), _setter("owner"), doc="Test plan owner.") parent = property(_getter("parent"), _setter("parent"), doc="Parent test plan.") product = property(_getter("product"), _setter("product"), doc="Test plan product.") type = property(_getter("type"), _setter("type"), doc="Test plan type.") status = property(_getter("status"), _setter("status"), doc="Test plan status.") version = property(_getter("version"), _setter("version"), doc="Default product version.") @property def synopsis(self): """ One line test plan overview """ return "{0} - {1} ({2} cases, {3} runs)".format( self.identifier, self.name, len(self.testcases), len(self.testruns)) @classmethod def _cache_lookup(cls, id, **kwargs): """ Look up cached objects, return found instance and search key """ # ID check if isinstance(id, int): return cls._cache[id], id # Check dictionary (only ID so far) if isinstance(id, dict): return cls._cache[id["plan_id"]], id["plan_id"] raise KeyError # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # TestPlan Special # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def __init__(self, id=None, name=None, product=None, version=None, type=None, **kwargs): """ Initialize a test plan or create a new one. Provide id to initialize an existing test plan or name, product, version and type to create a new plan. Other parameters are optional. document .... Test plan document (default: '') parent ...... Parent test plan (object or id, default: None) Product, version and type can be provided as id, name or object. """ # Initialize (unless already done) id, ignore, inject, initialized = self._is_initialized(id) if initialized: return Mutable.__init__(self, id, prefix="TP") # If inject given, fetch test case data from it if inject: self._fetch(inject) # Create a new test plan if name, type, product and version provided elif name and type and product and version: self._create(name=name, product=product, version=version, type=type, **kwargs) # Otherwise just check that the test plan id was provided elif not id: raise TCMSError( "Need either id or name, product, version " "and type to initialize the test plan") def __iter__(self): """ Provide test cases as the default iterator """ for testcase in self.testcases: yield testcase def __str__(self): """ Test plan id & summary for printing """ return "{0} - {1}".format(self.identifier, self.name) @staticmethod def search(**query): """ Search for test plans """ return [TestPlan(hash) for hash in TCMS()._server.TestPlan.filter(dict(query))] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # TestPlan Methods # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _create(self, name, product, version, type, **kwargs): """ Create a new test plan """ hash = {} # Name if name is None: raise TCMSError("Name required for creating new test plan") hash["name"] = name # Product if product is None: raise TCMSError("Product required for creating new test plan") elif isinstance(product, (int, str)): product = Product(product) hash["product"] = product.id # Version if version is None: raise TCMSError("Version required for creating new test plan") elif isinstance(version, int): version = Version(version) elif isinstance(version, str): version = Version(name=version, product=product) hash["default_product_version"] = version.id # Type if type is None: raise TCMSError("Type required for creating new test plan") elif isinstance(type, (int, str)): type = PlanType(type) hash["type"] = type.id # Parent parent = kwargs.get("parent") if parent is not None: if isinstance(parent, int): parent = TestPlan(parent) hash["parent"] = parent.id # Document - if not explicitly specified, put empty text hash["text"] = kwargs.get("text", " ") # Workaround for BZ#725995 hash["is_active"] = "1" # Submit log.info("Creating a new test plan") log.data(pretty(hash)) inject = self._server.TestPlan.create(hash) log.data(pretty(inject)) try: self._id = inject["plan_id"] except TypeError: log.debug("Failed to create a new test plan") log.data(pretty(hash)) log.data(pretty(inject)) raise TCMSError("Failed to create test plan") self._fetch(inject) log.info("Successfully created {0}".format(self)) def _fetch(self, inject=None): """ Initialize / refresh test plan data. Either fetch them from the server or use provided hash. """ TCMS._fetch(self, inject) # Fetch the data hash from the server unless provided if inject is None: log.info("Fetching test plan " + self.identifier) try: inject = self._server.TestPlan.filter({'pk': self.id})[0] except IndexError as error: log.debug(error) raise TCMSError( "Failed to fetch test plan TP#{0}".format(self.id)) self._inject = inject # Otherwise just initialize the id from inject else: self._id = inject["plan_id"] log.debug("Initializing test plan " + self.identifier) log.data(pretty(inject)) if "plan_id" not in inject: log.data(pretty(inject)) raise TCMSError("Failed to initialize " + self.identifier) # Set up attributes self._author = User(inject["author_id"]) if inject["owner_id"] is not None: self._owner = User(inject["owner_id"]) else: self._owner = None self._name = inject["name"] self._product = Product({ "id": inject["product_id"], "name": inject["product"]}) self._version = Version({ "id": inject["product_version_id"], "value": inject["product_version"], "product_id": inject["product_id"]}) self._type = PlanType(inject["type_id"]) self._status = PlanStatus(inject["is_active"] in ["True", True]) if inject["parent_id"] is not None: self._parent = TestPlan(inject["parent_id"]) else: self._parent = None # Initialize containers self._testcases = PlanCases(self) self._testruns = PlanRuns(self) self._children = ChildPlans(self) # If all tags are cached, initialize them directly from the inject if "tag" in inject and Tag._is_cached(inject["tag"]): self._tags = PlanTags( self, inset=[Tag(tag) for tag in inject["tag"]]) else: self._tags = PlanTags(self) # Index the fetched object into cache self._index() def _update(self): """ Save test plan data to the server """ # Prepare the update hash hash = {} hash["name"] = self.name hash["product"] = self.product.id hash["type"] = self.type.id hash["is_active"] = self.status.id == 1 if self.parent is not None: hash["parent"] = self.parent.id hash["default_product_version"] = self.version.id if self.owner is not None: hash["owner"] = self.owner.id log.info("Updating test plan " + self.identifier) log.data(pretty(hash)) self._server.TestPlan.update(self.id, hash) def update(self): """ Update self and containers, if modified, to the server """ # Update containers (if initialized) if self._tags is not TCMSNone: self.tags.update() if self._testcases is not TCMSNone: self.testcases.update() if self._testruns is not TCMSNone: self.testruns.update() if self._children is not TCMSNone: self.children.update() # Update self (if modified) Mutable.update(self)
class TestCase(Mutable): """ Test case. Provides test case attributes and 'testplans' property as the default iterator. Furthermore contains bugs, components and tags properties. """ _cache = {} _identifier_width = 7 # List of all object attributes (used for init & expiration) _attributes = ["action", "arguments", "author", "automated", "autoproposed", "breakdown", "bugs", "category", "components", "created", "effect", "link", "manual", "notes", "plans", "priority", "script", "setup", "status", "summary", "tags", "tester", "testplans", "time"] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # TestCase Properties # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Read-only properties id = property(_getter("id"), doc="Test case id (read-only).") author = property(_getter("author"), doc="Test case author.") created = property(_getter("created"), doc="Test case creation date (datetime).") tags = property(_getter("tags"), doc="Attached tags.") bugs = property(_getter("bugs"), doc="Attached bugs.") testplans = property(_getter("testplans"), doc="Test plans linked to this test case.") components = property(_getter("components"), doc="Components related to this test case.") setup = property(_getter("setup"), doc="Setup steps to prepare the machine for the test case.") action = property(_getter("action"), doc="Actions to be performed.") effect = property(_getter("effect"), doc="Expected Results to be measured.") breakdown = property(_getter("breakdown"), doc="Breakdown steps to return machine to original state.") @property def synopsis(self): """ Short summary about the test case """ plans = len(self.testplans) return "{0} ({1}, {2}, {3}, {4} {5})".format( self, self.category, self.priority, self.status, plans, "test plan" if plans == 1 else "test plans") # Read-write properties automated = property(_getter("automated"), _setter("automated"), doc="Automation flag. True if the test case is automated.") autoproposed = property(_getter("autoproposed"), _setter("autoproposed"), doc="True if the test case is proposed for automation.") arguments = property(_getter("arguments"), _setter("arguments"), doc="Test script arguments (used for automation).") category = property(_getter("category"), _setter("category"), doc="Test case category.") link = property(_getter("link"), _setter("link"), doc="Test case reference link.") manual = property(_getter("manual"), _setter("manual"), doc="Manual flag. True if the test case is manual.") notes = property(_getter("notes"), _setter("notes"), doc="Test case notes.") priority = property(_getter("priority"), _setter("priority"), doc="Test case priority.") requirement = property(_getter("requirement"), _setter("requirement"), doc="Test case requirements.") script = property(_getter("script"), _setter("script"), doc="Test script (used for automation).") status = property(_getter("status"), _setter("status"), doc="Current test case status.") summary = property(_getter("summary"), _setter("summary"), doc="Summary describing the test case.") tester = property(_getter("tester"), _setter("tester"), doc="Default tester.") time = property(_getter("time"), _setter("time"), doc="Estimated time.") @classmethod def _cache_lookup(cls, id, **kwargs): """ Look up cached objects, return found instance and search key """ # ID check if isinstance(id, int): return cls._cache[id], id # Check dictionary (only ID so far) if isinstance(id, dict): return cls._cache[id["case_id"]], id["case_id"] raise KeyError # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # TestCase Special # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def __init__(self, id=None, summary=None, category=None, **kwargs): """ Initialize a test case or create a new one. Initialize an existing test case (if id provided) or create a new one (based on provided summary and category. Other optional parameters supported are: product ........ product (default: category.product) status ......... test case status (default: PROPOSED) automated ...... automation flag (default: True) autoproposed ... proposed for automation (default: False) manual ......... manual flag (default: False) priority ....... priority object, id or name (default: P3) script ......... test path (default: None) arguments ...... script arguments (default: None) requirement .... requirement (default: None) tester ......... user object or login (default: None) link ........... reference link (default: None) Examples: existing = TestCase(1234) sanity = Category(name="Sanity", product="Fedora") new = TestCase(summary="Test", category=sanity) new = TestCase(summary="Test", category="Sanity", product="Fedora") Note: When providing category by name specify product as well. """ # Initialize (unless already done) id, name, inject, initialized = self._is_initialized(id) if initialized: return Mutable.__init__(self, id, prefix="TC") # If inject given, fetch test case data from it if inject: self._fetch(inject) # Create a new test case based on summary and category elif summary and category: self._create(summary=summary, category=category, **kwargs) # Otherwise just check that the test case id was provided elif not id: raise TCMSError("Need either id or both summary and category " "to initialize the test case") def __str__(self): """ Test case id & summary for printing """ return "{0} - {1}".format(self.identifier, self.summary) @staticmethod def search(**query): """ Search for test cases """ # Special handling for automated & manual attributes manual = automated = None if "automated" in query: automated = query["automated"] del query["automated"] if "manual" in query: manual = query["manual"] del query["manual"] # Map to appropriate value of 'is_automated' attribute if manual is not None or automated is not None: if automated is False and manual is False: raise TCMSError("Invalid search " "('manual' and 'automated' cannot be both False)") elif automated is False: query["is_automated"] = 0 elif manual is False: query["is_automated"] = 1 elif automated is True and manual is True: query["is_automated"] = 2 elif automated is True: query["is_automated__in"] = [1, 2] elif manual is True: query["is_automated__in"] = [0, 2] log.debug("Searching for test cases") log.data(pretty(query)) return [TestCase(inject) for inject in TCMS()._server.TestCase.filter(dict(query))] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # TestCase Methods # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _create(self, summary, category, **kwargs): """ Create a new test case """ hash = {} # Summary hash["summary"] = summary # If category provided as text, we need product as well product = kwargs.get("product") if isinstance(category, str) and not kwargs.get("product"): raise TCMSError( "Need product when category specified by name") # Category & Product if isinstance(category, str): category = Category(category=category, product=product) elif not isinstance(category, Category): raise TCMSError("Invalid category '{0}'".format(category)) hash["category"] = category.id hash["product"] = category.product.id # Priority priority = kwargs.get("priority") if priority is None: priority = Priority("P3") elif not isinstance(priority, Priority): priority = Priority(priority) hash["priority"] = priority.id # User tester = kwargs.get("tester") if tester: if isinstance(tester, str): tester = User(login=tester) hash["default_tester"] = tester.login # Script, arguments, requirement & reference link hash["script"] = kwargs.get("script") hash["arguments"] = kwargs.get("arguments") hash["requirement"] = kwargs.get("requirement") hash["extra_link"] = kwargs.get("link") # Case Status status = kwargs.get("status") if status: if isinstance(status, str): status = CaseStatus(status) hash["case_status"] = status.id # Manual, automated and autoproposed automated = kwargs.get("automated", True) autoproposed = kwargs.get("autoproposed", False) manual = kwargs.get("manual", False) if automated and manual: hash["is_automated"] = 2 elif automated: hash["is_automated"] = 1 else: hash["is_automated"] = 0 hash["is_automated_proposed"] = autoproposed # Estimated time hash["estimated_time"] = kwargs.get("time", '00:00:00') # Notes notes = kwargs.get("notes") if notes: hash["notes"] = notes # Submit log.info("Creating a new test case") log.data(pretty(hash)) testcasehash = self._server.TestCase.create(hash) log.data(pretty(testcasehash)) try: self._id = testcasehash["case_id"] except TypeError: log.debug("Failed to create a new test case") log.data(pretty(hash)) log.data(pretty(testcasehash)) raise TCMSError("Failed to create test case") self._fetch(testcasehash) log.info("Successfully created {0}".format(self)) def _fetch(self, inject=None): """ Initialize / refresh test case data. Either fetch them from the server or use provided hash. """ TCMS._fetch(self, inject) # Fetch the data hash from the server unless provided if inject is None: log.info("Fetching test case " + self.identifier) try: inject = self._server.TestCase.filter({'pk': self.id})[0] except IndexError as error: log.debug(error) raise TCMSError( "Failed to fetch test case TC#{0}".format(self.id)) self._inject = inject else: self._id = inject["case_id"] log.debug("Initializing test case " + self.identifier) log.data(pretty(inject)) # Set up attributes self._arguments = inject["arguments"] self._author = User(inject["author_id"]) self._category = Category(inject["category_id"]) if isinstance(inject["create_date"], str): self._created = datetime.datetime.strptime( inject["create_date"], "%Y-%m-%d %H:%M:%S") else: self._created = inject["create_date"] self._link = inject["extra_link"] self._notes = inject["notes"] self._priority = Priority(inject["priority_id"]) self._requirement = inject["requirement"] self._script = inject["script"] self._status = CaseStatus(inject["case_status_id"]) self._summary = inject["summary"] self._time = inject["estimated_time"] if inject["default_tester_id"] is not None: self._tester = User(inject["default_tester_id"]) else: self._tester = None # Handle manual, automated and autoproposed self._automated = inject["is_automated"] in [1, '1', 2, '2'] self._manual = inject["is_automated"] in [0, '0', 2, '2'] self._autoproposed = inject["is_automated_proposed"] # Empty script or arguments to be handled same as None if self._script == "": self._script = None if self._arguments == "": self._arguments = None # Test case documentation for attribute in ["setup", "action", "effect", "breakdown"]: if "text" in inject: setattr(self, "_" + attribute, inject["text"][attribute]) else: setattr(self, "_" + attribute, None) # Initialize containers self._bugs = CaseBugs(self) self._testplans = CasePlans(self) self._components = CaseComponents(self) # If all tags are cached, initialize them directly from the inject if "tag" in inject and Tag._is_cached(inject["tag"]): self._tags = CaseTags( self, inset=[Tag(tag) for tag in inject["tag"]]) else: self._tags = CaseTags(self) # Index the fetched object into cache self._index() def _update(self): """ Save test case data to server """ hash = {} hash["arguments"] = self.arguments hash["case_status"] = self.status.id hash["category"] = self.category.id hash["estimated_time"] = self.time if self.automated and self.manual: hash["is_automated"] = 2 elif self.automated: hash["is_automated"] = 1 else: hash["is_automated"] = 0 hash["is_automated_proposed"] = self.autoproposed hash["extra_link"] = self.link hash["notes"] = self.notes hash["priority"] = self.priority.id hash["product"] = self.category.product.id hash["requirement"] = self.requirement hash["script"] = self.script hash["summary"] = self.summary if self.tester: hash["default_tester"] = self.tester.login log.info("Updating test case " + self.identifier) log.data(pretty(hash)) self._server.TestCase.update(self.id, hash) def update(self): """ Update self and containers, if modified, to the server """ # Update containers (if initialized) if self._bugs is not TCMSNone: self.bugs.update() if self._tags is not TCMSNone: self.tags.update() if self._testplans is not TCMSNone: self.testplans.update() if self._components is not TCMSNone: self._components.update() # Update self (if modified) Mutable.update(self)
class TestRun(Mutable): """ Test run. Provides test run attributes and 'caseruns' property containing all relevant case runs (which is also the default iterator). """ _cache = {} _identifier_width = 6 # List of all object attributes (used for init & expiration) _attributes = ["build", "caseruns", "finished", "manager", "notes", "product", "started", "status", "summary", "tags", "tester", "testcases", "testplan", "time"] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # TestRun Properties # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Read-only properties id = property(_getter("id"), doc="Test run id.") testplan = property(_getter("testplan"), doc="Test plan related to this test run.") tags = property(_getter("tags"), doc="Attached tags.") started = property(_getter("started"), doc="Timestamp when the test run was started (datetime).") finished = property(_getter("finished"), doc="Timestamp when the test run was finished (datetime).") caseruns = property(_getter("caseruns"), doc="CaseRun objects related to this test run.") testcases = property(_getter("testcases"), doc="""TestCase objects related to this test run\n Supports common container methods add(), remove() and clear() for adding and removing testcases to/from the test run.""") # Read-write properties build = property(_getter("build"), _setter("build"), doc="Build relevant for this test run.") manager = property(_getter("manager"), _setter("manager"), doc="Manager responsible for this test run.") notes = property(_getter("notes"), _setter("notes"), doc="Test run notes.") status = property(_getter("status"), _setter("status"), doc="Test run status") summary = property(_getter("summary"), _setter("summary"), doc="Test run summary.") tester = property(_getter("tester"), _setter("tester"), doc="Default tester.") time = property(_getter("time"), _setter("time"), doc="Estimated time.") @property def synopsis(self): """ One-line test run overview """ return "{0} - {1} ({2} cases)".format( self.identifier, self.summary, len(self.caseruns)) @classmethod def _cache_lookup(cls, id, **kwargs): """ Look up cached objects, return found instance and search key """ # ID check if isinstance(id, int): return cls._cache[id], id # Check dictionary (only ID so far) if isinstance(id, dict): return cls._cache[id["run_id"]], id["run_id"] raise KeyError # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # TestRun Special # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def __init__(self, id=None, testplan=None, **kwargs): """ Initialize a test run or create a new one. Initialize an existing test run if id provided, otherwise create a new test run based on specified test plan (required). Other parameters are optional and have the following defaults: build ....... "unspecified" product ..... test run product version ..... test run product version summary ..... <test plan name> on <build> notes ....... "" manager ..... current user tester ...... current user """ # Initialize (unless already done) id, name, inject, initialized = self._is_initialized(id) if initialized: return Mutable.__init__(self, id, prefix="TR") # If inject given, fetch test case data from it if inject: self._fetch(inject) # Create a new test run based on provided plan elif testplan: self._create(testplan=testplan, **kwargs) # Otherwise just check that the test run id was provided elif not id: raise TCMSError( "Need either id or test plan to initialize the test run") def __iter__(self): """ Provide test case runs as the default iterator """ for caserun in self.caseruns: yield caserun def __str__(self): """ Test run id & summary for printing """ return "{0} - {1}".format(self.identifier, self.summary) @staticmethod def search(**query): """ Search for test runs """ return [TestRun(hash) for hash in TCMS()._server.TestRun.filter(dict(query))] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # TestRun Methods # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _create(self, testplan, product=None, version=None, build=None, summary=None, notes=None, manager=None, tester=None, **kwargs): """ Create a new test run """ hash = {} # Test plan if isinstance(testplan, int): testplan = TestPlan(testplan) hash["plan"] = testplan.id # Product & version if product is None: product = testplan.product elif not isinstance(product, Product): product = Product(product) hash["product"] = product.id if version is None: version = testplan.version elif isinstance(version, int): version = Version(version) else: version = Version(name=version, product=product) hash["product_version"] = version.id # Build if build is None: build = "unspecified" if isinstance(build, str): build = Build(build=build, product=product) hash["build"] = build.id # Summary & notes if summary is None: summary = "{0} on {1}".format(testplan.name, build) if notes is None: notes = "" hash["summary"] = summary hash["notes"] = notes # Manager & tester (current user by default) if not isinstance(manager, User): manager = User(manager) if not isinstance(tester, User): tester = User(tester) hash["manager"] = manager.id hash["default_tester"] = tester.id # Submit to the server and initialize log.info("Creating a new test run based on {0}".format(testplan)) log.data(pretty(hash)) testrunhash = self._server.TestRun.create(hash) log.data(pretty(testrunhash)) try: self._id = testrunhash["run_id"] except TypeError: log.debug("Failed to create a new test run based on {0}".format( testplan)) log.data(pretty(hash)) log.data(pretty(testrunhash)) raise TCMSError("Failed to create test run") self._fetch(testrunhash) # Add newly created test run to testplan.testruns container if PlanRuns._is_cached(testplan.testruns): testplan.testruns._fetch(list(testplan.testruns) + [self]) log.info("Successfully created {0}".format(self)) def _fetch(self, inject=None): """ Initialize / refresh test run data. Either fetch them from the server or use the provided hash. """ TCMS._fetch(self, inject) # Fetch the data hash from the server unless provided if inject is None: log.info("Fetching test run {0}".format(self.identifier)) try: inject = self._server.TestRun.filter({'pk': self.id})[0] except IndexError as error: log.debug(error) raise TCMSError( "Failed to fetch test run TR#{0}".format(self.id)) self._inject = inject else: self._id = inject["run_id"] log.debug("Initializing test run {0}".format(self.identifier)) log.data(pretty(inject)) # Set up attributes self._build = Build(inject["build_id"]) self._manager = User(inject["manager_id"]) self._notes = inject["notes"] self._status = RunStatus(inject["stop_date"]) self._old_status = self._status self._summary = inject["summary"] self._tester = User(inject["default_tester_id"]) self._testplan = TestPlan(inject["plan_id"]) self._time = inject["estimated_time"] try: self._started = datetime.datetime.strptime( inject["start_date"], "%Y-%m-%d %H:%M:%S") except TypeError: self._started = None try: self._finished = datetime.datetime.strptime( inject["stop_date"], "%Y-%m-%d %H:%M:%S") except TypeError: self._finished = None # Initialize containers self._caseruns = RunCaseRuns(self) self._testcases = RunCases(self) self._tags = RunTags(self) # Index the fetched object into cache self._index() def _update(self): """ Save test run data to the server """ # Prepare the update hash hash = {} hash["build"] = self.build.id hash["default_tester"] = self.tester.id hash["estimated_time"] = self.time hash["manager"] = self.manager.id hash["notes"] = self.notes # This is required until BZ#731982 is fixed hash["product"] = self.build.product.id hash["summary"] = self.summary # Update status field only if its value has changed. This is to avoid # updating the 'Finished at' field, which is done automatically by # TCMS even when "switching" from 'Finished' to 'Finished'. if self._status != self._old_status: self._old_status = self._status hash["status"] = self.status.id log.info("Updating test run " + self.identifier) log.data(pretty(hash)) self._server.TestRun.update(self.id, hash) def update(self): """ Update self and containers, if modified, to the server """ # Update containers (if initialized) if self._tags is not TCMSNone: self.tags.update() if self._caseruns is not TCMSNone: self._caseruns.update() if self._testcases is not TCMSNone: self._testcases.update() # Update self (if modified) Mutable.update(self)
class CaseRun(Mutable): """ Test case run. Provides case run attributes such as status and assignee, including the relevant 'testcase' object. """ _identifier_width = 8 # By default we do not cache CaseRun objects at all _expiration = config.NEVER_CACHE _cache = {} # List of all object attributes (used for init & expiration) _attributes = ["assignee", "bugs", "build", "notes", "sortkey", "status", "testcase", "testrun"] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Case Run Properties # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Read-only properties id = property(_getter("id"), doc="Test case run id.") testcase = property(_getter("testcase"), doc="Test case object.") testrun = property(_getter("testrun"), doc="Test run object.") bugs = property(_getter("bugs"), doc="Attached bugs.") # Read-write properties assignee = property(_getter("assignee"), _setter("assignee"), doc="Test case run assignee object.") build = property(_getter("build"), _setter("build"), doc="Test case run build object.") notes = property(_getter("notes"), _setter("notes"), doc="Test case run notes (string).") sortkey = property(_getter("sortkey"), _setter("sortkey"), doc="Test case sort key (int).") status = property(_getter("status"), _setter("status"), doc="Test case run status object.") @classmethod def _cache_lookup(cls, id, **kwargs): """ Look up cached objects, return found instance and search key """ # ID check if isinstance(id, int): return cls._cache[id], id # Check dictionary (only ID so far) if isinstance(id, dict): return cls._cache[id["case_run_id"]], id["case_run_id"] raise KeyError # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # CaseRun Special # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def __init__(self, id=None, testcase=None, testrun=None, **kwargs): """ Initialize a test case run or create a new one. Initialize an existing test case run (if id provided) or create a new test case run (based on provided test case and test run). """ # Initialize (unless already done) id, name, inject, initialized = self._is_initialized(id, **kwargs) if initialized: return Mutable.__init__(self, id, prefix="CR") # If inject given, fetch test case run data from it if inject: self._fetch(inject, **kwargs) # Create a new test case run based on case and run elif testcase and testrun: self._create(testcase=testcase, testrun=testrun, **kwargs) # Otherwise just check that the test case run id was provided elif not id: raise TCMSError("Need either id or testcase and testrun " "to initialize the case run") def __str__(self): """ Case run id, status & summary for printing """ return "{0} - {1} - {2}".format( self.status.shortname, self.identifier, self.testcase.summary) @staticmethod def search(**query): """ Search for case runs """ return [CaseRun(inject) for inject in TCMS()._server.TestCaseRun.filter(dict(query))] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # CaseRun Methods # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _create(self, testcase, testrun, **kwargs): """ Create a new case run """ hash = {} # TestCase if testcase is None: raise TCMSError("Case ID required for new case run") elif isinstance(testcase, str): testcase = TestCase(testcase) hash["case"] = testcase.id # TestRun if testrun is None: raise TCMSError("Run ID required for new case run") elif isinstance(testrun, str): testrun = TestRun(testrun) hash["run"] = testrun.id # Build is required by XMLRPC build = testrun.build hash["build"] = build.id # Submit log.info("Creating new case run") log.data(pretty(hash)) inject = self._server.TestCaseRun.create(hash) log.data(pretty(inject)) try: self._id = inject["case_run_id"] except TypeError: log.debug("Failed to create new case run") log.data(pretty(hash)) log.data(pretty(inject)) raise TCMSError("Failed to create case run") self._fetch(inject) log.info("Successfully created {0}".format(self)) # And finally add to testcases and caseruns containers self.testrun.testcases._fetch( [self.testcase] + list(self.testrun.testcases)) self.testrun.caseruns._fetch( [self] + list(self.testrun.caseruns)) def _fetch(self, inject=None, **kwargs): """ Initialize / refresh test case run data. Either fetch them from the server or use the supplied hashes. """ TCMS._fetch(self, inject) # Fetch the data from the server unless inject provided if inject is None: log.info("Fetching case run {0}".format(self.identifier)) inject = self._server.TestCaseRun.filter({'pk': self.id})[0] self._inject = inject else: self._id = inject["case_run_id"] log.debug("Initializing case run {0}".format(self.identifier)) log.data(pretty(inject)) # Set up attributes self._assignee = User(inject["assignee_id"]) self._build = Build(inject["build_id"]) self._notes = inject["notes"] if inject["sortkey"] is not None: self._sortkey = int(inject["sortkey"]) else: self._sortkey = None self._status = Status(inject["case_run_status_id"]) self._testrun = TestRun(inject["run_id"]) # Initialize attached test case (from dict, object or id) testcaseinject = kwargs.get("testcaseinject", None) if testcaseinject and isinstance(testcaseinject, dict): self._testcase = TestCase(testcaseinject) elif testcaseinject and isinstance(testcaseinject, TestCase): self._testcase = testcaseinject else: self._testcase = TestCase(inject["case_id"]) # Initialize containers self._bugs = CaseRunBugs(self) # Index the fetched object into cache self._index() def _update(self): """ Save test case run data to the server """ # Prepare the update hash hash = {} hash["build"] = self.build.id hash["assignee"] = self.assignee.id hash["case_run_status"] = self.status.id hash["notes"] = self.notes hash["sortkey"] = self.sortkey # Work around BZ#715596 if self.notes is None: hash["notes"] = "" log.info("Updating case run " + self.identifier) log.data(pretty(hash)) self._server.TestCaseRun.update(self.id, hash) def update(self): """ Update self and containers, if modified, to the server """ # Update containers (if initialized) if self._bugs is not TCMSNone: self.bugs.update() # Update self (if modified) Mutable.update(self)
class Container(Mutable): """ General container class for handling sets of objects. Provides the add() and remove() methods for adding and removing objects and the internal _add() and _remove() which perform the actual update to the server (implemented by respective class). """ # List of all object attributes (used for init & expiration) _attributes = ["current", "original"] # Class of objects to be contained (defined in each container) _class = None # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Container Properties # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ id = property(_getter("id"), doc="Related object id.") @property def _items(self): """ Set representation containing the items """ if self._current is TCMSNone: self._fetch() # Fetch the whole container if there are uncached items (except when # the container is modified otherwise we would lose local changes). if not self._modified and not self._class._is_cached(self._current): self._fetch() return self._current # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Container Special # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def __new__(cls, object, inset=None): """ Create new container objects based on the object id """ return super(Container, cls).__new__(cls, object.id) def __init__(self, object, inset=None): """ Initialize container for specified object """ # Initialize (unless already done) if self._id is not None: # Initialized but not fetched ---> fetch from the inset if given if inset is not None and not self._is_cached(self): self._fetch(inset) return Mutable.__init__(self, object.id) self._identifier = object.identifier self._object = object # Initialize directly if initial set provided if inset is not None: self._fetch(inset) def __iter__(self): """ Container iterator """ for item in self._items: yield item def __getitem__(self, index): """ Indexing support """ if isinstance(index, int): return list(self)[index] elif isinstance(index, slice): return list(self)[index.start:index.stop:index.step] else: raise IndexError("Invalid index '{0}'".format(index)) def __contains__(self, item): """ Container 'in' operator """ return item in self._items def __len__(self): """ Number of container items """ return len(self._items) def __str__(self): """ Display items as a list for printing """ if self._items: # List of identifiers try: return listed(sorted([item.identifier for item in self._items])) # If no identifiers, just join strings except AttributeError: return listed(self._items, quote="'") else: return "[None]" # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Container Methods # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def _fetch(self, inset=None): """ Save cache timestamp and initialize from inset if given """ TCMS._fetch(self) # Create copies of the initial set (if given) if inset is not None: log.debug("Initializing {0} for {1} from the inset".format( self.__class__.__name__, self._identifier)) log.debug(pretty(inset)) self._current = set(inset) self._original = set(inset) # cache into container class if config.get_cache_level() >= config.CACHE_OBJECTS: self.__class__._cache[self._id] = self # Return True if the data are already initialized return inset is not None def add(self, items): """ Add an item or a list of items to the container """ # Convert to set representation if isinstance(items, list): items = set(items) else: items = set([items]) # If there are any new items add_items = items - self._items if add_items: log.info("Adding {0} to {1}'s {2}".format( listed([item.identifier for item in add_items], self._class.__name__, max=10), self._object.identifier, self.__class__.__name__)) self._items.update(items) if config.get_cache_level() != config.CACHE_NONE: self._modified = True else: self._update() def remove(self, items): """ Remove an item or a list of items from the container """ # Convert to set representation if isinstance(items, list): items = set(items) else: items = set([items]) # If there are any items to be removed remove_items = items.intersection(self._items) if remove_items: log.info("Removing {0} from {1}'s {2}".format( listed([item.identifier for item in remove_items], self._class.__name__, max=10), self._object.identifier, self.__class__.__name__)) self._items.difference_update(items) if config.get_cache_level() != config.CACHE_NONE: self._modified = True else: self._update() def clear(self): """ Remove all items from the container """ self.remove(list(self._items)) def _add(self, items): """ Add provided items to the server """ raise TCMSError("To be implemented by respective class.") def _remove(self, items): """ Remove provided items from the server """ raise TCMSError("To be implemented by respective class.") def _update(self): """ Update container changes to the server """ # Added items added = self._current - self._original if added: self._add(added) # Removed items removed = self._original - self._current if removed: self._remove(removed) # Save the current state as the original (for future updates) self._original = set(self._current) def _sleep(self): """ Prepare container items for caching """ # When restoring the container from the cache, unpickling failed # because of trying to construct set() of objects which were not # fully rebuild yet (__hash__ failed because of missing self._id). # So we need to convert containers into list of ids before the # cache dump and instantiate the objects back after cache restore. if self._current is TCMSNone: return self._original = [item.id for item in self._original] self._current = [item.id for item in self._current] def _wake(self): """ Restore container object after loading from cache """ # See _sleep() method above for explanation why this is necessary if self._current is TCMSNone: return if self._class._is_cached(list(self._original)): log.cache("Waking up {0} for {1}".format(self.__class__.__name__, self._identifier)) self._original = set([self._class(id) for id in self._original]) self._current = set([self._class(id) for id in self._current]) else: log.cache("Skipping wake up of {0} for {1}".format( self.__class__.__name__, self._identifier)) self._init()