Source code for oyProjectManager.core.models

# -*- coding: utf-8 -*-
# Copyright (c) 2009-2012, Erkan Ozgur Yilmaz
# 
# This module is part of oyProjectManager and is released under the BSD 2
# License: http://www.opensource.org/licenses/BSD-2-Clause
from copy import copy

import os
import re
import logging
import jinja2

from sqlalchemy import (orm, Column, String, Integer, Float, Enum, ForeignKey,
                        UniqueConstraint, Boolean)
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import synonym_for
from sqlalchemy.orm import relationship, synonym, backref
from sqlalchemy.orm.mapper import validates
from sqlalchemy.ext.hybrid import Comparator, hybrid_property
from sqlalchemy.schema import Table

from oyProjectManager import utils
from oyProjectManager.utils import cache
from oyProjectManager import db, conf
from oyProjectManager.db.declarative import Base
from oyProjectManager.core.errors import CircularDependencyError

# disable beaker DEBUG messages
logger = logging.getLogger('beaker.container')
logger.setLevel(logging.WARNING)

# create a logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.WARNING)

[docs]class Repository(object): """Repository class gives information about the repository and projects in that repository. The Repository class helps: * Get a list of project names in the current repository * Find server paths * and some auxiliary things like: * convert the given path to repository relative path which contains the environment variable key in the repository path. In the current design of the system there can only be one repository where all the projects are saved on. It is a little bit hard or adventurous to build a system which supports multiple repositories. .. note:: In future may there be support for multiple repositories by using repository specific environment variables, like $REPO1 for repository in the first index of the config.repository settings and $REPO2 for the second and etc. But in the current design it was a little bit an overkill to add this support. .. warning:: The repository setting (``repository``) in the users own config.py file is useless for getting the repository path. It is the $REPO environment variable that oyProjectManager uses. The ``repository`` setting in the ``config.py`` is there to be able replace the path values for one operating system in another, for example, think that a path for a texture file is set to "/mnt/Projects/TestProject/Texture1". This is obviously a path for OSX or linux, but what happens when you are under Windows and open the file, in this case oyProjectManager will try to replace the path with the environment variable by checking if the path matches any of the oses repository path settings and it will reproduce the path as "$REPO/TestProject" in case the repository settings is "/mnt/Projects" for OSX. There are no parameters that needs to be set to initialize a Repository instance. """
[docs] def __init__(self): logger.debug("initializing repository instance") # get the config from oyProjectManager import conf self.conf = conf self._server_path = "" self._windows_path = "" self._osx_path = "" self._linux_path = "" self._project_names = [] self._validate_repository_env_key() # ----------------------------------------------------- # read the repository settings and assign the defaults try: self._windows_path = \ self.conf.repository["windows_path"].replace("\\", "/") except AttributeError: pass try: self._linux_path = \ self.conf.repository["linux_path"].replace("\\", "/") except AttributeError: pass try: self._osx_path = \ self.conf.repository["osx_path"].replace("\\", "/") except AttributeError: pass logger.debug("finished initializing repository instance")
def _validate_repository_env_key(self): """validates the repository env key environment variable """ # raise a RuntimeError if no REPO environment var is set if not os.environ.has_key(self.conf.repository_env_key): raise RuntimeError("Please set an environment variable with the " "name %s and set it to your repository path" % self.conf.repository_env_key) if os.environ[self.conf.repository_env_key] == "": raise ValueError("The %s environment variable can not be an " "empty string" % self.conf.repository_env_key) # @property # @bCache.cache() @cache.CachedMethod @property def project_names(self): """returns a list of project names """ self.update_project_list() return self._project_names
[docs] def update_project_list(self): """updates the project list variable """ logger.debug("updating projects list") try: self._project_names = [] child_folders = utils.getChildFolders(self.server_path) for folder in child_folders: # check if the .metadata.db file exists under the folder if os.path.exists( os.path.join( self.server_path, folder, self.conf.database_file_name ) ): # it should be a valid project self._project_names.append(folder) self._project_names.sort() except IOError: logger.warning("server path doesn't exists, %s" % self.server_path)
@property
[docs] def server_path(self): """The server path """ return os.path.expandvars( os.path.expandvars( os.path.expanduser( os.environ[self.conf.repository_env_key] ) ) )
@property def linux_path(self): return self._linux_path.replace("\\", "/") @property
[docs] def windows_path(self): """The windows path of the jobs server """ return self._windows_path.replace("\\", "/")
@property
[docs] def osx_path(self): """The osx path of the jobs server """ return self._osx_path.replace("\\", "/")
[docs] def get_project_name(self, file_path): """Returns the project name from the given path or full path. Calculates the project name from the given file or folder full path. It returns None if it can not get a suitable name. :param str file_path: The file or folder path. :returns: Returns a string containing the name of the project :rtype: str """ #assert(isinstance(file_path, (str, unicode))) if file_path is None: return None file_path = os.path.expandvars( os.path.expanduser( os.path.normpath(file_path) ) ).replace("\\", "/") if not file_path.startswith(self.server_path.replace("\\", "/")): return None residual = file_path[len(self.server_path.replace("\\", "/"))+1:] parts = residual.split("/") if len(parts) > 1: return parts[0] return None
[docs] def relative_path(self, path): """Converts the given path to repository relative path. If "M:/JOBs/EXPER/_PROJECT_SETUP_" is given it will return "$REPO/EXPER/_PROJECT_SETUP_" """ return path.replace(self.server_path, "$" + self.conf.repository_env_key)
[docs]class Project(Base): """Manages project related data. The Project class is in the center of the Asset Management system. Everything starts with the Project instance. **Creating a Project** All Projects have their own folder structure in the repository. Creating a :class:`~oyProjectManager.core.models.Project` instance is not enough to physically create the project folder structure. To make it happen the :meth:`~oyProjectManager.core.models.Project.create` should be called to finish the creation process. This will both create the project folder and the general structure of the project and a ``.metadata.db`` file. Any Project, which has a ``.metadata.db`` file (thus a folder with a name of ``Project.name``) considered an existing Project and ``Project.exists`` returns ``True``. A Project can not be created without a `name` or with a name which is None or with an invalid name. For example, a project with name "'^+'^" can not be created because the name will become an empty string after the name validation process. The name of the project can freely be changed, but the path of the project will not change after the name of the project is changed. :param name: The name of the project. Should be a string or unicode. Name can not be None, a TypeError will be raised when it is given as None, can not be an empty string, a ValueError will be raised when it is an empty string. The given project name is validated against the following rules: * The name can only have a-z, A-Z, 0-9 and "-_" characters, all the other characters will be filtered out. * The name can only start with literals, no spaces, no numbers or any other character is not allowed. * Numbers and underscores are only be allowed if they are not the first letter. :param code: The code of the project. Should be a string or unicode. If given as None it will be generated from the :attr:`~oyProjectManager.core.models.Project.name` attribute. If it an empty string or become an empty string after validation a ValueError will be raised. * The name can only have A-Z and 0-9 and "_" characters, all the other chars are going to be filtered out. * The name can only start with literals, no spaces, no numbers or any other character is not allowed. * Numbers and underscores are only be allowed if they are not the first letter. * All the letters should be upper case. * All the minus ("-") signs will be converted to underscores ("_") * All the CamelCase formatting are expanded to underscore (CAMEL_CASE) The :attr:`~oyProjectManager.core.models.Project.code` is a read only attribute. :param int fps: The frame rate in frame per second format. It is an integer. The default value is 25. It can be skipped. If set to None. The default value will be used. """ __tablename__ = "Projects" __table_args__ = ( {"extend_existing":True} ) id = Column(Integer, primary_key=True) active = Column(Boolean, default=True) # TODO: add doc strings for all the attributes name = Column(String(256), unique=True) _code = Column(String(256), unique=True) description = Column(String) shot_number_prefix = Column(String(16)) shot_number_padding = Column(Integer) rev_number_prefix = Column(String(16)) rev_number_padding = Column(Integer) ver_number_prefix = Column(String(16)) ver_number_padding = Column(Integer) fps = Column( Integer, doc="""The frames per second setting of this project. The default value is 25 """ ) width = Column(Integer) height = Column(Integer) pixel_aspect = Column(Float) #structure = Column(PickleType) structure = Column(String) sequences = relationship( "Sequence", primaryjoin="Sequences.c.project_id==Projects.c.id" ) client_id = Column(Integer, ForeignKey("Clients.id")) client = relationship( "Client", primaryjoin="Projects.c.client_id==Clients.c.id" ) def __new__(cls, name=None, code=None, client=None): """the overridden __new__ method to manage the creation of a Project instances. If the Project is created before then calling Project() for a second time, may be in another Python session will return the Project instance from the database. """ # check the name argument if name: # condition the name name = Project._condition_name(name) # now get the instance from the db if db.session is None: # create the session first logger.debug("db.session is None, setting up a new session") db.setup() proj_db = Project.query().filter_by(name=name).first() if proj_db is not None: # return the database instance logger.debug("found the project in the database") logger.debug("returning the Project instance from the " "database") # skip the __init__ proj_db.__skip_init__ = None from oyProjectManager import conf proj_db.conf = conf return proj_db else: logger.debug("Project doesn't exists") # just create it normally logger.debug("returning a normal Project instance") return super(Project, cls).__new__(cls, name, code, client)
[docs] def __init__(self, name, code=None, client=None): # do not initialize if it is created from the DB if hasattr(self, "__skip_init__"): logging.debug("skipping the __init__ on Project") return logger.debug("initializing the Project") # get the config from oyProjectManager import conf self.conf = conf self.repository = Repository() self.name = name if code is None: code = self.name self._code = self._condition_code(code) self.shot_number_prefix = self.conf.shot_number_prefix self.shot_number_padding = self.conf.shot_number_padding self.rev_number_prefix = self.conf.rev_number_prefix self.rev_number_padding = self.conf.rev_number_padding self.ver_number_prefix = self.conf.ver_number_prefix self.ver_number_padding = self.conf.ver_number_padding # set the default resolution default_resolution_key = conf.default_resolution_preset default_resolution = conf.resolution_presets[default_resolution_key] self.fps = self.conf.default_fps self.width = default_resolution[0] self.height = default_resolution[1] self.pixel_aspect = default_resolution[2] # and the structure self.structure = self.conf.project_structure # add the client self.client = client
@orm.reconstructor def __init_on_load__(self): """init when loaded from the db """ self.repository = Repository() from oyProjectManager import conf self.conf = conf self._sequenceList = [] self._exists = None def __str__(self): """the string representation of the project """ return self.name def __eq__(self, other): """equality of two projects """ return isinstance(other, Project) and self.name == other.name @classmethod def _condition_name(cls, name): if name is None: raise TypeError("The Project.name can not be None") if not isinstance(name, (str, unicode)): raise TypeError("Project.name should be an instance of string or " "unicode not %s" % type(name)) if name is "": raise ValueError("The Project.name can not be an empty string") # strip the name name = name.strip() # remove unnecessary characters from the string name = re.sub("([^a-zA-Z0-9\s_\-]+)", r"", name) # remove all the characters from the beginning which are not alphabetic name = re.sub("(^[^a-zA-Z]+)", r"", name) # check if the name became empty string after validation if name is "": raise ValueError("The Project.name is not valid after validation") return name @classmethod def _condition_code(cls, code): if code is None: raise TypeError("The Project.code can not be None") if not isinstance(code, (str, unicode)): raise TypeError("Project.code should be an instance of string or " "unicode not %s" % type(code)) if code is "": raise ValueError("The Project.code can not be an empty string") # strip the code code = code.strip() # convert all the "-" signs to "_" code = code.replace("-", "_") # replace camel case letters code = re.sub(r"(.+?[a-z]+)([A-Z])", r"\1_\2", code) # remove unnecessary characters from the string code = re.sub("([^a-zA-Z0-9\s_]+)", r"", code) # remove all the characters from the beginning which are not alphabetic code = re.sub("(^[^a-zA-Z]+)", r"", code) # substitute all spaces with "_" characters code = re.sub("([\s])+", "_", code) # convert it to upper case code = code.upper() # check if the code became empty string after validation if code is "": raise ValueError("The Project.code is not valid after validation") return code @validates("name") def _validate_name(self, key, name_in): """validates the given name_in value """ name_in = self._condition_name(name_in) return name_in def _validate_code(self, code): """validates the given code_in value """ if code is None: code = self.name if not isinstance(code, (str, unicode)): raise TypeError("Project.code should be an instance of string or " "unicode not %s" % type(code)) if code is "": raise ValueError("Project.code can not be an empty string") code = self._condition_code(code) # check if the code became empty string after validation if code is "": raise ValueError("Project.code is not valid after validation") return code
[docs] def save(self): """Saves the Project related information to the database. If there is no ``.metadata.db`` file it will be created, but be careful that the project structure will not be created. The safest way to both create the project structure and the .metadata.db file is to call the :meth:`~oyProjectManager.core.models.Project.create` method. """ logger.debug("saving project settings to %s" % db.database_url) # create the database if db.session is None: logger.debug("there is no session, creating a new one") db.setup() if self not in db.session: db.session.add(self) db.session.commit()
[docs] def create(self): """Creates the project directory structure and saves the project, thus creates the ``.metadata.db`` file in the repository. """ # check if the folder already exists utils.mkdir(self.full_path) # create the structure if it is not present rendered_structure = jinja2.Template(self.structure).\ render(project=self) folders = rendered_structure.split("\n") if len(folders): for folder in rendered_structure.split("\n"): try: utils.createFolder(folder.strip()) except OSError: pass self._exists = True self.save()
@property
[docs] def path(self): """The path of this project instance. Basically it is the same value with what $REPO env variable holds """ return self.repository.server_path
@property
[docs] def full_path(self): """The full_path of this project instance. """ return os.path.join(self.path, self.code)
@synonym_for("_code") @property
[docs] def code(self): """Returns the code of this Project instance. The ``code`` attribute is read-only. :return: str """ return self._code
@validates("client") def _validate_client(self, key, client): """validates the given client value """ if client is not None: if not isinstance(client, Client): raise TypeError('Project.client should be an ' 'oyProjectManager.core.models.Client ' 'instance, not %s' % client.__class__.__name__) return client
[docs]class Sequence(Base): """Sequence object to help manage sequence related data. By definition a Sequence is a group of :class:`~oyProjectManager.core.models.Shot`\ s. The class should be initialized with a :class:`~oyProjectManager.core.models.Project` instance and a sequenceName. When a Sequence instance is created it is not persisted in the project database. To do it the :meth:`~oyProjectManager.core.models.Sequence.save` should be called. The structure of the Sequence should be created by calling its :meth:`~oyProjectManager.core.models.Sequence.create` method after it is saved. Two sequences are considered to be the same if their name and their project names are matching. :param project: The owner :class:`~oyProjectManager.core.models.Project`. A Sequence instance can not be created without a proper :class:`~oyProjectManager.core.models.Project` instance passed to it with the ``project`` argument. :type project: :class:`~oyProjectManager.core.models.Project` or string :param str name: The name of the sequence. It is heavily formatted. Follows the same naming rules with the :class:`~oyProjectManager.core.models.Project`. """ __tablename__ = "Sequences" __table_args__ = ( UniqueConstraint("code", "project_id"), UniqueConstraint("name", "project_id"), {"extend_existing":True} ) id = Column(Integer, primary_key=True) name = Column(String(256)) code = Column(String(256)) description = Column(String) project_id = Column(Integer, ForeignKey("Projects.id")) _project = relationship("Project") shots = relationship("Shot") def __new__(cls, project=None, name=None, code=None): """the overridden __new__ method to manage the creation of Sequence instances. If the Sequence is created before then calling Sequence() for a second time, may be in another Python session will return the Sequence instance from the database. """ if project and name: project = Sequence._check_project(project) # condition the name name = Sequence._condition_name(name) # now get it from the database seq_db = db.session.query(Sequence).\ filter_by(name=name).first() if seq_db is not None: logger.debug("found the sequence in the database") logger.debug("returning the Sequence instance from the " "database") seq_db.__skip_init__ = None return seq_db else: logger.debug("the Sequence should be new, there is no such " "Sequence in the database") # in any other case just return the normal __new__ logger.debug("returning a normal Sequence instance") return super(Sequence, cls).__new__(cls, project, name, code)
[docs] def __init__(self, project, name, code=None): # skip initialization if this is coming from DB if hasattr(self, "__skip_init__"): return logger.debug("initializing the Sequence") self._project = self._check_project(project) logger.debug("id(project.session): %s" % id(db.session)) # self.session = self.project.session # logger.debug("id(sequence.session): %s" % id(self.session)) self.name = name if code is None: code = name self.code = code self._exists = False
@orm.reconstructor def __init_on_load__(self): """method that will run for database loaded instances """ # self.session = self.project.session # def create(self): # """creates the sequence structure by calling self.save() and then a # self.project.create() # """ # self.save() # self.project.create()
[docs] def create(self): """creates the sequence structure """ # create the sequence structure by calling the self.project.create self.project.create()
[docs] def save(self): """persists the sequence in the database """ logger.debug("saving self to the database") # there should be a session # because a Sequence can not be created # without an already created Project instance if self not in db.session: db.session.add(self) db.session.commit()
[docs] def add_shots(self, shot_range_formula): """adds new shots to the sequence shot_range_formula should be a range in on of the following format: # #,# #-# (not supported anymore) #,#-# (not supported anymore) #,#-#,# (not supported anymore) #-#,# (not supported anymore) etc. """ ## for now consider the shot_range_formula as a string of range ## do the hard work later # #new_shot_numbers = utils.uncompress_range(shot_range_formula) # ## convert the list to strings #new_shot_numbers = map(str, new_shot_numbers) new_shot_numbers = shot_range_formula.split(",") new_shots = [] for shot_number in new_shot_numbers: # the shot_number can not start with the project.shot_number_prefix if shot_number.startswith(self.project.shot_number_prefix): # remove it shot_number = re.sub( r"^[" + self.project.shot_number_prefix + "]+", "", shot_number ) # if the shot_number starts with zeros ('0000') remove them shot_number = re.sub(r'^[0]+', '', shot_number) # create a new shot instance new_shots.append(Shot(self, shot_number)) db.session.add_all(new_shots) db.session.commit()
[docs] def add_alternative_shot(self, shot_number): """adds a new alternative to the given shot returns the alternative shot number """ # TODO: this functionality should be shifted to the Shot class # shot_number could be an int convert it to str # get the first integer as int in the string shot_number = utils.embedded_numbers(str(shot_number))[1] # get the next available alternative shot number for that shot alternative_shot_number = \ self.get_next_alternate_shot_number(shot_number) # add that alternative shot to the shot list if alternative_shot_number is not None: new_alternative_shot = Shot(self, alternative_shot_number) db.session.add(new_alternative_shot) db.session.commit() return alternative_shot_number
[docs] def get_next_alternate_shot_number(self, shot_number): """returns the next alternate shot_number number for the given shot_number number """ # get the shot_number list alternate_letters = 'ABCDEFGHIJKLMNOPRSTUVWXYZ' for letter in alternate_letters: #check if the alternate is in the list new_shot_number = str(shot_number) + letter shot_from_db = Shot.query().\ filter(Shot.sequence_id==self.id).\ filter(Shot.number==new_shot_number).\ first() if not shot_from_db: return new_shot_number return None
def __eq__(self, other): """The equality operator """ return isinstance(other, Sequence) and other.name == self.name and\ other.project == self.project def __ne__(self, other): """The in equality operator """ return not self.__eq__(other) @validates("name") def _validate_name(self, key, name): """validates the given name """ name = Project._condition_name(name) return name @classmethod def _check_project(cls, project): """A convenience function which checks the given project argument value It is a ``classmethod``, so can be called both in ``__new__`` and other methods like ``_validate_project``. Checks the given project for a couple of conditions, like being None or not being an Project instance etc. """ if project is None: raise TypeError("Sequence.project can not be None") if not isinstance(project, Project): raise TypeError("The project should be and instance of " "oyProjectManager.core.models.Project") return project @synonym_for("_project") @property
[docs] def project(self): """a read-only attribute to return the related Project of this Sequence instance """ return self._project
@validates("code") def _validate_code(self, key, code): """validates the given code value """ if code is None: code = self.name if not isinstance(code, (str, unicode)): raise TypeError("Sequence.code should be an instance of str or " "unicode, not %s" % type(code)) code = Project._condition_code(code) return code @classmethod def _condition_name(cls, name): """Formats the given name value :param name: The name value to be conditioned :return: str """ if name is None: raise TypeError("The Sequence.name can not be None") if not isinstance(name, (str, unicode)): raise TypeError("Sequence.name should be an instance of string or " "unicode not %s" % type(name)) if name is "": raise ValueError("The Sequence.name can not be an empty string") name = Project._condition_name(name)
[docs]class VersionableBase(Base): """A base class for :class:`~oyProjectManager.core.models.Shot` and :class:`~oyProjectManager.core.models.Asset` classes. It will supply the base attributes to be able to attach a :class:`~oyProjectManager.core.models.Version` to the :class:`~oyProjectManager.core.models.Shot` and :class:`~oyProjectManager.core.models.Asset` instances. It doesn't need any parameter while initialization. It supplies only one read-only attribute called :attr:`~oyProjectManager.core.models.VersionableBase.versions` which is a list and holds :class:`~oyProjectManager.core.models.Version` instances. """ __tablename__ = "Versionables" __table_args__ = ( UniqueConstraint("_code", "project_id"), UniqueConstraint("_name", "project_id"), {"extend_existing":True} ) versionable_type = Column(String(64), nullable=False) __mapper_args__ = { "polymorphic_on": versionable_type, "polymorphic_identity": "VersionableBase" } id = Column(Integer, primary_key=True) _versions = relationship("Version") project_id = Column( Integer, ForeignKey("Projects.id"), nullable=False ) _project = relationship( "Project", cascade="all" ) _code = Column( String(128), doc="""The nicely formatted version of the :attr:`~oyProjectManager.core.models.Asset.name` attribute or :attr:`~oyProjectManager.core.models.Shot.number` attribute. It will be overloaded in the :class:`~oyProjectManager.core.models.Asset` or :class:`~oyProjectManager.core.models.Shot` class. """ ) _name = Column(String(128)) description = Column(String) @synonym_for("_versions") @property
[docs] def versions(self): """the Version instances attached to this object It is a read-only attribute """ return self._versions
@synonym_for("_project") @property
[docs] def project(self): """the Project instance which this object is related to It is a read-only attribute """ return self._project
@validates("description") def _validate_description(self, key, description): """validates the given description value """ if not isinstance(description, (str, unicode)): raise TypeError("Asset.description should be an instance of " "string or unicode") return description @property
[docs] def thumbnail_full_path(self): """returns the thumbnail full path for this versionable """ # just render a thumbnail path template_vars = {} # define the template for the versionable type (asset or shot) path_template = '' filename_template = '' if isinstance(self, Asset): path_template = jinja2.Template(conf.asset_thumbnail_path) filename_template = jinja2.Template(conf.asset_thumbnail_filename) template_vars.update( { "project": self.project, "asset": self, "extension": conf.thumbnail_format } ) elif isinstance(self, Shot): path_template = jinja2.Template(conf.shot_thumbnail_path) filename_template = jinja2.Template(conf.shot_thumbnail_filename) template_vars.update( { "project": self.project, "sequence": self.sequence, "shot": self, "extension": conf.thumbnail_format } ) # render the templates path = path_template.render(**template_vars) filename = filename_template.render(**template_vars) # the path should be $REPO relative thumbnail_full_path = os.path.join( os.environ[conf.repository_env_key], path, filename ).replace('\\', '/') return thumbnail_full_path
[docs]class Shot(VersionableBase): """The class that enables the system to manage shot data. .. note:: There is a design flaw, which I've recognized at the day I'll release version 0.2.0. The ``_code`` of the Shot is not stored in the database, whereas the ``_code`` of a the Asset is. So one can not query Shot's by using the ``_code`` attribute, but it is easy to get the same effect by using the ``number`` attribute. So you need to create you queries with ``number`` instead of ``_code``. I hope I'll fix it in a later version. The total of the handle attributes should not be bigger then duration-1, in which results no frame for the real shot. :param sequence: The :class:`~oyProjectManager.core.models.Sequence` instance that this Shot should belong to. The Sequence may not be created yet. Skipping it or passing None will raise TypeError, and anything other than a :class:`~oyProjectManager.core.models.Sequence` will raise a TypeError. :param number: A string or integer holding the number of this shot. Can not be None or can not be skipped, a TypeError will be raised either way. It will be used to create the :attr:`~oyProjectManager.core.models.Shot.code` attribute. :param start_frame: The start frame of this shot. Should be an integer, any other type will raise TypeError. The default value is 1 and skipping it will result the start_frame to be set to 1. :param end_frame: The end frame of this shot. Should be an integer, any other type will raise TypeError. The default value is 1 and skipping it will result the end_frame to be set to 1. :param description: A string holding the short description of this shot. Can be skipped. """ __tablename__ = "Shots" __table_args__ = ( UniqueConstraint("sequence_id", "number"), {"extend_existing":True} ) __mapper_args__ = {"polymorphic_identity": "Shot"} shot_id = Column("id", Integer, ForeignKey("Versionables.id") ,primary_key=True) number = Column(String) start_frame = Column(Integer, default=1) end_frame = Column(Integer, default=1) # TODO: create tests for handles handle_at_start = Column(Integer, default=0) handle_at_end = Column(Integer, default=0) sequence_id = Column(Integer, ForeignKey("Sequences.id")) _sequence = relationship( "Sequence", primaryjoin="Shots.c.sequence_id==Sequences.c.id" )
[docs] def __init__(self, sequence, number, start_frame=1, end_frame=1, description=''): self._sequence = self._validate_sequence(sequence) # update the project attribute self._project = self._sequence.project self.number = number self.description = description self._duration = 1 self.start_frame = start_frame self.end_frame = end_frame self.handle_at_start = 0 self.handle_at_end = 0 #self._cutSummary = ''
def __eq__(self, other): """the equality operator """ return isinstance(other, Shot) and other.number == self.number and \ other.sequence == self.sequence def __ne__(self, other): """the inequality operator """ return not self.__eq__(other) def __str__(self): """returns the string representation of the object """ return self.code def __repr__(self): """returns the representation of the class """ return "< oyProjectManager.core.models.Shot object: " + self.code + ">" def _validate_sequence(self, sequence): """validates the given sequence value """ if sequence is None: raise TypeError("Shot.sequence can not be None") if not isinstance(sequence, Sequence): raise TypeError("Shot.sequence should be an instance of " "oyProjectManager.core.models.Sequence") return sequence @validates("description") def _validate_description(self, key, description): """validates the given description value """ if description is None: description = "" if not isinstance(description, (str, unicode)): raise TypeError("Shot.description should be an instance of str " "or unicode") return description @validates("start_frame") def _validate_start_frame(self, key, start_frame): """validates the given start_frame value """ if start_frame is None: start_frame = 1 if not isinstance(start_frame, int): raise TypeError("Shot.start_frame should be an instance of " "integer") if self.end_frame is not None: self._update_duration(start_frame, self.end_frame) return start_frame @validates("end_frame") def _validate_end_frame(self, key, end_frame): """validates the given end_frame value """ if end_frame is None: end_frame = 1 if not isinstance(end_frame, int): raise TypeError("Shot.end_frame should be an instance of " "integer") if self.end_frame is not None: self._update_duration(self.start_frame, end_frame) return end_frame def _update_duration(self, start_frame, end_frame): """updates the duration """ self._duration = end_frame - start_frame + 1 @synonym_for("_sequence") @property
[docs] def sequence(self): """The sequence of the current Shot instance. :returns: :class:`~oyProjectManager.core.models.Sequence` """ return self._sequence
@property
[docs] def duration(self): """the duration """ self._update_duration(self.start_frame, self.end_frame) return self._duration
@validates("number") def _validates_number(self, key, number): """validates the given number value """ if not isinstance(number, (int, str, unicode)): raise TypeError("Shot.number should be and instance of integer, " "string or unicode") # first convert it to a string number = str(number) # then format it # remove anything which is not a number or letter number = re.sub(r"[^0-9a-zA-Z\-]+", "", number) number = number.upper() if number == "": raise ValueError("Shot.number is not in good format, please " "supply something like 1, 2, 3A, 10B") # now check if the number is present for the current Sequence shot_instance = db.session.query(Shot).\ filter(Shot.number==number).\ filter(Shot.sequence_id==self.sequence.id).\ first() if shot_instance is not None: raise ValueError("Shot.number already exists for the given " "sequence please give a unique shot code") return number
[docs] def save(self): """commits the shot to the database """ logger.debug("saving shot to the database") if self not in db.session: db.session.add(self) db.session.commit()
@synonym_for("_code") @property
[docs] def code(self): """Returns the code of this shot by composing the :attr:`~oyProjectManager.core.models.Shot.number` with the :attr:`~oyProjectManager.core.models.Project.shot_prefix` attribute of the :class:`~oyProjectManager.core.models.Project` :: >> shot.number "1" >> shot.code "SH001" >> shot.number "12A" >> shot.code "SH012A" """ # TODO: there is a weird situation here need to fix it later by # introducing a new variable to the Project if "-" in self.number: return self.project.shot_number_prefix + self.number else: number = re.sub(r"[A-Z]+", "", self.number) alter = re.sub(r"[0-9]+", "", self.number) return self.project.shot_number_prefix +\ number.zfill(self.project.shot_number_padding) + alter
@validates("handle_at_start") def _validate_handles_at_start(self, key, handle_at_start): """validates the given handle_at_start value """ if not isinstance(handle_at_start, int): raise TypeError("Shot.handle_at_start should be an instance of " "integer not %s" % type(handle_at_start)) if handle_at_start < 0: raise ValueError("Shot.handle_at_start can not be a negative " "value") # the total count of the handles can not be bigger than duration-1 if self.handle_at_end is not None: if handle_at_start + self.handle_at_end > self.duration - 1: raise ValueError("Shot.handle_at_start + Shot.handle_at_end " "can not be bigger than Shot.duration - 1") return handle_at_start @validates("handle_at_end") def _validate_handle_at_end(self, key, handle_at_end): """validates the given handle_at_end value """ if not isinstance(handle_at_end, int): raise TypeError("Shot.handle_at_end should be an instance of " "integer not %s" % type(handle_at_end)) if handle_at_end < 0: raise ValueError("Shot.handle_at_end can not be a negative " "value") # the total count of the handles can not be bigger than duration-1 if self.handle_at_start is not None: if self.handle_at_start + handle_at_end > self.duration - 1: raise ValueError("Shot.handle_at_start + Shot.handle_at_end " "can not be bigger than Shot.duration - 1") return handle_at_end
[docs]class Asset(VersionableBase): """Manages Assets in a given :class:`~oyProjectManager.core.models.Project` Assets are the data created to finish a :class:`~oyProjectManager.core.models.Project`. It can be a Character or a Vehicle or anything that participate in to a :class:`~oyProjectManager.core.models.Shot`. Assets have :class:`~oyProjectManager.core.models.Versions`\ s to hold every change made to that asset file. The name attribute will be copied to the code attribute if the code argument is None or an empty string. :param project: The :class:`~oyProjectManager.core.models.Project` instance that this Asset belongs to. It is not possible to initialize an Asset without defining its :class:`~oyProjectManager.core.models.Project`. :param name: The name of this asset. It can not be None or an empty string. Anything is possible to be used as a name but it is recommended to keep it brief. The name attribute will be formatted and the result will be copied to the :attr:`~oyProjectManager.core.models.Asset.code` attribute. The name should be unique among all the asset in the current :class:`~oyProjectManager.core.models.Project`. The following rules will apply for the formatting of the name: * Spaces are allowed. * It should start with an upper case letter (A-Z) * Only the following characters are allowed (-\_ a-zA-Z0-9) :param code: The code of this asset. If it is given as None or empty string the value will be get from the name attribute. The following rules will apply for the formatting of the code: * No spaces are allowed, all the spaces will be replaced with "_" (underscore) characters * It should start with upper case letter (A-Z) * Only the following characters are allowed (a-zA-Z0-9\_) * All the "-" (minus) signs are converted to "_" (under score) If the code becomes an empty string after formatting a ValueError will be raised. The code should be unique among all the Assets in the current :class:`~oyProjectManager.core.models.Project`. :param type: The ``type`` of this asset. Can be used to distinguish different types of assets like 'Prop', 'Character', 'Vehicle' etc. If given as None the default value (default_asset_type_name) from the config will be used. """ __tablename__ = "Assets" __table_args__ = ( {"extend_existing":True} ) __mapper_args__ = {"polymorphic_identity": "Asset"} asset_id = Column("id", Integer, ForeignKey("Versionables.id"), primary_key=True) type = Column(String(256))
[docs] def __init__(self, project, name, code=None, type=None): self._project = project self.name = name self.code = code self.type = type
def __eq__(self, other): """equality operator """ return isinstance(other, Asset) and self.name == other.name and \ self.project==other.project def __ne__(self, other): return not self.__eq__(other) def __repr__(self): """the string representation of the object """ return "<Asset, %s in %s>" % (self.name, self.project.name) def _validate_code(self, code): """validates the given code value """ if code is None: code = self.name if not isinstance(code, (str, unicode)): raise TypeError("Asset.code should be an instance of string or " "unicode not %s" % type(code)) if code is "": raise ValueError("The Asset.code can not be an empty string") # strip the code code = code.strip() # remove unnecessary characters from the string code = re.sub("([^a-zA-Z0-9\s_]+)", r"", code) # remove all the characters from the beginning which are not alphabetic code = re.sub("(^[^a-zA-Z0-9]+)", r"", code) # substitute all spaces with "_" characters code = re.sub("([\s])+", "_", code) # check if the code became empty string after validation if code is "": raise ValueError("Asset.code is not valid after validation") # convert the first letter to uppercase code = code[0].upper() + code[1:] return code def _code_getter(self): """The nicely formatted version of the :attr:`~oyProjectManager.core.models.Asset.name` attribute """ return self._code def _code_setter(self, code): """Sets the code of this Asset instance """ self._code = self._validate_code(code) code = synonym( "_code", descriptor=property( _code_getter, _code_setter ) ) def _validate_name(self, name): """validates the given name value """ if name is None: raise TypeError("") if not isinstance(name, (str, unicode)): raise TypeError("Asset.name should be an instance of string or " "unicode not %s" % type(name)) if name is "": raise ValueError("The Asset.name can not be an empty string") # strip the name name = name.strip() # remove unnecessary characters from the string name = re.sub("([^a-zA-Z0-9\s_-]+)", r"", name) # remove all the characters from the beginning which are not alphabetic name = re.sub("(^[^a-zA-Z0-9]+)", r"", name) # # substitute all spaces with "_" characters # name = re.sub("([\s])+", "_", name) # check if the name became empty string after validation if name == "": raise ValueError("Asset.name is not valid after validation") # convert the first letter to uppercase name = name[0].upper() + name[1:] return name def _name_getter(self): """getter for the name attribute """ return self._name def _name_setter(self, name): """setter for the name attribute """ name = self._validate_name(name) self._name = name name = synonym( "_name", descriptor=property( _name_getter, _name_setter, doc="""The name of this Asset instance, try to be brief.""" ) )
[docs] def save(self): """saves the asset to the related projects database """ if self not in db.session: logger.debug("adding %s to the session" % self) db.session.add(self) logger.debug("saving the asset %s" % self) db.session.commit()
@validates('type') def _validate_type(self, key, type): """validates the given type value """ if type is None: from oyProjectManager import conf type = conf.default_asset_type_name if not isinstance(type, (str, unicode)): raise TypeError('Asset.type should be an instance of string or ' 'unicode not %s' % type.__class__.__name__) # strip the code type = type.strip() # remove unnecessary characters from the string type = re.sub("([^a-zA-Z0-9\s_]+)", r"", type) # remove all the characters from the beginning which are not alphabetic type = re.sub("(^[^a-zA-Z0-9]+)", r"", type) # substitute all spaces with "_" characters type = re.sub("([\s])+", "_", type) # check if the code became empty string after validation if type is "": raise ValueError("Asset.type is not valid after validation") # convert the first letter to uppercase type = type[0].upper() + type[1:] return type
[docs]class VersionStatusComparator(str, Comparator): """The comparator class for Version.status """ def __new__(cls, status): if isinstance(status, VersionStatusComparator): status = status.status elif isinstance(status, basestring): status_list_long_names = conf.status_list_long_names if status in status_list_long_names: index = status_list_long_names.index(status) status = conf.status_list[index] status = status obj = str.__new__(cls, status) obj.status = status return obj #def __init__(self, status): # if isinstance(status, VersionStatusComparator): # self.status = status.status # elif isinstance(status, basestring): # status_list_long_names = conf.status_list_long_names # if status in status_list_long_names: # index = status_list_long_names.index(status) # status = conf.status_list[index] # self.status = status def __eq__(self, other): if not isinstance(other, VersionStatusComparator): other = VersionStatusComparator(other) return self.__clause_element__() == other.status def __clause_element__(self): return self.status # def __set__(self, instance, value): #def __str__(self): # return self.status
[docs]class Version(Base): """Holds versions of assets or shots. In oyProjectManager a Version is the file created for an :class:`~oyProjectManager.core.models.Asset` or :class:`~oyProjectManager.core.models.Shot`\ . The placement of this file is automatically handled by the connected :class:`~oyProjectManager.core.models.VersionType` instance. The values given for :attr:`~oyProjectManager.core.models.Version.base_name` and :attr:`~oyProjectManager.core.models.Version.take_name` are conditioned as follows: * Each word in the string should start with an upper-case letter (title) * It can have all upper-case letters * CamelCase is allowed * Valid characters are ([A-Z])([a-zA-Z0-9\_]) * No white space characters are allowed, if a string is given with white spaces, it will be replaced with underscore ("_") characters. * No numbers are allowed at the beginning of the string * No leading or trailing underscore character is allowed So, with these rules are given, the examples for input and conditioned strings are as follows: * "BaseName" -> "BaseName" * "baseName" -> "BaseName" * " baseName" -> "BaseName" * " base name" -> "Base_Name" * " 12base name" -> "Base_Name" * " 12 base name" -> "Base_Name" * " 12 base name 13" -> "Base_Name_13" * ">£#>$#£½$ 12 base £#$£#$£½¾{½{ name 13" -> "Base_Name_13" * "_base_name_" -> "Base_Name" For a newly created Version the :attr:`~oyProjectManager.core.models.Version.filename` and the :attr:`~oyProjectManager.core.models.Version.path` attributes are rendered from the associated :class:`~oyProjectManager.core.models.VersionType` instance. The resultant :attr:`~oyProjectManager.core.models.Version.filename` and :attr:`~oyProjectManager.core.models.Version.path` values are stored and retrieved back from the Version instance itself, no re-rendering happens. It means, the Version class depends the :class:`~oyProjectManager.core.models.VersionType` class only at the initialization, any change made to the :class:`~oyProjectManager.core.models.VersionType` instance (like changing the :attr:`~oyProjectManager.core.models.VersionType.name` or :attr:`~oyProjectManager.core.models.VersionType.code` of the :class:`~oyProjectManager.core.models.VersionType`) will not effect the Version instances created before this change. This is done in that way to be able to freely change the :class:`~oyProjectManager.core.models.VersionType` attributes and prevent loosing the connection between a Version and a file on the repository for previously created Versions. .. versionadded:: 0.2.2 Published Versions: After v0.2.2 Versions can be set published. It is a bool attribute holding information about if this Version is published or not. :param version_of: A :class:`~oyProjectManager.core.models.VersionableBase` instance (:class:`~oyProjectManager.core.models.Asset` or :class:`~oyProjectManager.core.models.Shot`) which is the owner of this version. Can not be skipped or set to None. :type type: :class:`~oyProjectManager.core.models.Asset`, :class:`~oyProjectManager.core.models.Shot` or :class:`~oyProjectManager.core.models.VersionableBase` :param type: A :class:`~oyProjectManager.core.models.VersionType` instance which is showing the type of the current version. The type is also responsible of the placement of this Version in the repository. So the :attr:`~oyProjectManager.core.models.Version.filename` and the :attr:`~oyProjectManager.core.models.Version.path` is defined by the related :class:`~oyProjectManager.core.models.VersionType` and the :class:`~oyProjectManager.core.models.Project` settings. Can not be skipped or can not be set to None. :type type: :class:`~oyProjectManager.core.models.VersionType` :param str base_name: A string showing the base name of this Version instance. Generally used to create an appropriate :attr:`~oyProjectManager.core.models.Version.filename` and a :attr:`~oyProjectManager.core.models.Version.path` value. Can not be skipped, can not be None or empty string. :param str take_name: A string showing the take name. The default value is "MAIN" and it will be used in case it is skipped or it is set to None or an empty string. Generally used to create an appropriate :attr:`~oyProjectManager.core.models.Version.filename` and a :attr:`~oyProjectManager.core.models.Version.path` value. :param int revision_number: It is an integer number showing the client revision number. The default value is 0 and it is used when the argument is skipped or set to None. It should be an increasing number with the newly created versions. :param int version_number: An integer number showing the current version number. It should be an increasing number among the Versions with the same base_name and take_name values. If skipped a proper value will be used by looking at the previous versions created with the same base_name and take_name values from the database. If the given value already exists then it will be replaced with the next available version number from the database. :param str note: A string holding the related note for this version. Can be used for anything the user desires. :param created_by: A :class:`~oyProjectManager.core.models.User` instance showing who created this version. It can not be skipped or set to None or anything other than a :class:`~oyProjectManager.core.models.User` instance. :type created_by: :class:`~oyProjectManager.core.models.User` :param str extension: A string holding the file extension of this version. It may or may not include a dot (".") sign as the first character. :param bool is_published: A bool value defining this Version as a published one. """ # TODO: add audit info like date_created, date_updated, created_at and updated_by # TODO: add needs_update flag, primarily need to be used with renamed versions # file_size_format = "%.2f MB" # timeFormat = '%d.%m.%Y %H:%M' __tablename__ = "Versions" __table_args__ = ( UniqueConstraint("version_of_id", "take_name", "_version_number", "type_id"), {"extend_existing":True} ) id = Column(Integer, primary_key=True) version_of_id = Column(Integer, ForeignKey("Versionables.id"), nullable=False) _version_of = relationship("VersionableBase") type_id = Column(Integer, ForeignKey("VersionTypes.id")) _type = relationship("VersionType") _filename = Column(String) _path = Column(String) _output_path = Column(String) _extension = Column(String) base_name = Column(String) take_name = Column(String, default="MAIN") revision_number = Column(Integer, default=0) _version_number = Column(Integer, default=1, nullable=False) note = Column(String) created_by_id = Column(Integer, ForeignKey("Users.id")) created_by = relationship("User") references = relationship( "Version", secondary="Version_References", primaryjoin="Versions.c.id==Version_References.c.referencer_id", secondaryjoin="Version_References.c.reference_id==Versions.c.id", backref="referenced_by" ) is_published = Column(Boolean, default=False) _status = Column( Enum(*conf.status_list, name='StatusNames'), )
[docs] def __init__(self, version_of, base_name, type, created_by, take_name="MAIN", version_number=1, note="", extension="", is_published=False, status=None, ): self.is_published = is_published self._version_of = version_of self._type = type self.base_name = base_name self.take_name = take_name self.version_number = version_number self.note = note self.created_by = created_by self._filename = "" self._path = "" self._output_path = "" # setting the extension will update the path variables already self.extension = extension self.status = status
def __repr__(self): """The representation of this version """ return "<Version: %s>" % self.filename def __eq__(self, other): """the equality operator """ return isinstance(other, Version) and \ self.base_name==other.base_name and \ self.version_of==other.version_of and \ self.type==other.type and self.take_name==other.take_name and \ self.version_number==other.version_number def __ne__(self, other): """the not equal operator """ return not self.__eq__(other)
[docs] def update_paths(self): """updates the path variables """ kwargs = self._template_variables() self._filename = jinja2.Template(self.type.filename).render(**kwargs) self._path = jinja2.Template(self.type.path).render(**kwargs) self._output_path = jinja2.Template(self.type.output_path).\ render(**kwargs)
@validates("_version_of") def _validate_version_of(self, key, version_of): """validates the given version of value """ if version_of is None: raise TypeError("Version.version_of can not be None") if not isinstance(version_of, VersionableBase): raise TypeError("Version.version_of should be an Asset or Shot " "or anything derives from VersionableBase class") return version_of @synonym_for("_version_of") @property
[docs] def version_of(self): """The instance that this version belongs to. Generally it is a Shot or an Asset instance or anything which derives from VersionableBase class """ return self._version_of
@validates("_type") def _validate_type(self, key, type): """validates the given type value """ if type is None: raise TypeError("Version.type can not be None") if not isinstance(type, VersionType): raise TypeError("Version.type should be an instance of " "VersionType class") # raise a TypeError if the given VersionType is not suitable for the # given version_of instance if self.version_of.__class__.__name__ != type.type_for: raise TypeError("The VersionType instance given for Version.type " "is not suitable for the given VersionableBase " "instance, the version_of is a %s and the " "version_type is for %s" % (self.version_of.__class__.__name__, type.type_for ) ) return type def _validate_extension(self, extension): """Validates the given extension value """ if not isinstance(extension, (str, unicode)): raise TypeError("Version.extension should be an instance of " "string or unicode") if extension != "": if not extension.startswith("."): extension = "." + extension return extension def _extension_getter(self): """Returns the extension attribute value """ return self._extension def _extension_setter(self, extension): """Sets the extension attribute :param extension: The new extension should be a string or unicode value either starting with a dot sign or not. """ self._extension = self._validate_extension(extension) # now update the filename self.update_paths() extension = synonym( "_extension", descriptor=property(fget=_extension_getter, fset=_extension_setter), doc="""The extension of this version file, updating the extension will also update the filename """ ) @synonym_for("_type") @property
[docs] def type(self): """The type of this Version instance. It is a VersionType object. """ return self._type
def _template_variables(self): kwargs = { "project": self.version_of.project, "sequence": self.version_of.sequence if isinstance(self.version_of, Shot) else "", "shot": self.version_of, "asset": self.version_of, "version": self, "type": self.type, } return kwargs @synonym_for("_filename") @property
[docs] def filename(self): """The filename of this version. It is automatically created by rendering the VersionType.filename template with the information supplied with this Version instance. """ return self._filename
@synonym_for("_path") @property
[docs] def path(self): """The path of this version. It is automatically created by rendering the template in :class`~oyProjectManager.core.models.Version`\. :attr:`~oyProjectManager.core.models.VersionType.path` of the with the information supplied by this :class:`~oyProjectManager.core.models.Version` instance. The resultant path is an absolute one. But the stored path in the database is just the relative portion to the :class:`~oyProjectManager.core.models.Repository`\ .\ :attr:`~oyProjectManager.core.models.Repository.server_path` """ return os.path.join( self.project.path, self._path ).replace("\\", "/")
@property
[docs] def full_path(self): """The full_path of this version. It is the join of :class:`~oyProjectManager.core.models.Repository`.\ :attr:`~oyProjectManager.core.models.Repository.server_path` and :class:`~oyProjectManager.core.models.Version`.\ :attr:`~oyProjectManager.core.models.Version.path` and :class:`~oyProjectManager.core.models.Version`.\ :attr:`~oyProjectManager.core.models.Version.filename` attributes. So, it is an absolute path. The value of the ``full_path`` is not stored in the database. """ return os.path.join( self.path, self.filename ).replace("\\", "/")
@synonym_for("_output_path") @property
[docs] def output_path(self): """The output_path of this version. It is automatically created by rendering the :class:`~oyProjectManager.core.models.VersionType`\ .\ :attr:`~oyProjectManager.core.models.VersionType.output_path` template with the information supplied with this ``Version`` instance. The resultant path is an absolute one. But the stored path in the database is just the relative portion to the :class:`~oyProjectManager.core.models.Repository`\ .\ :attr:`~oyProjectManager.core.models.Repository.server_path`. """ return os.path.join( self.project.path, self._output_path ).replace("\\", "/")
def _condition_name(self, name): """conditions the base name, see the :class:`~oyProjectManager.core.models.Version` documentation for details """ # strip the name name = name.strip() # convert all the "-" signs to "_" at the beginning and the at the end # of the string #name = name.replace("-", "_") #name = re.sub(r"^[\-]+", r"", name) #name = re.sub(r"[\-]+$", r"", name) # remove unnecessary characters from the string name = re.sub(r"([^a-zA-Z0-9\s_\-]+)", r"", name) # remove all the characters from the beginning which are not alphabetic name = re.sub(r"(^[^a-zA-Z0-9]+)", r"", name) # substitute all spaces with "_" characters name = re.sub(r"([\s])+", "_", name) # make each words first letter uppercase name = "_".join([ word[0].upper() + word[1:] for word in name.split("_") if len(word) ]) name = "-".join([ word[0].upper() + word[1:] for word in name.split("-") if len(word) ]) return name @validates("base_name") def _validate_base_name(self, key, base_name): """validates the given base_name value """ if base_name is None: raise TypeError("Version.base_name can not be None, please " "supply a proper string or unicode value") if not isinstance(base_name, (str, unicode)): raise TypeError("Version.base_name should be an instance of " "string or unicode") base_name = self._condition_name(base_name) if base_name == "": raise ValueError("Version.base_name is either given as an empty " "string or it became empty after formatting") return base_name @validates("take_name") def _validate_take_name(self, key, take_name): """validates the given take_name value """ # get the config # from oyProjectManager import config # conf = config.Config() from oyProjectManager import conf if take_name is None: take_name = conf.default_take_name if not isinstance(take_name, (str, unicode)): raise TypeError("Version.take_name should be an instance of " "string or unicode") take_name = self._condition_name(take_name) if take_name == "": raise ValueError("Version.take_name is either given as an empty " "string or it became empty after formatting") return take_name
[docs] def latest_version(self): """returns the Version instance with the highest version number in this series :returns: :class:`~oyProjectManager.core.models.Version` instance """ # .filter(Version.base_name == self.base_name)\ return db.session\ .query(Version)\ .filter(Version.type==self.type)\ .filter(Version.version_of==self.version_of)\ .filter(Version.take_name==self.take_name)\ .order_by(Version.version_number.desc())\ .first()
@property
[docs] def max_version(self): """returns the maximum version number for this Version from the database. :returns: int """ last_version = self.latest_version() if last_version: max_version = last_version.version_number else: max_version = 0 return max_version
def _validate_version_number(self, version_number): """validates the given version number """ max_version = self.max_version if version_number is None: # get the smallest possible value for the version_number # from the database version_number = max_version + 1 if version_number <= max_version: version_number = max_version + 1 return version_number def _version_number_getter(self): """returns the version_number of this Version instance """ return self._version_number def _version_number_setter(self, version_number): """sets the version_number of this Version instance """ self._version_number = self._validate_version_number(version_number) version_number = synonym( "_version_number", descriptor=property( _version_number_getter, _version_number_setter ) )
[docs] def save(self): """commits the changes to the database """ if self not in db.session: db.session.add(self) db.session.commit()
@validates("note") def _validate_note(self, key, note): """validates the given note value """ if note is None: note = "" if not isinstance(note, (str, unicode)): raise TypeError("Version.note should be an instance of " "string or unicode") return note @validates("created_by") def _validate_created_by(self, key, created_by): """validates the created_by value """ if created_by is None: raise TypeError("Version.created_by can not be None, please " "set it to oyProjectManager.core.models.User " "instance") if not isinstance(created_by, User): raise TypeError("Version.created_by should be an instance of" "oyProjectManager.core.models.User") return created_by @validates("references") def _validate_references(self, key, reference): """validates the given reference value """ if reference is self: raise ValueError("Version.references can not have a reference to " "itself") # check circular dependency _check_circular_dependency(reference, self) return reference @property
[docs] def project(self): """The :class:`~oyProjectManager.core.models.Project` instance that this Version belongs to """ return self.version_of.project
[docs] def is_latest_version(self): """returns True if this is the latest Version False otherwise """ return self.max_version == self.version_number
[docs] def is_latest_published_version(self): """returns True if this is the latest published Version False otherwise """ if not self.is_published: return False return self == self.latest_published_version()
[docs] def latest_published_version(self): """Returns the last published version. :return: :class:`~oyProjectManager.core.models.Version` """ return db.session\ .query(Version)\ .filter(Version.type==self.type)\ .filter(Version.version_of==self.version_of)\ .filter(Version.take_name==self.take_name)\ .filter(Version.is_published==True)\ .order_by(Version.version_number.desc())\ .first()
@property
[docs] def dependency_update_list(self, published_only=True): """Calculates a list of :class:`~oyProjectManager.core.models.Version` instances, which are referenced by this Version and has a newer version. Also checks the references in the referenced Version and appends the resultant list to the current dependency_update_list. Resulting a much deeper update info. :return: list of :class:`~oyProjectManager.core.models.Version` instances. """ # loop through the referenced Version instances and check if they have # newer Versions update_list = [] # for ref in self.references: # if not ref.is_latest_version(): # update_list.append(ref) # # also loop in their references # update_list.extend(ref.dependency_update_list) # for now just search for published versions for the first references # do not search the children of it for ref in self.references: # get the last published versions of the references published_version = ref.latest_published_version() # if the version number is bigger add it to the update list if published_version: if published_version.version_number > ref.version_number: update_list.append(ref) return update_list # @validates('status')
def _validate_status(self, status): """validates the given status value """ if isinstance(status, VersionStatusComparator): status = status.status if status is None: latest_version = self.latest_version() if latest_version: status = latest_version.status else: # set it to status[0] status = conf.status_list[0] if not isinstance(status, (str, unicode)): raise TypeError('Version.status should be one an instance of ' 'string and the value should be one of of %s not ' '%s' % (conf.status_list, status.__class__.__name__) ) all_statuses = copy(conf.status_list) all_statuses.extend(conf.status_list_long_names) if status not in all_statuses: raise ValueError('Version.status should be one of %s not %s' % (conf.status_list, status)) if status in conf.status_list_long_names: index = conf.status_list_long_names.index(status) status = conf.status_list[index] return status @hybrid_property def status(self): return VersionStatusComparator(self._status) @status.setter def status(self, status): self._status = self._validate_status(status)
[docs]class VersionType(Base): """A template for :class:`~oyProjectManager.core.models.Version` class. A VersionType is basically a template object for the :class:`~oyProjectManager.core.models.Version` instances. It gives the information about the filename template, path template and output path template for the :class:`~oyProjectManager.core.models.Version` class. Then the :class:`~oyProjectManager.core.models.Version` class renders this Jinja2 templates and places itself (or the produced files) in to the appropriate folders in the :class:`~oyProjectManager.core.models.Repository`. All the template variables ( :attr:`~oyProjectManager.core.models.VersionType.filename`, :attr:`~oyProjectManager.core.models.VersionType.path`, :attr:`~oyProjectManager.core.models.VersionType.output_path`) can use the following variables in their template codes. .. _table: +---------------+-----------------------------+--------------------------+ | Variable Name | Variable Source | Description | +===============+=============================+==========================+ | project | version.version_of.project | The project that the | | | | Version belongs to | +---------------+-----------------------------+--------------------------+ | sequence | version.version_of.sequence | Only available for Shot | | | | versions | +---------------+-----------------------------+--------------------------+ | version | version | The version itself | +---------------+-----------------------------+--------------------------+ | type | version.type | The VersionType instance | | | | attached to the this | | | | Version | +---------------+-----------------------------+--------------------------+ In oyProjectManager, generally you don't need to create VersionType instances by hand. Instead, add all the version types you need to your config.py file and the :class:`~oyProjectManager.core.models.Project` instance will create all the necessary VersionTypes from this config.py configuration file. For more information about the the config.py please see the documentation of config.py. For previously created projects, where a new type is needed to be added you can still create a new VersionType instance and save it to the Projects' database. :param str name: The name of this template. The name is not formatted in anyway. It can not be skipped or it can not be None or it can not be an empty string. The name attribute should be unique. Be careful that even specifying a non unique name VersionType instance will not raise an error until :meth:`~oyProjectManager.core.models.VersionType.save` is called. :param str code: The code is a shorthand form of the name. For example, if the name is "Animation" than the code can be "ANIM" or "Anim" or "anim". Because the code is generally used in filename, path or output_path templates it is going to be a part of the filename or path, so be careful about what you give as a code. The code attribute should be unique. Be careful that even specifying a non unique code VersionType instance will not raise an error until :meth:`~oyProjectManager.core.models.VersionType.save` is called. For formatting, these rules are current: * no white space characters are allowed * can not start with a number * can not start or end with an underscore character * both lowercase or uppercase letters are allowed A good code is the short form of the :attr:`~oyProjectManager.core.models.VersionType.name` attribute. Examples: +----------------+----------------------+ | Name | Code | +================+======================+ | Animation | Anim or ANIM | +----------------+----------------------+ | Composition | Comp or COMP | +----------------+----------------------+ | Match Move | MM | +----------------+----------------------+ | Camera Track | Track or TACK | +----------------+----------------------+ | Model | Model or MODEL | +----------------+----------------------+ | Rig | Rig or RIG | +----------------+----------------------+ | Scene Assembly | Asmbly or ASMBLY | +----------------+----------------------+ | Lighting | Lighting or LIGHTING | +----------------+----------------------+ | Camera | Cam or CAM | +----------------+----------------------+ :param filename: The filename template. It is a single line Jinja2 template showing the filename of the :class:`~oyProjectManager.core.models.Version` which is using this VersionType. Look for the above `table`_ for possible variables those can be used in the template code. For an example the following is a nice example for Asset version filename:: {{version.base_name}}_{{version.take_name}}_{{type.code}}_\\ v{{'%03d'|format(version.version_number)}}_\\ {{version.created_by.initials}} Which will render something like that:: Car_Main_Model_v001_oy Now all the versions for the same asset will have a consistent name. When the filename argument is skipped or is an empty string is given a TypeError will be raised to prevent creation of files with no names. :param str path: The path template. It is a single line Jinja2 template showing the absolute path of this :class:`~oyProjectManager.core.models.Version` instance. Look for the above `table`_ for possible variables those can be used in the template code. For an example the following is a nice template for a Shot version:: {{project.full_path}}/Sequences/{{sequence.code}}/Shots/\\ {{version.base_name}}/{{type.code}} This will place a Shot Version whose base_name is SH001 and let say that the type is Animation (where the code is ANIM) to a path like:: M:/JOBs/PROJ1/Sequences/SEQ1/Shots/SH001/ANIM All the animation files realted to this shot will be saved inside that folder. :param str output_path: It is a single line Jinja2 template which shows where to place the outputs of this kind of :class:`~oyProjectManager.core.models.Version`\ s. An output is simply anything that is rendered out from the source file, it can be the renders or playblast/flipbook outputs for Maya, Nuke or Houdini and can be different file type versions (tga, jpg, etc.) for Photoshop files. Generally it is a good idea to store the output right beside the source file. So for a Shot the following is a good example:: {{version.path}}/Outputs Which will render as:: M:/JOBs/PROJ1/Sequences/SEQ1/Shots/SH001/ANIM/Outputs :param str extra_folders: It is a list of single line Jinja2 template codes which are showing the extra folders those need to be created. It is generally created in the :class:`~oyProjectManager.core.models.Project` creation phase. The following is an extra folder hierarchy created for the FX version type:: {{version.path}}/cache :param environments: A list of environments that this VersionType is valid for. The idea behind is to limit the possible list of types for the program that the user is working on. So let say it may not possible to create a camera track in Photoshop then what one can do is to add a Camera Track type but exclude the Photoshop from the list of environments that this type is valid for. The given value should be a list of environment names, be careful about not to pass just a string for the environments list, python will convert the string to a list by putting all the letters in separate elements in the list. And probably this is not something one wants. :type environments: list of strings :param type_for: An enum value specifying what this VersionType instance is for, is it for an "Asset" or for an "Shot". The two acceptable values are "Asset" or "Shot". Any other value will raise an IntegrityError. It can not be skipped. """ # :param project: A VersionType instance can not be created without defining # which :class:`~oyProjectManager.core.models.Project` it belongs to. So it # can not be None or anything other than a # :class:`oyProjectManager.core.models.Project` instance. # # :type project: :class:`~oyProjectManager.core.models.Project` __tablename__ = "VersionTypes" __table_args__ = ( {"extend_existing":True} ) id = Column(Integer, primary_key=True) # project_id = Column(Integer, ForeignKey("Projects.id")) # _project = relationship("Project") name = Column(String, unique=True) code = Column(String, unique=True) filename = Column( String, doc="""The filename template for this type of version instances. You can freely change the filename template attribute after creating :class:`~oyProjectManager.core.models.Version`\ s of this type. Any :class:`~oyProjectManager.core.models.Version` which is created prior to this change will not be effected. But be careful about the older and newer :class:`~oyProjectManager.core.models.Version`\ s of the same :class:`~oyProjectManager.core.models.Asset` or :class:`~oyProjectManager.core.models.Shot` may placed to different folders according to your new template. The template **should not** include a dot (".") sign before the extension, it is handled by the :class:`~oyProjectManager.core.models.Version` instance. """ ) path = Column( String, doc="""The path template for this Type of Version instance. You can freely change the path template attribute after creating :class:`~oyProjectManager.core.models.Version`\ s of this type. Any :class:`~oyProjectManager.core.models.Version` which is created prior to this change will not be effected. But be careful about the older and newer :class:`~oyProjectManager.core.models.Version`\ s of the same :class:`~oyProjectManager.core.models.Asset` or :class:`~oyProjectManager.core.models.Shot` may placed to different folders according to your new template. The path template should be an relative one to the :attr:`~oyProjectManager.core.models.Repository.server_path`, so don't forget to place ``{{project.code}}`` at the beginning of your template if you are storing all your asset and shots inside the project directory. If you want to store your assets in one place and use them in several projects, you can do it by starting the ``path`` of the VersionType with something like that:: "Assets/{{version.base_name}}/{{type.code}}" and if your repository path is "/mnt/M/JOBs" then your assets will be stored in:: "/mnt/M/JOBs/Assets" """ ) output_path = Column( String, doc="""The output path template for this Type of Version instances. To place your output path right beside the original version file you can set the ``output_path`` to:: "{{version.path}}/Outputs/{{version.take_name}}" """ ) extra_folders = Column( String, doc="""A string containing the extra folder names those needs to be created""" ) environments = association_proxy( "version_type_environments", "environment_name" ) _type_for = Column( Enum("Asset", "Shot", name="ckEnumType"), doc="""A enum value showing if this version type is valid for Assets or Shots. """ )
[docs] def __init__(self, name, code, path, filename, output_path, environments, type_for, extra_folders=None ): self.name = name self.code = code self.filename = filename self.path = path self.output_path = output_path self.environments = environments self.extra_folders = extra_folders self._type_for = type_for
def __eq__(self, other): """equality operator """ return isinstance(other, VersionType) and self.name == other.name def __ne__(self, other): """inequality operator """ return not self.__eq__(other) @validates("name") def _validate_name(self, key, name): """validates the given name value """ if name is None: raise TypeError("VersionType.name can not be None, please " "supply a string or unicode instance") if not isinstance(name, (str, unicode)): raise TypeError("VersionType.name should be an instance of " "string or unicode") return name @validates("code") def _validate_code(self, key, code): """validates the given code value """ if code is None: raise TypeError("VersionType.code can not be None, please " "specify a proper string value") if not isinstance(code, (str, unicode)): raise TypeError("VersionType.code should be an instance of " "string or unicode, please supply one") return code @validates("extra_folders") def _validate_extra_folders(self, key, extra_folders): """validates the given extra_folders value """ if extra_folders is None: extra_folders = "" if not isinstance(extra_folders, (str, unicode)): raise TypeError("VersionType.extra_folders should be a string or " "unicode value showing the extra folders those " "needs to be created with the Version of this " "type.") return extra_folders @validates("filename") def _validate_filename(self, key, filename): """validates the given filename """ if filename is None: raise TypeError("VersionType.filename can not be None, please " "specify a valid filename template string by " "using Jinja2 template syntax") if not isinstance(filename, (str, unicode)): raise TypeError("VersionType.filename should be an instance of" "string or unicode") if filename=="": raise ValueError("VersionType.filename can not be an empty " "string, it should be a string containing a " "Jinja2 template code showing the file naming " "convention of Versions of this type.") return filename @validates("path") def _validate_path(self, key, path): """validates the given path """ if path is None: raise TypeError("VersionType.path can not be None, please " "specify a valid path template string by using " "Jinja2 template syntax") if not isinstance(path, (str, unicode)): raise TypeError("VersionType.path should be an instance of string " "or unicode") if path=="": raise ValueError("VersionType.path can not be an empty " "string, it should be a string containing a " "Jinja2 template code showing the file naming " "convention of Versions of this type.") return path @validates("output_path") def _validate_output_path(self, key, output_path): """Validates the given output_path value """ if output_path is None: raise TypeError("VersionType.output_path can not be None") if not isinstance(output_path, (str, unicode)): raise TypeError("VersionType.output_path should be an instance " "of string or unicode, not %s" % type(output_path)) if output_path == "": raise ValueError("VersionType.output_path can not be an empty " "string") return output_path # @classmethod # def _check_project(cls, project): # """A convenience function which checks the given project argument value # # It is a ``classmethod``, so can be called both in ``__new__`` and other # methods like ``_validate_project``. # # Checks the given project for a couple of conditions, like being None or # not being an Project instance etc. # """ # # if project is None: # raise TypeError("VersionType.project can not be None") # # if not isinstance(project, Project): # raise TypeError("The project should be and instance of " # "oyProjectManager.core.models.Project") # # return project # @synonym_for("_project") # @property # def project(self): # """A read-only attribute to return the related Project of this Sequence # instance # """ # # return self._project
[docs] def save(self): """Saves the current VersionType to the database """ if self not in db.session: db.session.add(self) db.session.commit()
@validates("_type_for") def _validate_type_for(self, key, type_for): """Validates the given type_for value """ if type_for is None: raise TypeError("VersionType.type_for can not be None, it should " "be a string or unicode value") if not isinstance(type_for, (str, unicode)): raise TypeError("VersionType.type_for should be an instance of " "string or unicode, not %s" % type(type_for)) return type_for @synonym_for("_type_for") @property
[docs] def type_for(self): """An enum attribute holds what is this VersionType created for, a Shot or an Asset. """ return self._type_for
class VersionTypeEnvironments(Base): """An association object for VersionType.environments """ __tablename__ = "VersionType_Environments" __table_args__ = ( {"extend_existing":True} ) versionType_id = Column(Integer, ForeignKey("VersionTypes.id"), primary_key=True) environment_name = Column( String, primary_key=True, doc="""The name of the environment which the VersionType instance is valid for """ ) version_type = relationship( "VersionType", backref=backref( "version_type_environments", cascade="all, delete-orphan" ) ) def __init__(self, environment_name): self.environment_name = environment_name @validates("environment_name") def _validate_environment_name(self, key, environment_name): """validates the given environment_name value """ if environment_name is None or\ not isinstance(environment_name, (str, unicode)): raise TypeError("VersionType.environments should be a list of " "strings containing the environment names") return environment_name
[docs]class User(Base): """Manages users The user class is the representation of the users in the system. Because there is no central database in the current implementation of oyProjectManager, a user is stored in the :class:`~oyProjectManager.core.models.Project`\ 's database only if the user has created some data. So creating a user and querying the :class:`~oyProjectManager.core.models.Project`\ s that this user has worked has no direct way. :param name: The name of the user. :param initials: Initials of the user. If skipped the initials will be extracted from the :attr:`~oyProjectManager.core.models.User.name` attribute. :param email: The email address of the user. """ __tablename__ = "Users" __table_args__ = ( {"extend_existing":True} ) id = Column(Integer, primary_key=True) name = Column(String) initials = Column(String) email = Column(String) active = Column(Boolean, default=True) versions_created = relationship("Version")
[docs] def __init__(self, name, initials=None, email=None, active=True): self.name = name self.initials = initials self.email = email self.active = True
def __eq__(self, other): """the equality operator """ return isinstance(other, User) and self.name == other.name def __str__(self): """The string representation of this User instance """ return self.name def __repr__(self): """The representation of this User """ return "<User: %s>" % self.name
[docs] def save(self): """saves this User instance to the database """ if db.session is not None: if self not in db.session: db.session.add(self) db.session.commit()
[docs]class Client(Base): """Represents the Client :param str name: The name of the client. It is a string can not be empty :param str generic_info: it is a string holding generic information about the client. """ __tablename__ = "Clients" __table_args__ = ( {"extend_existing":True} ) id = Column(Integer, primary_key=True) name = Column(String(256), unique=True) code = Column(String(256), unique=True) generic_info = Column(String)
[docs] def __init__(self, name, code=None, generic_info=""): self.name = name self.code = code self.generic_info = generic_info
def __repr__(self): return "<Client : %s (%s)>" % (self.name, self.code) @validates('name') def _validate_name(self, key, name): """validates the given name value """ if name is None or not isinstance(name, (str, unicode)): raise TypeError('Client.name should be a string or unicode, not ' '%s' % name.__class__.__name__) return name @validates('code') def _validate_code(self, key, code): """validates the given code value """ if code is None: code = re.sub(r"[^A-Z]", "", self.name.title()) if not isinstance(code, (str, unicode)): raise TypeError('Client.code should be a string or unicode, not ' '%s' % code.__class__.__name__) # remove spaces code = code.replace(" ", "") return code
[docs] def save(self): """saves the instance to database """ if db.session is not None: if self not in db.session: db.session.add(self) db.session.commit()
[docs]class EnvironmentBase(object): """Connects the environment (the host program) to the oyProjectManager. In oyProjectManager, an Environment is a host application like Maya, Nuke, Houdini etc. Generally a GUI for the end user is given an environment which helps the QtGui to be able to open, save, import or export a Version without knowing the details of the environment. .. note:: For now the :class:`~oyProjectManager.core.models.EnvironmentBase` inherits from the Python object class. There were no benefit to inherit it from the ``DeclarativeBase``. To create a new environment for you own program, just instantiate this class and override the methods as necessary. And call the UI with by giving an environment instance to it, so the interface can call the correct methods as needed. Here is an example how to create an environment for a program and use the GUI:: from oyProjectManager.core import EnvironmentBase class MyProgram(EnvironmentBase): \"""This is a class which will be used by the UI \""" def open(): \"""uses the programs own Python API to open a version of an asset \""" # do anything that needs to be done before opening the file my_programs_own_python_api.open(filepath=self.version.full_path) def save(): \"""uses the programs own Python API to save the current file as a new version. \""" # do anything that needs to be done before saving the file my_programs_own_python_api.save(filepath=self.version.full_path) # do anything that needs to be done after saving the file and that is it. The environment class by default has a property called ``version``. Holding the current open version. It is None for a new scene and a :class:`~oyProjectManager.core.models.Version` instance in any other case. :param name: To initialize the class the name of the environment should be given in the name argument. It can not be skipped or None or an empty string. """ # __tablename__ = "Environments" # id = Column(Integer, primary_key=True) name = "EnvironmentBase" # def __init__(self, name=""): # self._name = name # self._extensions = [] # self._version = None def __str__(self): """the string representation of the environment """ return self._name @property
[docs] def version(self): """returns the current Version instance which is open in the environment """ return self._version # @version.setter # def version(self, version): # """sets the version of the environment # """ # self._version = version
@property def name(self): """returns the environment name """ return self._name @name.setter
[docs] def name(self, name): """sets the environment name """ self._name = name
[docs] def save_as(self, version): """The save as action of this environment. It should save the current scene or file to the given version.full_path """ raise NotImplemented
[docs] def export_as(self, version): """Exports the contents of the open document as the given version. :param version: A :class:`~oyProjectManager.core.models.Version` instance holding the desired version. """ raise NotImplemented
[docs] def open_(self, version, force=False): """the open action """ raise NotImplemented
[docs] def import_(self, asset): """the import action """ raise NotImplemented
[docs] def reference(self, asset): """the reference action """ raise NotImplemented
[docs] def trim_server_path(self, path_in): """Trims the server_path value from the given path_in :param path_in: The path that wanted to be trimmed :return: str """ repo = Repository() server_path = repo.server_path if path_in.startswith(server_path): path_in = path_in[len(os.path.normpath(server_path))+1:] return path_in
[docs] def get_versions_from_path(self, path): """Finds Version instances from the given path value. Finds and returns the :class:`~oyProjectManager.core.models.Version` instances from the given path value. Returns an empth list if it can't find any matching. This method is different than :meth:`~oyProjectManager.core.models.EnvironmentBase.get_version_from_full_path` because it returns a list of :class:`~oyProjectManager.core.models.Version` instances which are residing in that path. The list is ordered by the ``id``\ s of the instances. :param path: A path which has possible :class:`~oyProjectManager.core.models.Version` instances. :return: A list of :class:`~oyProjectManager.core.models.Version` instances. """ # get the path by trimming the server_path path = self.trim_server_path(path) # get all the version instance at that path return Version.query()\ .filter(Version.path.startswith(path))\ .order_by(Version.id.desc())\ .all()
[docs] def get_version_from_full_path(self, full_path): """Finds the Version instance from the given full_path value. Finds and returns a :class:`~oyProjectManager.core.models.Version` instance from the given full_path value. Returns None if it can't find any matching. :param full_path: The full_path of the desired :class:`~oyProjectManager.core.models.Version` instance. :return: :class:`~oyProjectManager.core.models.Version` """ path, filename = os.path.split(full_path) path = self.trim_server_path(path) # try to get a version with that info version = Version.query()\ .filter(Version.path==path)\ .filter(Version.filename==filename)\ .first() return version
[docs] def get_current_version(self): """Returns the current Version instance from the environment. :returns: :class:`~oyProjectManager.core.models.Version` instance or None """ raise NotImplemented
[docs] def get_last_version(self): """Returns the last opened Version instance from the environment. * It first looks at the current open file full path and tries to match it with a Version instance. * Then searches for the recent files list. * Still not able to find any Version instances, will return the version instance with the highest id which has the current workspace path in its path * Still not able to find any Version instances returns None :returns: :class:`~oyProjectManager.core.models.Version` instance or None """ raise NotImplemented
[docs] def getProject(self): """returns the current project from environment """ raise NotImplemented
[docs] def set_project(self, version): """Sets the project to the given versions project. Because the projects are related to the created Version instances instead of passing a Project instance it is much meaningful to pass a Version instance which will have a reference to the Project instance already. :param project: A :class:`~oyProjectManager.core.models.Version`. """ raise NotImplemented # def setOutputFileName(self): # def set_output_path(self): # """sets the output file names # """ # raise NotImplemented # def append_to_recent_files(self, path): # """appends the given path to the recent files list # """ # raise NotImplemented
[docs] def check_referenced_versions(self): """Checks the referenced versions returns list of asset objects """ raise NotImplemented
[docs] def get_referenced_versions(self): """Returns the :class:`~oyProjectManager.core.models.Version` instances which are referenced in to the current scene :returns: list of :class:`~oyProjectManager.core.models.Version` instances """ raise NotImplemented # def update_versions(self, version_tuple_list): # """updates the assets to the latest versions # """ # raise NotImplemented
[docs] def get_frame_range(self): """Returns the frame range from the environment :returns: a tuple of integers containing the start and end frame numbers """ raise NotImplemented
[docs] def set_frame_range(self, start_frame=1, end_frame=100, adjust_frame_range=False): """Sets the frame range in the environment to the given start and end frames """ raise NotImplemented
[docs] def get_fps(self): """Returns the frame rate of this current environment """ raise NotImplemented
[docs] def set_fps(self, fps=25): """Sets the frame rate of the environment. The default value is 25. """ raise NotImplemented
@property def extensions(self): """Returns the valid native extensions for this environment. :returns: a list of strings """ return self._extensions @extensions.setter
[docs] def extensions(self, extensions): """Sets the valid native extensions of this environment. :param extensions: A list of strings holding the extensions. Ex: ["ma", "mb"] for Maya """ self._extensions = extensions
[docs] def has_extension(self, filename): """Returns True if the given filenames extension is in the extensions list false otherwise. accepts: * a full path with extension or not * a file name with extension or not * an extension with a dot on the start or not :param filename: A string containing the filename """ if fileName is None: return False if fileName.split('.')[-1].lower() in self._extensions: return True return False
[docs] def load_referenced_versions(self): """loads all the references """ raise NotImplemented
[docs] def replace_version(self, source_version, target_version): """Replaces the source_version with the target_version :param source_version: A :class:`~oyProjectManager.core.models.Version` instance holding the version to be replaced :param target_version: A :class:`~oyProjectManager.core.models.Version` instance holding the new version replacing the source one """ raise NotImplemented
[docs] def replace_external_paths(self, mode=0): """Replaces the external paths (which are not starting with the environment variable) with a proper path. The mode controls if the resultant path should be absolute or relative to the project dir. :param mode: Controls the resultant path is absolute or relative. mode 0: absolute (a path which starts with $REPO) mode 1: relative (to project path) :return: """ raise NotImplemented # secondary tables
Version_References = Table( "Version_References", Base.metadata, Column("referencer_id", Integer, ForeignKey("Versions.id"), primary_key=True), Column("reference_id", Integer, ForeignKey("Versions.id"), primary_key=True), extend_existing=True ) def _check_circular_dependency(version, check_for_version): """checks the circular dependency in version if it has check_for_version in its depends list """ for reference in version.references: if reference is check_for_version: raise CircularDependencyError( "version %s can not reference %s, this creates a circular " "dependency" % (version, check_for_version) ) else: _check_circular_dependency(reference, check_for_version)