def _update_scope_project_team(self, select_action, user, user_type): """ Update the Project Team of the Scope. Updates include addition or removing of managers or members. :param select_action: type of action to be applied :param user: the username of the user to which the action applies to :param user_type: the type of the user (member or manager) """ if isinstance(user, str): users = self._client._retrieve_users() manager_object = next( (item for item in users['results'] if item["username"] == user), None) if manager_object: url = self._client._build_url('scope', scope_id=self.id) r = self._client._request( 'PUT', url, params={'select_action': select_action}, data={'user_id': manager_object['pk']}) if r.status_code != requests.codes.ok: # pragma: no cover raise APIError("Could not {} {} in Scope".format( select_action.split('_')[0], user_type)) else: raise NotFoundError("User {} does not exist".format(user)) else: raise TypeError( "User {} should be defined as a string".format(user))
def property(self, name): # type: (str) -> Property """Retrieve the property with name belonging to this part. If you need to retrieve the property using eg. the id, use :meth:`pykechain.Client.properties`. :param name: property name to search for :return: a single :class:`pykechain.models.Property` :raises: NotFoundError Example ------- >>> part = project.part('Bike') >>> part.properties [<pyke Property ...>, ...] # this returns a list of all properties of this part >>> gears = part.property('Gears') >>> gears.value 6 """ found = find(self.properties, lambda p: name == p.name) if not found: raise NotFoundError( "Could not find property with name {}".format(name)) return found
def proxy_model(self): """ Retrieve the proxy model of this proxied `Part` as a `Part`. Allows you to retrieve the model of a proxy. But trying to get the catalog model of a part that has no proxy, will raise an NotFoundError. Only models can have a proxy. :return: pykechain.models.Part :raises: NotFoundError Example ------- >>> proxy_part = project.model('Proxy based on catalog model') >>> catalog_model_of_proxy_part = proxy_part.proxy_model() >>> proxied_material_of_the_bolt_model = project.model('Bolt Material') >>> proxy_basis_for_the_material_model = proxied_material_of_the_bolt_model.proxy_model() """ if self.category != Category.MODEL: raise IllegalArgumentError( "Part {} is not a model, therefore it cannot have a proxy model" .format(self)) if 'proxy' in self._json_data and self._json_data.get('proxy'): catalog_model_id = self._json_data['proxy'].get('id') return self._client.model(pk=catalog_model_id) else: raise NotFoundError("Part {} is not a proxy".format(self.name))
def subprocess(self): """Retrieve the subprocess in which this activity is defined. If this is a task on top level, it raises NotFounderror :return: subprocess `Activity` :raises: NotFoundError when it is a task in the top level of a project Example ------- >>> task = project.activity('Subtask') >>> subprocess = task.subprocess() """ subprocess_id = self._json_data.get('container') if subprocess_id == self._json_data.get('root_container'): raise NotFoundError("Cannot find subprocess for this task '{}', " "as this task exist on top level.".format( self.name)) return self._client.activity(pk=subprocess_id, scope=self._json_data['scope']['id'])
def model(self): """ Retrieve the model of this `Part` as `Part`. For instance, you can get the part model of a part instance. But trying to get the model of a part that has no model, like a part model, will raise a NotFoundError. :return: pykechain.models.Part :raises: NotFoundError Example ------- >>> front_fork = project.part('Front Fork') >>> front_fork_model = front_fork.model() """ if self.category == Category.INSTANCE: model_id = self._json_data['model'].get('id') return self._client.model(pk=model_id) else: raise NotFoundError("Part {} has no model".format(self.name))
def children(self): """Retrieve the direct activities of this subprocess. It returns a combination of Tasks (a.o. UserTasks) and Subprocesses on the direct descending level. Only when the activity is a Subprocess, otherwise it raises a NotFoundError :return: list of activities :raises: NotFoundError when this task is not of type `ActivityType.SUBPROCESS` Example ------- >>> subprocess = project.subprocess('Subprocess') >>> children = subprocess.children() """ if self.activity_type != ActivityType.SUBPROCESS: raise NotFoundError( "Only subprocesses can have children, please choose a subprocess instead of a '{}' " "(activity '{}')".format(self.activity_type, self.name)) return self._client.activities(container=self.id, scope=self._json_data['scope']['id'])
def instances(self): """ Retrieve the instances of this `Part` as a `PartSet`. For instance, if you have a model part, you can get the list of instances that are created based on this moodel. If there are no instances (only possible if the multiplicity is `Multiplicity.ZERO_MANY` than a NotFoundError is returned :return: pykechain.models.PartSet :raises: NotFoundError Example ------- >>> wheel_model = project.model('Wheel') >>> wheel_instance_set = wheel_model.instances() """ if self.category == Category.MODEL: return self._client.parts(model=self, category=Category.INSTANCE) else: raise NotFoundError( "Part {} has no instances or is not a model".format(self.name))
def edit(self, name=None, description=None, start_date=None, due_date=None, assignees=None, status=None): """Edit the details of an activity. :param name: (optionally) edit the name of the activity :param description: (optionally) edit the description of the activity :param start_date: (optionally) edit the start date of the activity as a datetime object (UTC time/timezone aware preferred) :param due_date: (optionally) edit the due_date of the activity as a datetime object (UTC time/timzeone aware preferred) :param assignees: (optionally) edit the assignees of the activity as a list, will overwrite all assignees :param status: (optionally) edit the status of the activity as a string :return: None :raises: NotFoundError, TypeError, APIError Example ------- >>> from datetime import datetime >>> specify_wheel_diameter = project.activity('Specify wheel diameter') >>> specify_wheel_diameter.edit(name='Specify wheel diameter and circumference', ... description='The diameter and circumference are specified in inches', ... start_date=datetime.utcnow(), # naive time is interpreted as UTC time ... assignee='testuser') If we want to provide timezone aware datetime objects we can use the 3rd party convenience library `pytz`. Mind that we need to fetch the timezone first and use `<timezone>.localize(<your datetime>)` to make it work correctly. Using datetime(2017,6,1,23,59,0 tzinfo=<tz>) does NOT work for most timezones with a daylight saving time. Check the pytz documentation. (see http://pythonhosted.org/pytz/#localized-times-and-date-arithmetic) >>> import pytz >>> start_date_tzaware = datetime.now(pytz.utc) >>> mytimezone = pytz.timezone('Europe/Amsterdam') >>> due_date_tzaware = mytimezone.localize(datetime(2019, 10, 27, 23, 59, 0)) >>> specify_wheel_diameter.edit(due_date=due_date_tzaware, start_date=start_date_tzaware) """ update_dict = {'id': self.id} if name: if isinstance(name, (str, text_type)): update_dict.update({'name': name}) self.name = name else: raise TypeError('Name should be a string') if description: if isinstance(description, (str, text_type)): update_dict.update({'description': description}) self.description = description else: raise TypeError('Description should be a string') if start_date: if isinstance(start_date, datetime.datetime): if not start_date.tzinfo: warnings.warn( "The startdate '{}' is naive and not timezone aware, use pytz.timezone info. " "This date is interpreted as UTC time.".format( start_date.isoformat(sep=' '))) update_dict.update( {'start_date': start_date.isoformat(sep='T')}) else: raise TypeError( 'Start date should be a datetime.datetime() object') if due_date: if isinstance(due_date, datetime.datetime): if not due_date.tzinfo: warnings.warn( "The duedate '{}' is naive and not timezone aware, use pytz.timezone info. " "This date is interpreted as UTC time.".format( due_date.isoformat(sep=' '))) update_dict.update({'due_date': due_date.isoformat(sep='T')}) else: raise TypeError( 'Due date should be a datetime.datetime() object') if assignees: if isinstance(assignees, list): project = self._client.scope(self._json_data['scope']['name']) members_list = [ member['username'] for member in project._json_data['members'] ] for assignee in assignees: if assignee not in members_list: raise NotFoundError( "Assignee '{}' should be a member of the scope". format(assignee)) update_dict.update({'assignees': assignees}) else: raise TypeError('Assignees should be a list') if status: if isinstance(status, (str, text_type)): update_dict.update({'status': status}) else: raise TypeError('Status should be a string') url = self._client._build_url('activity', activity_id=self.id) r = self._client._request('PUT', url, json=update_dict) if r.status_code != requests.codes.ok: # pragma: no cover raise APIError("Could not update Activity ({})".format(r)) if status: self._json_data['status'] = str(status) if assignees: self._json_data['assignees'] = assignees if due_date: self._json_data['due_date'] = str(due_date) if start_date: self._json_data['start_date'] = str(start_date)