Ejemplo n.º 1
0
    def options(self, options_list):
        """
        Set the options of the select list property if the property is a property model.

        Will raise an exception if the property is not a model. Will also raise an exception if the list is of
        incorrect form and contains non string, floats or integers.

        :param options_list: list of options to set, using only strings, integers and floats in the list.
        :raises: APIError, AssertionError

        Example
        -------

        >>> a_model = project.model('Model')
        >>> select_list_property = a_model.property('a_select_list_property')

        Get the list of options on the property
        >>> select_list_property.options
        ['1', '2', '3', '4']

        Update the list of options
        >>> select_list_property.options = [1,3,5,"wow"]
        """
        if not isinstance(options_list, list):
            raise APIError("The list of options should be a list")
        if self._json_data.get('category') != 'MODEL':
            raise APIError(
                "We can only update the options list of the model of this property. The model of this "
                "property has id '{}'".format(self._json_data.get('model')))

        # stringify the options list
        options_list = list(map(str, options_list))
        if not self._options or set(options_list) != set(self._options):
            self._put_options(options_list=options_list)
            self._options = options_list
Ejemplo n.º 2
0
    def add_with_properties(self,
                            model,
                            name=None,
                            update_dict=None,
                            bulk=True,
                            **kwargs):
        """
        Add a part and update its properties in one go.

        :param model: model of the part which to add a new instance, should follow the model tree in KE-chain
        :param name: (optional) name provided for the new instance as string otherwise use the name of the model
        :param update_dict: dictionary with keys being property names (str) and values being property values
        :param bulk: True to use the bulk_update_properties API endpoint for KE-chain versions later then 2.1.0b
        :param kwargs: (optional) additional keyword arguments that will be passed inside the update request
        :return: :class:`pykechain.models.Part`
        :raises: APIError, Raises `NotFoundError` when the property name is not a valid property of this part

        Examples
        --------
        >>> bike = client.scope('Bike Project').part('Bike')
        >>> wheel_model = client.scope('Bike Project').model('Wheel')
        >>> bike.add_with_properties(wheel_model, 'Wooden Wheel', {'Spokes': 11, 'Material': 'Wood'})

        """
        # TODO: add test coverage for this method
        if self.category != Category.INSTANCE:
            raise APIError("Part should be of category INSTANCE")
        name = name or model.name
        action = 'new_instance_with_properties'

        properties_update_dict = dict([
            (model.property(property_name).id, property_value)
            for property_name, property_value in update_dict.items()
        ])
        # TODO: add bulk = False flags such that is used the old API (sequential)
        if bulk:
            r = self._client._request(
                'POST',
                self._client._build_url('parts'),
                data=dict(name=name,
                          model=model.id,
                          parent=self.id,
                          properties=json.dumps(properties_update_dict),
                          **kwargs),
                params=dict(select_action=action))

            if r.status_code != requests.codes.created:  # pragma: no cover
                raise APIError('{}: {}'.format(str(r), r.content))
            return Part(r.json()['results'][0], client=self._client)
        else:  # do the old way
            new_part = self.add(model, name=name)  # type: Part
            new_part.update(update_dict=update_dict, bulk=bulk)
            return new_part
Ejemplo n.º 3
0
    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))
Ejemplo n.º 4
0
    def delete(self):
        """Delete this activity."""
        r = self._client._request(
            'DELETE', self._client._build_url('activity', activity_id=self.id))

        if r.status_code != requests.codes.no_content:
            raise APIError("Could not delete activity: {} with id {}".format(
                self.name, self.id))
Ejemplo n.º 5
0
    def _put_value(self, value):
        url = self._client._build_url('property', property_id=self.id)

        r = self._client._request('PUT', url, json={'value': value})

        if r.status_code != requests.codes.ok:  # pragma: no cover
            raise APIError("Could not update property value")

        return r.json()['results'][0]['value']
Ejemplo n.º 6
0
    def _download(self):
        url = self._client._build_url('property_download', property_id=self.id)

        r = self._client._request('GET', url)

        if r.status_code != requests.codes.ok:
            raise APIError("Could not download property value")

        return r
Ejemplo n.º 7
0
    def _upload(self, data):
        url = self._client._build_url('property_upload', property_id=self.id)

        r = self._client._request('POST',
                                  url,
                                  data={"part": self._json_data['part']},
                                  files={"attachment": data})

        if r.status_code != requests.codes.ok:
            raise APIError("Could not upload attachment")
Ejemplo n.º 8
0
    def delete(self):
        """Delete this property.

        :return: None
        :raises: APIError if delete was not successful
        """
        r = self._client._request(
            'DELETE', self._client._build_url('property', property_id=self.id))

        if r.status_code != requests.codes.no_content:  # pragma: no cover
            raise APIError("Could not delete property: {} with id {}".format(
                self.name, self.id))
