Skip to content

Commit

Permalink
Merge pull request #123 from KE-works/#118-instances-of-model
Browse files Browse the repository at this point in the history
#113 added instances() and instance() method to the part
  • Loading branch information
jberends authored Jun 5, 2017
2 parents 3d3106d + 6cc25e1 commit 793bed8
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 22 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ Change Log
pykechain changelog


* Added a convenience method to retrieve models and instances related to a task at once: `Activity.associated_parts()`.
Making use of the already provided method in `Activity.parts()`. (#118)
* Added missing tests for `Activity.parts()` and `Activity.associated_parts()`
* Added documentation


1.7.3 (01JUN17)
---------------
* Updated documentation for activity startdate and duedate editting using timezone supported datetime objects.
Expand Down
36 changes: 35 additions & 1 deletion pykechain/models/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from six import text_type
from typing import Any # flake8: noqa

from pykechain.enums import Category
from pykechain.exceptions import APIError, NotFoundError
from pykechain.models.base import Base

Expand All @@ -24,10 +25,43 @@ def __init__(self, json, **kwargs):
def parts(self, *args, **kwargs):
"""Retrieve parts belonging to this activity.
See :class:`pykechain.Client.parts` for available parameters.
Without any arguments it retrieves the Instances related to this task only.
See :class:`pykechain.Client.parts` for additional available parameters.
Example
-------
>>> task = project.activity('Specify Wheel Diameter')
>>> parts = task.parts()
To retrieve the models only.
>>> parts = task.parts(category=Category.MODEL)
"""
return self._client.parts(*args, activity=self.id, **kwargs)

def associated_parts(self, *args, **kwargs):
"""Retrieve models and instances belonging to this activity.
This is a convenience method for the `Activity.parts()` method, which is used to retrieve both the
`Category.MODEL` as well as the `Category.INSTANCE` in a tuple.
If you want to retrieve only the models associated to this task it is better to use:
`task.parts(category=Category.MODEL)`.
See :class:`pykechain.Client.parts` for additional available parameters.
:returns: Tuple(models PartSet, instances PartSet)
Example
-------
>>> task = project.activity('Specify Wheel Diameter')
>>> all_models, all_instances = task.associated_parts()
"""
return self.parts(category=Category.MODEL, *args, **kwargs), \
self.parts(category=Category.INSTANCE, *args, **kwargs)

def configure(self, inputs, outputs):
"""Configure activity input and output.
Expand Down
90 changes: 69 additions & 21 deletions pykechain/models/part.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import Any, AnyStr # flake8: noqa

from pykechain.enums import Multiplicity, Category
from pykechain.exceptions import NotFoundError, APIError
from pykechain.exceptions import NotFoundError, APIError, MultipleFoundError
from pykechain.models.base import Base
from pykechain.models.property import Property
from pykechain.utils import find
Expand All @@ -16,6 +16,35 @@ class Part(Base):
:cvar category: The category of the part, either 'MODEL' or 'INSTANCE' (use `pykechain.enums.Category`)
:cvar parent_id: The UUID of the parent of this part
:cvar properties: The list of `pykechain.models.Property` objects belonging to this part.
:cvar multiplicity: The multiplicity of the part being one of the following options: ZERO_ONE, ONE, ZERO_MANY,
ONE_MANY, (reserved) M_N
Examples
--------
For the category property
>>> bike = project.part('Bike')
>>> bike.category
'INSTANCE'
>>> bike_model = project.model('Bike')
>>> bike_model.category
'MODEL'
>>> bike_model == Category.MODEL
True
>>> bike == Category.INSTANCE
True
For the multiplicity property
>>> bike = project.models('Bike')
>>> bike.multiplicity
ONE_MANY
>>> from pykechain.enums import Multiplicity
>>> bike.multiplicity == Multiplicity.ONE_MANY
True
"""

Expand All @@ -27,26 +56,7 @@ def __init__(self, json, **kwargs):
self.category = json.get('category')
self.parent_id = json['parent'].get('id') if 'parent' in json and json.get('parent') else None
self.properties = [Property.create(p, client=self._client) for p in json['properties']]

@property
def multiplicity(self):
"""Return the multiplicity of a part.
Multiplicity of a part is one of the following options: ZERO_ONE, ONE, ZERO_MANY, ONE_MANY, (reserved) M_N
Use `pykechain.enums.Multiplicity` to check for the correct multiplicity
Examples
--------
>>> bike = project.models('Bike')
>>> bike.multiplicity
ONE_MANY
>>> from pykechain.enums import Multiplicity
>>> bike.multiplicity == Multiplicity.ONE_MANY
True
"""
return self._json_data.get('multiplicity', None)
self.multiplicity = json.get('multiplicity', None)

def property(self, name):
# type: (str) -> Property
Expand Down Expand Up @@ -148,6 +158,44 @@ def model(self):
else:
raise NotFoundError("Part {} has no model".format(self.name))

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 instance(self):
"""
Retrieve the single (expected) instance of this 'Part' (of `Category.MODEL`) as a 'Part'.
See `Part.instances()` method for documentation.
:return: pykechain.models.Part
:raises: NotFoundError, MultipleFoundError
"""
instances_list = list(self.instances())
if len(instances_list) == 1:
return instances_list[0]
else:
raise MultipleFoundError("Part {} has more than a single instance. "
"Use the `Part.instances()` method".format(self.name))

def proxy_model(self):
"""
Retrieve the proxy model of this proxied `Part` as a `Part`.
Expand Down
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ tox==2.7.0
flake8==3.3.0
pydocstyle==2.0.0
mypy==0.511

# to fix connection issues
PyOpenSSL==17.0.0; python_version <= '2.7'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"recorded_with": "betamax/0.8.0", "http_interactions": [{"request": {"body": {"encoding": "utf-8", "string": ""}, "method": "GET", "headers": {"PyKechain-Version": "1.7.3", "Accept-Encoding": "gzip, deflate", "Accept": "*/*", "X-Requested-With": "XMLHttpRequest", "User-Agent": "python-requests/2.17.3", "Authorization": "Token <AUTH_TOKEN>", "Connection": "keep-alive"}, "uri": "<API_URL>/api/scopes.json?status=ACTIVE&name=Bike+Project+%28pykechain+testing%29"}, "response": {"body": {"base64_string": "H4sIAAAAAAAAA5VT24rbMBD9lUVPLUSJLNmWladtt/tQKLTQZR9alqDLOHFiy0GSe2HJv3ecyzZLH5JiG6TROXNmxkfPJEAc2hTJ/PszGUJL5mSV0jbOZ7MNWK63zXQD1K5046e272YYmEXbbyHOyloaq2pGOa+A5k5oqpxlFGyhZeGkgMxN17H3ZEIah4mvISDW6w4Q/b7ZwM2X0K/Bpps3299YzljFTYKYGr98i8gEvxIicRWTTgM2Qd7dPXx8vB/PgvaxSU3vD72tmuWqxS8BlpLCAGeku0+fv95/+Cu935Pd04ToGJulBwz6oW0npIPOQDhOK0I4EsaaOu31EgJmgU434yDNrZn6duw+Lg7Ek/KRdhwMPyCO/CMEI9qm5gccArvJP4Lj9kxNX6kmXqvVuo0X5V6mf6Z3WUn+f1/r3q6gm2JK8C6eqb0+uEVP/uzDJo6evFxIeU0hT3tDhLRwOo1czjJJWUZ5+cDFnDF8v2E+N8AFhBnsBtCXz6TrHbSL0PejS6XiWlgDlImiRPernFZ5VVNjramN4Ky24nRTcsYQxSXN8ArSXJaaVlziTTGykBxKBcBGrMeKvYWTRF3wTAgBVGfgaA5lQU1WllQpVatMi8rWGdlNyDb0FuLofOY0FEooWglhaV5JTY0qc8oKl1sBqgaryJ6wDHsGm8oqO38U/iPXJG3al0Hu/gANgxWnVgQAAA==", "encoding": null, "string": ""}, "url": "<API_URL>/api/scopes.json?status=ACTIVE&name=Bike+Project+%28pykechain+testing%29", "status": {"code": 200, "message": "OK"}, "headers": {"X-XSS-Protection": "1; mode=block", "Server": "nginx/1.10.2", "Content-Encoding": "gzip", "X-Content-Type-Options": "nosniff", "Date": "Mon, 05 Jun 2017 09:39:29 GMT", "Content-Type": "application/json", "Connection": "keep-alive", "Strict-Transport-Security": "max-age=518400; includeSubDomains", "Allow": "GET, POST, OPTIONS", "X-Frame-Options": "SAMEORIGIN", "Transfer-Encoding": "chunked", "Vary": "Accept-Encoding"}}, "recorded_at": "2017-06-05T09:39:29"}, {"request": {"body": {"encoding": "utf-8", "string": ""}, "method": "GET", "headers": {"PyKechain-Version": "1.7.3", "Accept-Encoding": "gzip, deflate", "Accept": "*/*", "X-Requested-With": "XMLHttpRequest", "User-Agent": "python-requests/2.17.3", "Authorization": "Token <AUTH_TOKEN>", "Connection": "keep-alive"}, "uri": "<API_URL>/api/activities.json?scope=6f7bc9f0-228e-4d3a-9dc0-ec5a75d73e1d&name=Specify+wheel+diameter"}, "response": {"body": {"base64_string": "H4sIAAAAAAAAA51SwY6bMBD9FeRTK8XEGIOBY9vc2m6k0stWK+TYA3jDAsJmoyjKv3fIKtl2tYeqkrHkmTePmffmRCZwc+cdKX6dyDx1pCCt96Mr1us9aK5GG+6B6lbZPtTD0xoDa6W9fbbegltHWtdZygTdpTtJhZaKZlInNE1iiIXhO5OY8NENPVkRa5D8XwoQ26snQPSPEbStj8GhBegCYzHqYcK8AacnO3qLzAUpW+sCr9w+aJULVKCHAdF/QFZEOWebHhbS8YiTLQO9hrEzP82wIs4rP6MY5G67+Y55P6ne2YXjRaHWNm2Hn3+n5PPdt+3XTbn58tr/NUTODxfg5Cuj/JLhLJKUpZTnJecFY3jul7lmeIuI2V+IcRoa9Ax/GIVshfYpY/vLm4WJjATPbveK6KH3OChKVhBmFCR5nNMsjjUVGSq/y1NBWWKEjiGvQefIPw2Dr/6jTmMnqEql/K13Tpkso7iIsiLhYY71Ml1GmEfzFppSJkoeFUwUCQtFlkdCLlD0qW/AVZ1yvnq2cFh0f4c+CVPBJb8o5PQwooCnqwmf7B6C7TQ8gvbBh5v7gQfnbd98vK5mWsudzmtGOc+AChMrmhvNKOhEycTIGCJDzrgzL+t/rDR2tRj/08FU4vYh0cGaBi761bYhRT933fnh/Bu5iBBpZgMAAA==", "encoding": null, "string": ""}, "url": "<API_URL>/api/activities.json?scope=6f7bc9f0-228e-4d3a-9dc0-ec5a75d73e1d&name=Specify+wheel+diameter", "status": {"code": 200, "message": "OK"}, "headers": {"X-XSS-Protection": "1; mode=block", "Server": "nginx/1.10.2", "Content-Encoding": "gzip", "X-Content-Type-Options": "nosniff", "Date": "Mon, 05 Jun 2017 09:39:29 GMT", "Content-Type": "application/json", "Connection": "keep-alive", "Strict-Transport-Security": "max-age=518400; includeSubDomains", "Allow": "GET, POST, OPTIONS", "X-Frame-Options": "SAMEORIGIN", "Transfer-Encoding": "chunked", "Vary": "Accept-Encoding"}}, "recorded_at": "2017-06-05T09:39:29"}, {"request": {"body": {"encoding": "utf-8", "string": ""}, "method": "GET", "headers": {"PyKechain-Version": "1.7.3", "Accept-Encoding": "gzip, deflate", "Accept": "*/*", "X-Requested-With": "XMLHttpRequest", "User-Agent": "python-requests/2.17.3", "Authorization": "Token <AUTH_TOKEN>", "Connection": "keep-alive"}, "uri": "<API_URL>/api/parts.json?limit=100&category=MODEL&activity_id=1ccf8604-b6b7-4c7a-87c5-653e34d2bd5d"}, "response": {"body": {"base64_string": "H4sIAAAAAAAAA9VWbW/bNhD+K4E++2JRpEgxXwY3cdZgThx07jZgKAKSOsZcFEvQS5EgyH/vyY6crR1Wt3Bh7IvhI4/H53nujrqnyJXdqo1OklG0wgf6s+qKYhRVNX4MZdcMdo1NV7Rk/vkUhTw6iVKeS4HWAovzDIS0GVgpNTiTyMxgLjnj0Sjq6oKcl21bNSfj8R26xFTh+A7BLU1YHbvyfkwL48rUbTPeJebxX025osArc48U+U24Q7JybFwdqjbQ3klEC24Zivzmvsyx2HJYW68EywrrNuCGUw9gd1YfTdHhf4V9vGkfqx7fxdXi5rfJ7P2UTpVrfHTf0/Moatq6c21X4+B5/XKyd+zaqiM43hQNbqn+jKZuaHctv1ZcSqUkpEIIEEIxyFSag3fKa6EMS0Tcy78K25Q60+JtWT/S6cv52XQWPY9+HO/z2XyyN+aLsjXF0RLD7bIdBHCcaaN0AtprBJFqAzr2OWjm00RznkqLhxRgslhMTt9eTveX/+uw9h8EsMi1Y8pDgrEFYZMETIoSvGK5lVYwnrJDCrCY/rE36md/a++hASRj6EUK1AfUAKn3kMk4BeWQO9Rx7Hh2SPpnk8V0cXE53ZcE04cq1GF1e5QTi0EEk1olc06ZR2EItaF2sFaBltwpJ2Mls4O+ArOLq1/2JcDvaJvwSl0wlEmSM5ApKnoA6UcbUkJJE7OUO5Hog5b/m/l8Np1c7Yv9r6bAnwbu0mGWaacAtZUgUEqwLhWQeSlEjCzTsfwq9w//skhLhWma4ANtbb6l1+/mZ+9PF+tvbIGtsQXBIfgEDfPwD3tAvrG+5NgrPeoFx37geNq+a3WZk+eRK1ctzQRYDzRTlbEEEw+eZ5RiKTkYwRQY4qekykkCpBxG9zSZhKoILrQ9lflVr/drtEFJytDD46DH0jQ3TWe3IJsN7L4i1nejdYwuzCEnZUH4hEPGhAXtXCa5YIwl8beON7vE/Gy8Oa+JxtF5Wd/tccjZkdvutX76dvLu+wt9Uy4vhC+pJOtgim2pM61MxgykLnEgmDdgtGegNUsk15m3sf//lPrLtLr7AL3n6v7w/AnxA5W18AsAAA==", "encoding": null, "string": ""}, "url": "<API_URL>/api/parts.json?limit=100&category=MODEL&activity_id=1ccf8604-b6b7-4c7a-87c5-653e34d2bd5d", "status": {"code": 200, "message": "OK"}, "headers": {"X-XSS-Protection": "1; mode=block", "Server": "nginx/1.10.2", "Content-Encoding": "gzip", "X-Content-Type-Options": "nosniff", "Date": "Mon, 05 Jun 2017 09:39:29 GMT", "Content-Type": "application/json", "Connection": "keep-alive", "Strict-Transport-Security": "max-age=518400; includeSubDomains", "Allow": "GET, POST, OPTIONS", "X-Frame-Options": "SAMEORIGIN", "Transfer-Encoding": "chunked", "Vary": "Accept-Encoding"}}, "recorded_at": "2017-06-05T09:39:29"}, {"request": {"body": {"encoding": "utf-8", "string": ""}, "method": "GET", "headers": {"PyKechain-Version": "1.7.3", "Accept-Encoding": "gzip, deflate", "Accept": "*/*", "X-Requested-With": "XMLHttpRequest", "User-Agent": "python-requests/2.17.3", "Authorization": "Token <AUTH_TOKEN>", "Connection": "keep-alive"}, "uri": "<API_URL>/api/parts.json?limit=100&category=INSTANCE&activity_id=1ccf8604-b6b7-4c7a-87c5-653e34d2bd5d"}, "response": {"body": {"base64_string": "H4sIAAAAAAAAA8WXbW/bNhDHv0qg12EiPorMm8FN3a1bmhStuw0YiuBIHmstsmRIctsg6HffyY7cNPMAr3C2N4JFkce7H++Of99loVnVfXYmjrMaP9OPelVVx9myxY9ls+rG9xa7VdXT6x93WRmzsww1z41Thqk8FUx55RigRqYEVzq3unCcZ8fZqq1o8rzvl93Z6ekNBgHL8uQGWZhDWZ+EZnFKA6dLaPvudB+bJ392TU2Ga1ggWX5W3iC9RexCWy77kr6dZTQQ5mUVrxdNxGrjdGgRevAVLUpQdbi18KJt6v7oRdPe0LJNaD5wY2Rk0QfNVBKSWa48cyFYIxXnXOTrPSt8aPHL++NsvWF2dvfIvbVZLaNR6D3jebRMGW+ZN8axAMJYwGgkl9mXAX2zxLYvceP4gGZ/3h+hWtHGPN/6krlCGlMUhmmlFFOq4MwWOrIUiuRUAVyoIZz7bW+v+9vl4PrLy9n1r5OLd1P61qzJkj935F/Xt6vQr1ocZ76+XzlMXPXLVf8Y8Y8IbTdi4CJhAiuYUJowWMkZxBgZGGeli5FDKIbEqcttMgbo8UPT3q69ejubXJ5PCdR3otmYHOEEyR0UTjCXHK3QDpjLU2SOJy2clNp43AHnxcXV5GB4Zk0P1dEcyw/zfqQEUnA6N8m8NXRozmnmtHfMeTDCAQglnpRSBn0PYb7AmuoyKDA+R858cjlBCvRLCcus4h4QZK64OY1UXtc/X1xWb2Y3Y5GOlD1KF3iRmMDc095CMNBoWCp49MYrLjXfQXkym03Of3o1PVwmvi7X80fK+wT2pJQfMHKGc0xKM6pWOnGdErMm16wIKAO6PA/S7mA0m/5+MDrPH3TRe0LOGesdUKFCoigkBgZD87BRgQeV5yb/D6sVtC9MlJQ9qIBaKFDdel8wZ2QoArli7K5W9nwym85evpoeitP087Jsy/rDEeX8Npdi4gY9dRLh86HNcmBWcIorWAdRGAOQnjSXhkuW7thPnz6d1N1JXT3ILcXRCBE5MxrJkqKHow7DCgM51zIo4XbV38XLy18Oxew39F35lZYKEWlrxUROia4ACmZzepgQffSgrcXwlLTIf/zKxwS01oWCofNkAI1hdPUrZpNRivqDdbnZwefZ1dXFdHJ5KERvocIfRkDWR0KUAgtRUMvkkQovucRCkMpZk1KSfh9A73eP02gFXVemkr5uVNPrN1fP353Pdiib4wxjeT+wAfcogL/HOpzJ8XA0OIjLrR4iBpFmHgXSXKT/sB3DlTF5AZLygdtiEEeBRIoMTBN7Ha1PMeEgjhakQstlVYayH+K5ulyHsrU2OkQn9fl25DKH7rpb+a2T3SaKIXc2d60pkgVHKq8YUKs8MJ9TMvDoTMRCOu70v5Wy+9h8JGW/EaLfCtrJEWVOwtBXt0d10y5ILnwz+5Ha3SFEv1Pl/qMc3ZPZ2Jve1Y3voS5XiwddyXBXgKU+qYMIlOIJGLjEmXNcGOls8nnaUXXnP03efH/JbfL3nsorqoy2hGpbdNJH7ahP0o1HHhlumEN6FFHRqIoxBPX/F93mbe+ae/gfZK8meeAye//lL+7ipy1lDgAA", "encoding": null, "string": ""}, "url": "<API_URL>/api/parts.json?limit=100&category=INSTANCE&activity_id=1ccf8604-b6b7-4c7a-87c5-653e34d2bd5d", "status": {"code": 200, "message": "OK"}, "headers": {"X-XSS-Protection": "1; mode=block", "Server": "nginx/1.10.2", "Content-Encoding": "gzip", "X-Content-Type-Options": "nosniff", "Date": "Mon, 05 Jun 2017 09:39:29 GMT", "Content-Type": "application/json", "Connection": "keep-alive", "Strict-Transport-Security": "max-age=518400; includeSubDomains", "Allow": "GET, POST, OPTIONS", "X-Frame-Options": "SAMEORIGIN", "Transfer-Encoding": "chunked", "Vary": "Accept-Encoding"}}, "recorded_at": "2017-06-05T09:39:29"}]}
Loading

0 comments on commit 793bed8

Please sign in to comment.