Ejemplo n.º 9
0
    def add_property(self, *args, **kwargs):
        # type: (*Any, **Any) -> Property
        """Add a new property to this model.

        See :class:`pykechain.Client.create_property` for available parameters.

        :return: Property
        """
        if self.category != Category.MODEL:
            raise APIError("Part should be of category MODEL")

        return self._client.create_property(self, *args, **kwargs)
Ejemplo n.º 10
0
    def edit(self, name=None, description=None, **kwargs):
        # type: (AnyStr, AnyStr, Any) -> None
        """
        Edit the details of a part (model or instance).

        For an instance you can edit the Part instance name and the part instance description

        :param name: optional name of the part to edit
        :param description: (optional) description of the part
        :param kwargs: (optional) additional kwargs that will be passed in the during the edit/update request
        :return: None
        :raises: APIError

        Example
        -------

        For changing a part:

        >>> front_fork = project.part('Front Fork')
        >>> front_fork.edit(name='Front Fork - updated')
        >>> front_fork.edit(name='Front Fork cruizer', description='With my ragtop down so my hair can blow' )

        for changing a model:

        >>> front_fork = project.model('Front Fork')
        >>> front_fork.edit(name='Front Fork basemodel', description='Some description here')

        """
        update_dict = {'id': self.id}
        if name:
            if not isinstance(name, str):
                raise IllegalArgumentError(
                    "name should be provided as a string")
            update_dict.update({'name': name})
        if description:
            if not isinstance(description, str):
                raise IllegalArgumentError(
                    "description should be provided as a string")
            update_dict.update({'description': description})

        # TODO: What else can we edit? Don't know how to test for it, so for now I have decide to skip it in the tests
        if kwargs:  # pragma: no cover
            update_dict.update(**kwargs)
        r = self._client._request('PUT',
                                  self._client._build_url('part',
                                                          part_id=self.id),
                                  json=update_dict)

        if r.status_code != requests.codes.ok:  # pragma: no cover
            raise APIError("Could not update Part ({})".format(r))

        if name:
            self.name = name
Ejemplo n.º 11
0
    def delete(self):
        # type: () -> None
        """Delete this part.

        :return: None
        :raises: APIError if delete was not succesfull
        """
        r = self._client._request(
            'DELETE', self._client._build_url('part', part_id=self.id))

        if r.status_code != requests.codes.no_content:  # pragma: no cover
            raise APIError("Could not delete part: {} with id {}".format(
                self.name, self.id))
Ejemplo n.º 12
0
    def order_properties(self, property_list=None):
        """
        Order the properties of a part model using a list of property objects or property names.

        Examples
        --------
        >>> front_fork = client.scope('Bike Project').model('Front Fork')
        >>> front_fork.order_properties(['Material', 'Height (mm)', 'Color'])

        >>> front_fork = client.scope('Bike Project').model('Front Fork')
        >>> material = front_fork.property('Material')
        >>> height = front_fork.property('Height (mm)')
        >>> color = front_fork.property('Color')
        >>> front_fork.order_properties([material, height, color])

        """
        if self.category != Category.MODEL:
            raise APIError("Part should be of category MODEL")
        if not isinstance(property_list, list):
            raise TypeError(
                'Expected a list of strings or Property() objects, got a {} object'
                .format(type(property_list)))

        order_dict = dict()

        for prop in property_list:
            if isinstance(prop, (str, text_type)):
                order_dict[self.property(
                    name=prop).id] = property_list.index(prop)
            else:
                order_dict[prop.id] = property_list.index(prop)

        r = self._client._request(
            'PUT',
            self._client._build_url('part', part_id=self.id),
            data=dict(property_order=json.dumps(order_dict)))
        if r.status_code != requests.codes.ok:  # pragma: no cover
            raise APIError("Could not reorder properties")
Ejemplo n.º 13
0
    def update(self, name=None, update_dict=None, bulk=True, **kwargs):
        """
        Edit part name and property values in one go.

        :param name: new part name (defined as a string)
        :param update_dict: dictionary with keys being property names (str) and values being property values
        :param bulk: True to use the bulk_update_properties API endpoint for KE-chain versions later then 2.1.0b
        :param kwargs: (optional) additional keyword arguments that will be passed inside the update request
        :return: :class:`pykechain.models.Part`
        :raises: APIError, Raises `NotFoundError` when the property name is not a valid property of this part

        Example
        -------

        >>> bike = client.scope('Bike Project').part('Bike')
        >>> bike.update(name='Good name', update_dict={'Gears': 11, 'Total Height': 56.3}, bulk=True)

        """
        # new for 1.5 and KE-chain 2 (released after 14 march 2017) is the 'bulk_update_properties' action on the api
        # lets first use this one.
        # dict(name=name, properties=json.dumps(update_dict))) with property ids:value
        action = 'bulk_update_properties'

        url = self._client._build_url('part', part_id=self.id)
        request_body = dict([
            (self.property(property_name).id, property_value)
            for property_name, property_value in update_dict.items()
        ])

        if bulk and len(update_dict.keys()) > 1:
            if name:
                if not isinstance(name, str):
                    raise IllegalArgumentError(
                        "Name of the part should be provided as a string")
            r = self._client._request('PUT',
                                      self._client._build_url('part',
                                                              part_id=self.id),
                                      data=dict(
                                          name=name,
                                          properties=json.dumps(request_body),
                                          **kwargs),
                                      params=dict(select_action=action))
            if r.status_code != requests.codes.ok:  # pragma: no cover
                raise APIError('{}: {}'.format(str(r), r.content))
        else:
            for property_name, property_value in update_dict.items():
                self.property(property_name).value = property_value
Ejemplo n.º 14
0
    def edit(self, name=None, description=None, unit=None):
        # type: (AnyStr, AnyStr, AnyStr) -> None
        """
        Edit the details of a property (model).

        :param name: (optional) new name of the property to edit
        :param description: (optional) new description of the property
        :param unit: (optional) new unit of the property
        :return: None
        :raises: APIError

        Example
        -------
        >>> front_fork = project.part('Front Fork')
        >>> color_property = front_fork.property(name='Color')
        >>> color_property.edit(name='Shade', description='Could also be called tint, depending on mixture',
        >>> unit='RGB')

        """
        update_dict = {'id': self.id}
        if name:
            if not isinstance(name, (str, text_type)):
                raise TypeError(
                    "name should be provided as a string, was provided as '{}'"
                    .format(type(name)))
            update_dict.update({'name': name})
            self.name = name
        if description:
            if not isinstance(description, (str, text_type)):
                raise TypeError(
                    "description should be provided as a string, was provided as '{}'"
                    .format(type(description)))
            update_dict.update({'description': description})
        if unit:
            if not isinstance(unit, (str, text_type)):
                raise TypeError(
                    "unit should be provided as a string, was provided as '{}'"
                    .format(type(unit)))
            update_dict.update({'unit': unit})

        r = self._client._request('PUT',
                                  self._client._build_url('property',
                                                          property_id=self.id),
                                  json=update_dict)

        if r.status_code != requests.codes.ok:  # pragma: no cover
            raise APIError("Could not update Property ({})".format(r))
Ejemplo n.º 15
0
    def _put_options(self, options_list):
        """Save the options to the database.

        Will check for the correct form of the data.

        :param options_list: list of options to set.
        :raises APIError
        """
        url = self._client._build_url('property', property_id=self.id)
        response = self._client._request(
            'PUT', url, json={'options': {
                "value_choices": options_list
            }})

        if response.status_code != 200:  # pragma: no cover
            raise APIError(
                "Could not update property value. Response: {}".format(
                    str(response)))
Ejemplo n.º 16
0
    def add_to(self, parent, **kwargs):
        # type: (Part, **Any) -> Part
        """Add a new instance of this model to a part.

        :param parent: part to add the new instance to
        :return: :class:`pykechain.models.Part`
        :raises: APIError

        Example
        -------

        >>> wheel_model = project.model('wheel')
        >>> bike = project.part('Bike')
        >>> wheel_model.add_to(bike)
        """
        if self.category != Category.MODEL:
            raise APIError("Part should be of category MODEL")

        return self._client.create_part(parent, self, **kwargs)
Ejemplo n.º 17
0
    def configure(self, inputs, outputs):
        """Configure activity input and output.

        :param inputs: iterable of input property models
        :param outputs: iterable of output property models
        :raises: APIError
        """
        url = self._client._build_url('activity', activity_id=self.id)

        r = self._client._request(
            'PUT',
            url,
            params={'select_action': 'update_associations'},
            json={
                'inputs': [p.id for p in inputs],
                'outputs': [p.id for p in outputs]
            })

        if r.status_code != requests.codes.ok:  # pragma: no cover
            raise APIError("Could not configure activity")
Ejemplo n.º 18
0
    def add(self, model, **kwargs):
        # type: (Part, **Any) -> Part
        """Add a new child to this part.

        This can only act on instances

        :param model: model to use for the child
        :return: :class:`pykechain.models.Part`
        :raises: APIError

        Example
        -------

        >>> bike = project.part('Bike')
        >>> wheel_model = project.model('Wheel')
        >>> bike.add(wheel_model)
        """
        if self.category != Category.INSTANCE:
            raise APIError("Part should be of category INSTANCE")

        return self._client.create_part(self, model, **kwargs)
Ejemplo n.º 19
0
    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)