Ayon-Python-Api 0.3.3__tar.gz → 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/PKG-INFO +2 -3
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/README.md +1 -2
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_api/entity_hub.py +744 -4
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_api/server_api.py +33 -13
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_api/version.py +1 -1
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_python_api.egg-info/PKG-INFO +2 -3
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/pyproject.toml +1 -1
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/LICENSE +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_api/__init__.py +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_api/_api.py +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_api/constants.py +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_api/events.py +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_api/exceptions.py +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_api/graphql.py +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_api/graphql_queries.py +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_api/operations.py +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_api/thumbnails.py +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_api/utils.py +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_python_api.egg-info/SOURCES.txt +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_python_api.egg-info/dependency_links.txt +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_python_api.egg-info/requires.txt +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_python_api.egg-info/top_level.txt +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/setup.cfg +0 -0
- {ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ayon-python-api
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: AYON Python API
|
|
5
5
|
Home-page: https://github.com/ynput/ayon-python-api
|
|
6
6
|
Author: ynput.io
|
|
@@ -273,8 +273,7 @@ If pip complain that `ayon-python-api` is already installed just uninstall exist
|
|
|
273
273
|
- Move entity hub to first place
|
|
274
274
|
- Skip those which are invalid for the entity and fake it for base or remove it from base
|
|
275
275
|
- Entity hub should use operations session to do changes
|
|
276
|
-
- Entity hub could also handle '
|
|
277
|
-
- Missing 'statuses' on project
|
|
276
|
+
- Entity hub could also handle 'product', 'version' and 'representation' entities
|
|
278
277
|
- Missing 'status' on folders
|
|
279
278
|
- Missing assignees on tasks
|
|
280
279
|
- Pass docstrings and arguments definitions from `ServerAPI` methods to global functions
|
|
@@ -54,8 +54,7 @@ If pip complain that `ayon-python-api` is already installed just uninstall exist
|
|
|
54
54
|
- Move entity hub to first place
|
|
55
55
|
- Skip those which are invalid for the entity and fake it for base or remove it from base
|
|
56
56
|
- Entity hub should use operations session to do changes
|
|
57
|
-
- Entity hub could also handle '
|
|
58
|
-
- Missing 'statuses' on project
|
|
57
|
+
- Entity hub could also handle 'product', 'version' and 'representation' entities
|
|
59
58
|
- Missing 'status' on folders
|
|
60
59
|
- Missing assignees on tasks
|
|
61
60
|
- Pass docstrings and arguments definitions from `ServerAPI` methods to global functions
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import re
|
|
1
2
|
import copy
|
|
2
3
|
import collections
|
|
3
4
|
from abc import ABCMeta, abstractmethod
|
|
4
5
|
|
|
5
6
|
import six
|
|
6
7
|
from ._api import get_server_api_connection
|
|
7
|
-
from .utils import create_entity_id, convert_entity_id
|
|
8
|
+
from .utils import create_entity_id, convert_entity_id, slugify_string
|
|
8
9
|
|
|
9
10
|
UNKNOWN_VALUE = object()
|
|
10
11
|
PROJECT_PARENT_ID = object()
|
|
@@ -545,6 +546,7 @@ class EntityHub(object):
|
|
|
545
546
|
library=project["library"],
|
|
546
547
|
folder_types=project["folderTypes"],
|
|
547
548
|
task_types=project["taskTypes"],
|
|
549
|
+
statuses=project["statuses"],
|
|
548
550
|
name=project["name"],
|
|
549
551
|
attribs=project["ownAttrib"],
|
|
550
552
|
data=project["data"],
|
|
@@ -775,8 +777,7 @@ class EntityHub(object):
|
|
|
775
777
|
"projects/{}".format(self.project_name),
|
|
776
778
|
**project_changes
|
|
777
779
|
)
|
|
778
|
-
|
|
779
|
-
raise ValueError("Failed to update project")
|
|
780
|
+
response.raise_for_status()
|
|
780
781
|
|
|
781
782
|
self.project_entity.lock()
|
|
782
783
|
|
|
@@ -1485,6 +1486,722 @@ class BaseEntity(object):
|
|
|
1485
1486
|
self._children_ids = set(children_ids)
|
|
1486
1487
|
|
|
1487
1488
|
|
|
1489
|
+
class ProjectStatus:
|
|
1490
|
+
"""Project status class.
|
|
1491
|
+
|
|
1492
|
+
Args:
|
|
1493
|
+
name (str): Name of the status. e.g. 'In progress'
|
|
1494
|
+
short_name (Optional[str]): Short name of the status. e.g. 'IP'
|
|
1495
|
+
state (Optional[Literal[not_started, in_progress, done, blocked]]): A
|
|
1496
|
+
state of the status.
|
|
1497
|
+
icon (Optional[str]): Icon of the status. e.g. 'play_arrow'.
|
|
1498
|
+
color (Optional[str]): Color of the status. e.g. '#eeeeee'.
|
|
1499
|
+
index (Optional[int]): Index of the status.
|
|
1500
|
+
project_statuses (Optional[_ProjectStatuses]): Project statuses
|
|
1501
|
+
wrapper.
|
|
1502
|
+
"""
|
|
1503
|
+
|
|
1504
|
+
valid_states = ("not_started", "in_progress", "done", "blocked")
|
|
1505
|
+
color_regex = re.compile(r"#([a-f0-9]{6})$")
|
|
1506
|
+
default_state = "in_progress"
|
|
1507
|
+
default_color = "#eeeeee"
|
|
1508
|
+
|
|
1509
|
+
def __init__(
|
|
1510
|
+
self,
|
|
1511
|
+
name,
|
|
1512
|
+
short_name=None,
|
|
1513
|
+
state=None,
|
|
1514
|
+
icon=None,
|
|
1515
|
+
color=None,
|
|
1516
|
+
index=None,
|
|
1517
|
+
project_statuses=None,
|
|
1518
|
+
is_new=None,
|
|
1519
|
+
):
|
|
1520
|
+
short_name = short_name or ""
|
|
1521
|
+
icon = icon or ""
|
|
1522
|
+
state = state or self.default_state
|
|
1523
|
+
color = color or self.default_color
|
|
1524
|
+
self._name = name
|
|
1525
|
+
self._short_name = short_name
|
|
1526
|
+
self._icon = icon
|
|
1527
|
+
self._slugified_name = None
|
|
1528
|
+
self._state = None
|
|
1529
|
+
self._color = None
|
|
1530
|
+
self.set_state(state)
|
|
1531
|
+
self.set_color(color)
|
|
1532
|
+
|
|
1533
|
+
self._original_name = name
|
|
1534
|
+
self._original_short_name = short_name
|
|
1535
|
+
self._original_icon = icon
|
|
1536
|
+
self._original_state = state
|
|
1537
|
+
self._original_color = color
|
|
1538
|
+
self._original_index = index
|
|
1539
|
+
|
|
1540
|
+
self._index = index
|
|
1541
|
+
self._project_statuses = project_statuses
|
|
1542
|
+
if is_new is None:
|
|
1543
|
+
is_new = index is None or project_statuses is None
|
|
1544
|
+
self._is_new = is_new
|
|
1545
|
+
|
|
1546
|
+
def __str__(self):
|
|
1547
|
+
short_name = ""
|
|
1548
|
+
if self.short_name:
|
|
1549
|
+
short_name = "({})".format(self.short_name)
|
|
1550
|
+
return "<{} {}{}>".format(
|
|
1551
|
+
self.__class__.__name__, self.name, short_name
|
|
1552
|
+
)
|
|
1553
|
+
|
|
1554
|
+
def __repr__(self):
|
|
1555
|
+
return str(self)
|
|
1556
|
+
|
|
1557
|
+
def __getitem__(self, key):
|
|
1558
|
+
if key in {
|
|
1559
|
+
"name", "short_name", "icon", "state", "color", "slugified_name"
|
|
1560
|
+
}:
|
|
1561
|
+
return getattr(self, key)
|
|
1562
|
+
raise KeyError(key)
|
|
1563
|
+
|
|
1564
|
+
def __setitem__(self, key, value):
|
|
1565
|
+
if key in {"name", "short_name", "icon", "state", "color"}:
|
|
1566
|
+
return setattr(self, key, value)
|
|
1567
|
+
raise KeyError(key)
|
|
1568
|
+
|
|
1569
|
+
def lock(self):
|
|
1570
|
+
"""Lock status.
|
|
1571
|
+
|
|
1572
|
+
Changes were commited and current values are now the original values.
|
|
1573
|
+
"""
|
|
1574
|
+
|
|
1575
|
+
self._is_new = False
|
|
1576
|
+
self._original_name = self.name
|
|
1577
|
+
self._original_short_name = self.short_name
|
|
1578
|
+
self._original_icon = self.icon
|
|
1579
|
+
self._original_state = self.state
|
|
1580
|
+
self._original_color = self.color
|
|
1581
|
+
self._original_index = self.index
|
|
1582
|
+
|
|
1583
|
+
@staticmethod
|
|
1584
|
+
def slugify_name(name):
|
|
1585
|
+
"""Slugify status name for name comparison.
|
|
1586
|
+
|
|
1587
|
+
Args:
|
|
1588
|
+
name (str): Name of the status.
|
|
1589
|
+
|
|
1590
|
+
Returns:
|
|
1591
|
+
str: Slugified name.
|
|
1592
|
+
"""
|
|
1593
|
+
|
|
1594
|
+
return slugify_string(name.lower())
|
|
1595
|
+
|
|
1596
|
+
def get_project_statuses(self):
|
|
1597
|
+
"""Internal logic method.
|
|
1598
|
+
|
|
1599
|
+
Returns:
|
|
1600
|
+
_ProjectStatuses: Project statuses object.
|
|
1601
|
+
"""
|
|
1602
|
+
|
|
1603
|
+
return self._project_statuses
|
|
1604
|
+
|
|
1605
|
+
def set_project_statuses(self, project_statuses):
|
|
1606
|
+
"""Internal logic method to change parent object.
|
|
1607
|
+
|
|
1608
|
+
Args:
|
|
1609
|
+
project_statuses (_ProjectStatuses): Project statuses object.
|
|
1610
|
+
"""
|
|
1611
|
+
|
|
1612
|
+
self._project_statuses = project_statuses
|
|
1613
|
+
|
|
1614
|
+
def unset_project_statuses(self, project_statuses):
|
|
1615
|
+
"""Internal logic method to unset parent object.
|
|
1616
|
+
|
|
1617
|
+
Args:
|
|
1618
|
+
project_statuses (_ProjectStatuses): Project statuses object.
|
|
1619
|
+
"""
|
|
1620
|
+
|
|
1621
|
+
if self._project_statuses is project_statuses:
|
|
1622
|
+
self._project_statuses = None
|
|
1623
|
+
self._index = None
|
|
1624
|
+
|
|
1625
|
+
@property
|
|
1626
|
+
def changed(self):
|
|
1627
|
+
"""Status has changed.
|
|
1628
|
+
|
|
1629
|
+
Returns:
|
|
1630
|
+
bool: Status has changed.
|
|
1631
|
+
"""
|
|
1632
|
+
|
|
1633
|
+
return (
|
|
1634
|
+
self._is_new
|
|
1635
|
+
or self._original_name != self._name
|
|
1636
|
+
or self._original_short_name != self._short_name
|
|
1637
|
+
or self._original_index != self._index
|
|
1638
|
+
or self._original_state != self._state
|
|
1639
|
+
or self._original_icon != self._icon
|
|
1640
|
+
or self._original_color != self._color
|
|
1641
|
+
)
|
|
1642
|
+
|
|
1643
|
+
def delete(self):
|
|
1644
|
+
"""Remove status from project statuses object."""
|
|
1645
|
+
|
|
1646
|
+
if self._project_statuses is not None:
|
|
1647
|
+
self._project_statuses.remove(self)
|
|
1648
|
+
|
|
1649
|
+
def get_index(self):
|
|
1650
|
+
"""Get index of status.
|
|
1651
|
+
|
|
1652
|
+
Returns:
|
|
1653
|
+
Union[int, None]: Index of status or None if status is not under
|
|
1654
|
+
project.
|
|
1655
|
+
"""
|
|
1656
|
+
|
|
1657
|
+
return self._index
|
|
1658
|
+
|
|
1659
|
+
def set_index(self, index, **kwargs):
|
|
1660
|
+
"""Change status index.
|
|
1661
|
+
|
|
1662
|
+
Returns:
|
|
1663
|
+
Union[int, None]: Index of status or None if status is not under
|
|
1664
|
+
project.
|
|
1665
|
+
"""
|
|
1666
|
+
|
|
1667
|
+
if kwargs.get("from_parent"):
|
|
1668
|
+
self._index = index
|
|
1669
|
+
else:
|
|
1670
|
+
self._project_statuses.set_status_index(self, index)
|
|
1671
|
+
|
|
1672
|
+
def get_name(self):
|
|
1673
|
+
"""Status name.
|
|
1674
|
+
|
|
1675
|
+
Returns:
|
|
1676
|
+
str: Status name.
|
|
1677
|
+
"""
|
|
1678
|
+
|
|
1679
|
+
return self._name
|
|
1680
|
+
|
|
1681
|
+
def set_name(self, name):
|
|
1682
|
+
"""Change status name.
|
|
1683
|
+
|
|
1684
|
+
Args:
|
|
1685
|
+
name (str): New status name.
|
|
1686
|
+
"""
|
|
1687
|
+
|
|
1688
|
+
if not isinstance(name, six.string_types):
|
|
1689
|
+
raise TypeError("Name must be a string.")
|
|
1690
|
+
if name == self._name:
|
|
1691
|
+
return
|
|
1692
|
+
self._name = name
|
|
1693
|
+
self._slugified_name = None
|
|
1694
|
+
|
|
1695
|
+
def get_short_name(self):
|
|
1696
|
+
"""Status short name 3 letters tops.
|
|
1697
|
+
|
|
1698
|
+
Returns:
|
|
1699
|
+
str: Status short name.
|
|
1700
|
+
"""
|
|
1701
|
+
|
|
1702
|
+
return self._short_name
|
|
1703
|
+
|
|
1704
|
+
def set_short_name(self, short_name):
|
|
1705
|
+
"""Change status short name.
|
|
1706
|
+
|
|
1707
|
+
Args:
|
|
1708
|
+
short_name (str): New status short name. 3 letters tops.
|
|
1709
|
+
"""
|
|
1710
|
+
|
|
1711
|
+
if not isinstance(short_name, six.string_types):
|
|
1712
|
+
raise TypeError("Short name must be a string.")
|
|
1713
|
+
self._short_name = short_name
|
|
1714
|
+
|
|
1715
|
+
def get_icon(self):
|
|
1716
|
+
"""Name of icon to use for status.
|
|
1717
|
+
|
|
1718
|
+
Returns:
|
|
1719
|
+
str: Name of the icon.
|
|
1720
|
+
"""
|
|
1721
|
+
|
|
1722
|
+
return self._icon
|
|
1723
|
+
|
|
1724
|
+
def set_icon(self, icon):
|
|
1725
|
+
"""Change status icon name.
|
|
1726
|
+
|
|
1727
|
+
Args:
|
|
1728
|
+
icon (str): Name of the icon.
|
|
1729
|
+
"""
|
|
1730
|
+
|
|
1731
|
+
if icon is None:
|
|
1732
|
+
icon = ""
|
|
1733
|
+
if not isinstance(icon, six.string_types):
|
|
1734
|
+
raise TypeError("Icon name must be a string.")
|
|
1735
|
+
self._icon = icon
|
|
1736
|
+
|
|
1737
|
+
@property
|
|
1738
|
+
def slugified_name(self):
|
|
1739
|
+
"""Slugified and lowere status name.
|
|
1740
|
+
|
|
1741
|
+
Can be used for comparison of existing statuses. e.g. 'In Progress'
|
|
1742
|
+
vs. 'in-progress'.
|
|
1743
|
+
|
|
1744
|
+
Returns:
|
|
1745
|
+
str: Slugified and lower status name.
|
|
1746
|
+
"""
|
|
1747
|
+
|
|
1748
|
+
if self._slugified_name is None:
|
|
1749
|
+
self._slugified_name = self.slugify_name(self.name)
|
|
1750
|
+
return self._slugified_name
|
|
1751
|
+
|
|
1752
|
+
def get_state(self):
|
|
1753
|
+
"""Get state of project status.
|
|
1754
|
+
|
|
1755
|
+
Return:
|
|
1756
|
+
Literal[not_started, in_progress, done, blocked]: General
|
|
1757
|
+
state of status.
|
|
1758
|
+
"""
|
|
1759
|
+
|
|
1760
|
+
return self._state
|
|
1761
|
+
|
|
1762
|
+
def set_state(self, state):
|
|
1763
|
+
"""Set color of project status.
|
|
1764
|
+
|
|
1765
|
+
Args:
|
|
1766
|
+
state (Literal[not_started, in_progress, done, blocked]): General
|
|
1767
|
+
state of status.
|
|
1768
|
+
"""
|
|
1769
|
+
|
|
1770
|
+
if state not in self.valid_states:
|
|
1771
|
+
raise ValueError("Invalid state '{}'".format(str(state)))
|
|
1772
|
+
self._state = state
|
|
1773
|
+
|
|
1774
|
+
def get_color(self):
|
|
1775
|
+
"""Get color of project status.
|
|
1776
|
+
|
|
1777
|
+
Returns:
|
|
1778
|
+
str: Status color.
|
|
1779
|
+
"""
|
|
1780
|
+
|
|
1781
|
+
return self._color
|
|
1782
|
+
|
|
1783
|
+
def set_color(self, color):
|
|
1784
|
+
"""Set color of project status.
|
|
1785
|
+
|
|
1786
|
+
Args:
|
|
1787
|
+
color (str): Color in hex format. Example: '#ff0000'.
|
|
1788
|
+
"""
|
|
1789
|
+
|
|
1790
|
+
if not isinstance(color, six.string_types):
|
|
1791
|
+
raise TypeError(
|
|
1792
|
+
"Color must be string got '{}'".format(type(color)))
|
|
1793
|
+
color = color.lower()
|
|
1794
|
+
if self.color_regex.fullmatch(color) is None:
|
|
1795
|
+
raise ValueError("Invalid color value '{}'".format(color))
|
|
1796
|
+
self._color = color
|
|
1797
|
+
|
|
1798
|
+
name = property(get_name, set_name)
|
|
1799
|
+
short_name = property(get_short_name, set_short_name)
|
|
1800
|
+
project_statuses = property(get_project_statuses, set_project_statuses)
|
|
1801
|
+
index = property(get_index, set_index)
|
|
1802
|
+
state = property(get_state, set_state)
|
|
1803
|
+
color = property(get_color, set_color)
|
|
1804
|
+
icon = property(get_icon, set_icon)
|
|
1805
|
+
|
|
1806
|
+
def _validate_other_p_statuses(self, other):
|
|
1807
|
+
"""Validate if other status can be used for move.
|
|
1808
|
+
|
|
1809
|
+
To be able to work with other status, and position them in relation,
|
|
1810
|
+
they must belong to same existing object of '_ProjectStatuses'.
|
|
1811
|
+
|
|
1812
|
+
Args:
|
|
1813
|
+
other (ProjectStatus): Other status to validate.
|
|
1814
|
+
"""
|
|
1815
|
+
|
|
1816
|
+
o_project_statuses = other.project_statuses
|
|
1817
|
+
m_project_statuses = self.project_statuses
|
|
1818
|
+
if o_project_statuses is None and m_project_statuses is None:
|
|
1819
|
+
raise ValueError("Both statuses are not assigned to a project.")
|
|
1820
|
+
|
|
1821
|
+
missing_status = None
|
|
1822
|
+
if o_project_statuses is None:
|
|
1823
|
+
missing_status = other
|
|
1824
|
+
elif m_project_statuses is None:
|
|
1825
|
+
missing_status = self
|
|
1826
|
+
if missing_status is not None:
|
|
1827
|
+
raise ValueError(
|
|
1828
|
+
"Status '{}' is not assigned to a project.".format(
|
|
1829
|
+
missing_status.name))
|
|
1830
|
+
if m_project_statuses is not o_project_statuses:
|
|
1831
|
+
raise ValueError(
|
|
1832
|
+
"Statuse are assigned to different projects."
|
|
1833
|
+
" Cannot execute move."
|
|
1834
|
+
)
|
|
1835
|
+
|
|
1836
|
+
def move_before(self, other):
|
|
1837
|
+
"""Move status before other status.
|
|
1838
|
+
|
|
1839
|
+
Args:
|
|
1840
|
+
other (ProjectStatus): Status to move before.
|
|
1841
|
+
"""
|
|
1842
|
+
|
|
1843
|
+
self._validate_other_p_statuses(other)
|
|
1844
|
+
self._project_statuses.set_status_index(self, other.index)
|
|
1845
|
+
|
|
1846
|
+
def move_after(self, other):
|
|
1847
|
+
"""Move status after other status.
|
|
1848
|
+
|
|
1849
|
+
Args:
|
|
1850
|
+
other (ProjectStatus): Status to move after.
|
|
1851
|
+
"""
|
|
1852
|
+
|
|
1853
|
+
self._validate_other_p_statuses(other)
|
|
1854
|
+
self._project_statuses.set_status_index(self, other.index + 1)
|
|
1855
|
+
|
|
1856
|
+
def to_data(self):
|
|
1857
|
+
"""Convert status to data.
|
|
1858
|
+
|
|
1859
|
+
Returns:
|
|
1860
|
+
dict[str, str]: Status data.
|
|
1861
|
+
"""
|
|
1862
|
+
|
|
1863
|
+
output = {
|
|
1864
|
+
"name": self.name,
|
|
1865
|
+
"shortName": self.short_name,
|
|
1866
|
+
"state": self.state,
|
|
1867
|
+
"icon": self.icon,
|
|
1868
|
+
"color": self.color,
|
|
1869
|
+
}
|
|
1870
|
+
if (
|
|
1871
|
+
not self._is_new
|
|
1872
|
+
and self._original_name
|
|
1873
|
+
and self.name != self._original_name
|
|
1874
|
+
):
|
|
1875
|
+
output["original_name"] = self._original_name
|
|
1876
|
+
return output
|
|
1877
|
+
|
|
1878
|
+
@classmethod
|
|
1879
|
+
def from_data(cls, data, index=None, project_statuses=None):
|
|
1880
|
+
"""Create project status from data.
|
|
1881
|
+
|
|
1882
|
+
Args:
|
|
1883
|
+
data (dict[str, str]): Status data.
|
|
1884
|
+
index (Optional[int]): Status index.
|
|
1885
|
+
project_statuses (Optional[ProjectStatuses]): Project statuses
|
|
1886
|
+
object which wraps the status for a project.
|
|
1887
|
+
"""
|
|
1888
|
+
|
|
1889
|
+
return cls(
|
|
1890
|
+
data["name"],
|
|
1891
|
+
data.get("shortName", data.get("short_name")),
|
|
1892
|
+
data.get("state"),
|
|
1893
|
+
data.get("icon"),
|
|
1894
|
+
data.get("color"),
|
|
1895
|
+
index=index,
|
|
1896
|
+
project_statuses=project_statuses
|
|
1897
|
+
)
|
|
1898
|
+
|
|
1899
|
+
|
|
1900
|
+
class _ProjectStatuses:
|
|
1901
|
+
"""Wrapper for project statuses.
|
|
1902
|
+
|
|
1903
|
+
Supports basic methods to add, change or remove statuses from a project.
|
|
1904
|
+
|
|
1905
|
+
To add new statuses use 'create' or 'add_status' methods. To change
|
|
1906
|
+
statuses receive them by one of the getter methods and change their
|
|
1907
|
+
values.
|
|
1908
|
+
|
|
1909
|
+
Todos:
|
|
1910
|
+
Validate if statuses are duplicated.
|
|
1911
|
+
"""
|
|
1912
|
+
|
|
1913
|
+
def __init__(self, statuses):
|
|
1914
|
+
self._statuses = [
|
|
1915
|
+
ProjectStatus.from_data(status, idx, self)
|
|
1916
|
+
for idx, status in enumerate(statuses)
|
|
1917
|
+
]
|
|
1918
|
+
self._orig_status_length = len(self._statuses)
|
|
1919
|
+
self._set_called = False
|
|
1920
|
+
|
|
1921
|
+
def __len__(self):
|
|
1922
|
+
return len(self._statuses)
|
|
1923
|
+
|
|
1924
|
+
def __iter__(self):
|
|
1925
|
+
"""Iterate over statuses.
|
|
1926
|
+
|
|
1927
|
+
Yields:
|
|
1928
|
+
ProjectStatus: Project status.
|
|
1929
|
+
"""
|
|
1930
|
+
|
|
1931
|
+
for status in self._statuses:
|
|
1932
|
+
yield status
|
|
1933
|
+
|
|
1934
|
+
def create(
|
|
1935
|
+
self,
|
|
1936
|
+
name,
|
|
1937
|
+
short_name=None,
|
|
1938
|
+
state=None,
|
|
1939
|
+
icon=None,
|
|
1940
|
+
color=None,
|
|
1941
|
+
):
|
|
1942
|
+
"""Create project status.
|
|
1943
|
+
|
|
1944
|
+
Args:
|
|
1945
|
+
name (str): Name of the status. e.g. 'In progress'
|
|
1946
|
+
short_name (Optional[str]): Short name of the status. e.g. 'IP'
|
|
1947
|
+
state (Optional[Literal[not_started, in_progress, done, blocked]]): A
|
|
1948
|
+
state of the status.
|
|
1949
|
+
icon (Optional[str]): Icon of the status. e.g. 'play_arrow'.
|
|
1950
|
+
color (Optional[str]): Color of the status. e.g. '#eeeeee'.
|
|
1951
|
+
|
|
1952
|
+
Returns:
|
|
1953
|
+
ProjectStatus: Created project status.
|
|
1954
|
+
"""
|
|
1955
|
+
|
|
1956
|
+
status = ProjectStatus(
|
|
1957
|
+
name, short_name, state, icon, color, is_new=True
|
|
1958
|
+
)
|
|
1959
|
+
self.append(status)
|
|
1960
|
+
return status
|
|
1961
|
+
|
|
1962
|
+
def lock(self):
|
|
1963
|
+
"""Lock statuses.
|
|
1964
|
+
|
|
1965
|
+
Changes were commited and current values are now the original values.
|
|
1966
|
+
"""
|
|
1967
|
+
|
|
1968
|
+
self._orig_status_length = len(self._statuses)
|
|
1969
|
+
self._set_called = False
|
|
1970
|
+
for status in self._statuses:
|
|
1971
|
+
status.lock()
|
|
1972
|
+
|
|
1973
|
+
def to_data(self):
|
|
1974
|
+
"""Convert to project statuses data."""
|
|
1975
|
+
|
|
1976
|
+
return [
|
|
1977
|
+
status.to_data()
|
|
1978
|
+
for status in self._statuses
|
|
1979
|
+
]
|
|
1980
|
+
|
|
1981
|
+
def set(self, statuses):
|
|
1982
|
+
"""Explicitly override statuses.
|
|
1983
|
+
|
|
1984
|
+
This method does not handle if statuses changed or not.
|
|
1985
|
+
|
|
1986
|
+
Args:
|
|
1987
|
+
statuses (list[dict[str, str]]): List of statuses data.
|
|
1988
|
+
"""
|
|
1989
|
+
|
|
1990
|
+
self._set_called = True
|
|
1991
|
+
self._statuses = [
|
|
1992
|
+
ProjectStatus.from_data(status, idx, self)
|
|
1993
|
+
for idx, status in enumerate(statuses)
|
|
1994
|
+
]
|
|
1995
|
+
|
|
1996
|
+
@property
|
|
1997
|
+
def changed(self):
|
|
1998
|
+
"""Statuses have changed.
|
|
1999
|
+
|
|
2000
|
+
Returns:
|
|
2001
|
+
bool: True if statuses changed, False otherwise.
|
|
2002
|
+
"""
|
|
2003
|
+
|
|
2004
|
+
if self._set_called:
|
|
2005
|
+
return True
|
|
2006
|
+
|
|
2007
|
+
# Check if status length changed
|
|
2008
|
+
# - when all statuses are removed it is a changed
|
|
2009
|
+
if self._orig_status_length != len(self._statuses):
|
|
2010
|
+
return True
|
|
2011
|
+
# Go through all statuses and check if any of them changed
|
|
2012
|
+
for status in self._statuses:
|
|
2013
|
+
if status.changed:
|
|
2014
|
+
return True
|
|
2015
|
+
return False
|
|
2016
|
+
|
|
2017
|
+
def get(self, name, default=None):
|
|
2018
|
+
"""Get status by name.
|
|
2019
|
+
|
|
2020
|
+
Args:
|
|
2021
|
+
name (str): Status name.
|
|
2022
|
+
default (Any): Default value of status is not found.
|
|
2023
|
+
|
|
2024
|
+
Returns:
|
|
2025
|
+
Union[ProjectStatus, Any]: Status or default value.
|
|
2026
|
+
"""
|
|
2027
|
+
|
|
2028
|
+
return next(
|
|
2029
|
+
(
|
|
2030
|
+
status
|
|
2031
|
+
for status in self._statuses
|
|
2032
|
+
if status.name == name
|
|
2033
|
+
),
|
|
2034
|
+
default
|
|
2035
|
+
)
|
|
2036
|
+
|
|
2037
|
+
get_status_by_name = get
|
|
2038
|
+
|
|
2039
|
+
def index(self, status, **kwargs):
|
|
2040
|
+
"""Get status index.
|
|
2041
|
+
|
|
2042
|
+
Args:
|
|
2043
|
+
status (ProjectStatus): Status to get index of.
|
|
2044
|
+
default (Optional[Any]): Default value if status is not found.
|
|
2045
|
+
|
|
2046
|
+
Returns:
|
|
2047
|
+
Union[int, Any]: Status index.
|
|
2048
|
+
|
|
2049
|
+
Raises:
|
|
2050
|
+
ValueError: If status is not found and default value is not
|
|
2051
|
+
defined.
|
|
2052
|
+
"""
|
|
2053
|
+
|
|
2054
|
+
output = next(
|
|
2055
|
+
(
|
|
2056
|
+
idx
|
|
2057
|
+
for idx, st in enumerate(self._statuses)
|
|
2058
|
+
if st is status
|
|
2059
|
+
),
|
|
2060
|
+
None
|
|
2061
|
+
)
|
|
2062
|
+
if output is not None:
|
|
2063
|
+
return output
|
|
2064
|
+
|
|
2065
|
+
if "default" in kwargs:
|
|
2066
|
+
return kwargs["default"]
|
|
2067
|
+
raise ValueError("Status '{}' not found".format(status.name))
|
|
2068
|
+
|
|
2069
|
+
def get_status_by_slugified_name(self, name):
|
|
2070
|
+
"""Get status by slugified name.
|
|
2071
|
+
|
|
2072
|
+
Args:
|
|
2073
|
+
name (str): Status name. Is slugified before search.
|
|
2074
|
+
|
|
2075
|
+
Returns:
|
|
2076
|
+
Union[ProjectStatus, None]: Status or None if not found.
|
|
2077
|
+
"""
|
|
2078
|
+
|
|
2079
|
+
slugified_name = ProjectStatus.slugify_name(name)
|
|
2080
|
+
return next(
|
|
2081
|
+
(
|
|
2082
|
+
status
|
|
2083
|
+
for status in self._statuses
|
|
2084
|
+
if status.slugified_name == slugified_name
|
|
2085
|
+
),
|
|
2086
|
+
None
|
|
2087
|
+
)
|
|
2088
|
+
|
|
2089
|
+
def remove_by_name(self, name, ignore_missing=False):
|
|
2090
|
+
"""Remove status by name.
|
|
2091
|
+
|
|
2092
|
+
Args:
|
|
2093
|
+
name (str): Status name.
|
|
2094
|
+
ignore_missing (Optional[bool]): If True, no error is raised if
|
|
2095
|
+
status is not found.
|
|
2096
|
+
|
|
2097
|
+
Returns:
|
|
2098
|
+
ProjectStatus: Removed status.
|
|
2099
|
+
"""
|
|
2100
|
+
|
|
2101
|
+
matching_status = self.get(name)
|
|
2102
|
+
if matching_status is None:
|
|
2103
|
+
if ignore_missing:
|
|
2104
|
+
return
|
|
2105
|
+
raise ValueError(
|
|
2106
|
+
"Status '{}' not found in project".format(name))
|
|
2107
|
+
return self.remove(matching_status)
|
|
2108
|
+
|
|
2109
|
+
def remove(self, status, ignore_missing=False):
|
|
2110
|
+
"""Remove status.
|
|
2111
|
+
|
|
2112
|
+
Args:
|
|
2113
|
+
status (ProjectStatus): Status to remove.
|
|
2114
|
+
ignore_missing (Optional[bool]): If True, no error is raised if
|
|
2115
|
+
status is not found.
|
|
2116
|
+
|
|
2117
|
+
Returns:
|
|
2118
|
+
Union[ProjectStatus, None]: Removed status.
|
|
2119
|
+
"""
|
|
2120
|
+
|
|
2121
|
+
index = self.index(status, default=None)
|
|
2122
|
+
if index is None:
|
|
2123
|
+
if ignore_missing:
|
|
2124
|
+
return None
|
|
2125
|
+
raise ValueError("Status '{}' not in project".format(status))
|
|
2126
|
+
|
|
2127
|
+
return self.pop(index)
|
|
2128
|
+
|
|
2129
|
+
def pop(self, index):
|
|
2130
|
+
"""Remove status by index.
|
|
2131
|
+
|
|
2132
|
+
Args:
|
|
2133
|
+
index (int): Status index.
|
|
2134
|
+
|
|
2135
|
+
Returns:
|
|
2136
|
+
ProjectStatus: Removed status.
|
|
2137
|
+
"""
|
|
2138
|
+
|
|
2139
|
+
status = self._statuses.pop(index)
|
|
2140
|
+
status.unset_project_statuses(self)
|
|
2141
|
+
for st in self._statuses[index:]:
|
|
2142
|
+
st.set_index(st.index - 1, from_parent=True)
|
|
2143
|
+
return status
|
|
2144
|
+
|
|
2145
|
+
def insert(self, index, status):
|
|
2146
|
+
"""Insert status at index.
|
|
2147
|
+
|
|
2148
|
+
Args:
|
|
2149
|
+
index (int): Status index.
|
|
2150
|
+
status (Union[ProjectStatus, dict[str, str]]): Status to insert.
|
|
2151
|
+
Can be either status object or status data.
|
|
2152
|
+
|
|
2153
|
+
Returns:
|
|
2154
|
+
ProjectStatus: Inserted status.
|
|
2155
|
+
"""
|
|
2156
|
+
|
|
2157
|
+
if not isinstance(status, ProjectStatus):
|
|
2158
|
+
status = ProjectStatus.from_data(status)
|
|
2159
|
+
|
|
2160
|
+
start_index = index
|
|
2161
|
+
end_index = len(self._statuses) + 1
|
|
2162
|
+
matching_index = self.index(status, default=None)
|
|
2163
|
+
if matching_index is not None:
|
|
2164
|
+
if matching_index == index:
|
|
2165
|
+
status.set_index(index, from_parent=True)
|
|
2166
|
+
return
|
|
2167
|
+
|
|
2168
|
+
self._statuses.pop(matching_index)
|
|
2169
|
+
if matching_index < index:
|
|
2170
|
+
start_index = matching_index
|
|
2171
|
+
end_index = index + 1
|
|
2172
|
+
else:
|
|
2173
|
+
end_index -= 1
|
|
2174
|
+
|
|
2175
|
+
status.set_project_statuses(self)
|
|
2176
|
+
self._statuses.insert(index, status)
|
|
2177
|
+
for idx, st in enumerate(self._statuses[start_index:end_index]):
|
|
2178
|
+
st.set_index(start_index + idx, from_parent=True)
|
|
2179
|
+
return status
|
|
2180
|
+
|
|
2181
|
+
def append(self, status):
|
|
2182
|
+
"""Add new status to the end of the list.
|
|
2183
|
+
|
|
2184
|
+
Args:
|
|
2185
|
+
status (Union[ProjectStatus, dict[str, str]]): Status to insert.
|
|
2186
|
+
Can be either status object or status data.
|
|
2187
|
+
|
|
2188
|
+
Returns:
|
|
2189
|
+
ProjectStatus: Inserted status.
|
|
2190
|
+
"""
|
|
2191
|
+
|
|
2192
|
+
return self.insert(len(self._statuses), status)
|
|
2193
|
+
|
|
2194
|
+
def set_status_index(self, status, index):
|
|
2195
|
+
"""Set status index.
|
|
2196
|
+
|
|
2197
|
+
Args:
|
|
2198
|
+
status (ProjectStatus): Status to set index.
|
|
2199
|
+
index (int): New status index.
|
|
2200
|
+
"""
|
|
2201
|
+
|
|
2202
|
+
return self.insert(index, status)
|
|
2203
|
+
|
|
2204
|
+
|
|
1488
2205
|
class ProjectEntity(BaseEntity):
|
|
1489
2206
|
"""Entity representing project on AYON server.
|
|
1490
2207
|
|
|
@@ -1514,7 +2231,14 @@ class ProjectEntity(BaseEntity):
|
|
|
1514
2231
|
default_task_type_icon = "task_alt"
|
|
1515
2232
|
|
|
1516
2233
|
def __init__(
|
|
1517
|
-
self,
|
|
2234
|
+
self,
|
|
2235
|
+
project_code,
|
|
2236
|
+
library,
|
|
2237
|
+
folder_types,
|
|
2238
|
+
task_types,
|
|
2239
|
+
statuses,
|
|
2240
|
+
*args,
|
|
2241
|
+
**kwargs
|
|
1518
2242
|
):
|
|
1519
2243
|
super(ProjectEntity, self).__init__(*args, **kwargs)
|
|
1520
2244
|
|
|
@@ -1522,11 +2246,13 @@ class ProjectEntity(BaseEntity):
|
|
|
1522
2246
|
self._library_project = library
|
|
1523
2247
|
self._folder_types = folder_types
|
|
1524
2248
|
self._task_types = task_types
|
|
2249
|
+
self._statuses_obj = _ProjectStatuses(statuses)
|
|
1525
2250
|
|
|
1526
2251
|
self._orig_project_code = project_code
|
|
1527
2252
|
self._orig_library_project = library
|
|
1528
2253
|
self._orig_folder_types = copy.deepcopy(folder_types)
|
|
1529
2254
|
self._orig_task_types = copy.deepcopy(task_types)
|
|
2255
|
+
self._orig_statuses = copy.deepcopy(statuses)
|
|
1530
2256
|
|
|
1531
2257
|
def _prepare_entity_id(self, entity_id):
|
|
1532
2258
|
if entity_id != self.project_name:
|
|
@@ -1573,13 +2299,24 @@ class ProjectEntity(BaseEntity):
|
|
|
1573
2299
|
new_task_types.append(task_type)
|
|
1574
2300
|
self._task_types = new_task_types
|
|
1575
2301
|
|
|
2302
|
+
def get_orig_statuses(self):
|
|
2303
|
+
return copy.deepcopy(self._orig_statuses)
|
|
2304
|
+
|
|
2305
|
+
def get_statuses(self):
|
|
2306
|
+
return self._statuses_obj
|
|
2307
|
+
|
|
2308
|
+
def set_statuses(self, statuses):
|
|
2309
|
+
self._statuses_obj.set(statuses)
|
|
2310
|
+
|
|
1576
2311
|
folder_types = property(get_folder_types, set_folder_types)
|
|
1577
2312
|
task_types = property(get_task_types, set_task_types)
|
|
2313
|
+
statuses = property(get_statuses, set_statuses)
|
|
1578
2314
|
|
|
1579
2315
|
def lock(self):
|
|
1580
2316
|
super(ProjectEntity, self).lock()
|
|
1581
2317
|
self._orig_folder_types = copy.deepcopy(self._folder_types)
|
|
1582
2318
|
self._orig_task_types = copy.deepcopy(self._task_types)
|
|
2319
|
+
self._statuses_obj.lock()
|
|
1583
2320
|
|
|
1584
2321
|
@property
|
|
1585
2322
|
def changes(self):
|
|
@@ -1590,6 +2327,9 @@ class ProjectEntity(BaseEntity):
|
|
|
1590
2327
|
if self._orig_task_types != self._task_types:
|
|
1591
2328
|
changes["taskTypes"] = self.get_task_types()
|
|
1592
2329
|
|
|
2330
|
+
if self._statuses_obj.changed:
|
|
2331
|
+
changes["statuses"] = self._statuses_obj.to_data()
|
|
2332
|
+
|
|
1593
2333
|
return changes
|
|
1594
2334
|
|
|
1595
2335
|
@classmethod
|
|
@@ -14,7 +14,16 @@ except ImportError:
|
|
|
14
14
|
HTTPStatus = None
|
|
15
15
|
|
|
16
16
|
import requests
|
|
17
|
-
|
|
17
|
+
try:
|
|
18
|
+
# This should be used if 'requests' have it available
|
|
19
|
+
from requests.exceptions import JSONDecodeError as RequestsJSONDecodeError
|
|
20
|
+
except ImportError:
|
|
21
|
+
# Older versions of 'requests' don't have custom exception for json
|
|
22
|
+
# decode error
|
|
23
|
+
try:
|
|
24
|
+
from simplejson import JSONDecodeError as RequestsJSONDecodeError
|
|
25
|
+
except ImportError:
|
|
26
|
+
from json import JSONDecodeError as RequestsJSONDecodeError
|
|
18
27
|
|
|
19
28
|
from .constants import (
|
|
20
29
|
DEFAULT_PRODUCT_TYPE_FIELDS,
|
|
@@ -900,18 +909,24 @@ class ServerAPI(object):
|
|
|
900
909
|
|
|
901
910
|
self.validate_server_availability()
|
|
902
911
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
if
|
|
912
|
-
|
|
912
|
+
self._token_validation_started = True
|
|
913
|
+
|
|
914
|
+
try:
|
|
915
|
+
response = self.post(
|
|
916
|
+
"auth/login",
|
|
917
|
+
name=username,
|
|
918
|
+
password=password
|
|
919
|
+
)
|
|
920
|
+
if response.status_code != 200:
|
|
921
|
+
_detail = response.data.get("detail")
|
|
922
|
+
details = ""
|
|
923
|
+
if _detail:
|
|
924
|
+
details = " {}".format(_detail)
|
|
925
|
+
|
|
926
|
+
raise AuthenticationError("Login failed {}".format(details))
|
|
913
927
|
|
|
914
|
-
|
|
928
|
+
finally:
|
|
929
|
+
self._token_validation_started = False
|
|
915
930
|
|
|
916
931
|
self._access_token = response["token"]
|
|
917
932
|
|
|
@@ -1228,7 +1243,8 @@ class ServerAPI(object):
|
|
|
1228
1243
|
target_topic,
|
|
1229
1244
|
sender,
|
|
1230
1245
|
description=None,
|
|
1231
|
-
sequential=None
|
|
1246
|
+
sequential=None,
|
|
1247
|
+
events_filter=None,
|
|
1232
1248
|
):
|
|
1233
1249
|
"""Enroll job based on events.
|
|
1234
1250
|
|
|
@@ -1270,6 +1286,8 @@ class ServerAPI(object):
|
|
|
1270
1286
|
in target event.
|
|
1271
1287
|
sequential (Optional[bool]): The source topic must be processed
|
|
1272
1288
|
in sequence.
|
|
1289
|
+
events_filter (Optional[ayon_server.sqlfilter.Filter]): A dict-like
|
|
1290
|
+
with conditions to filter the source event.
|
|
1273
1291
|
|
|
1274
1292
|
Returns:
|
|
1275
1293
|
Union[None, dict[str, Any]]: None if there is no event matching
|
|
@@ -1285,6 +1303,8 @@ class ServerAPI(object):
|
|
|
1285
1303
|
kwargs["sequential"] = sequential
|
|
1286
1304
|
if description is not None:
|
|
1287
1305
|
kwargs["description"] = description
|
|
1306
|
+
if events_filter is not None:
|
|
1307
|
+
kwargs["filter"] = events_filter
|
|
1288
1308
|
response = self.post("enroll", **kwargs)
|
|
1289
1309
|
if response.status_code == 204:
|
|
1290
1310
|
return None
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Package declaring Python API for Ayon server."""
|
|
2
|
-
__version__ = "0.3.
|
|
2
|
+
__version__ = "0.3.4"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ayon-python-api
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: AYON Python API
|
|
5
5
|
Home-page: https://github.com/ynput/ayon-python-api
|
|
6
6
|
Author: ynput.io
|
|
@@ -273,8 +273,7 @@ If pip complain that `ayon-python-api` is already installed just uninstall exist
|
|
|
273
273
|
- Move entity hub to first place
|
|
274
274
|
- Skip those which are invalid for the entity and fake it for base or remove it from base
|
|
275
275
|
- Entity hub should use operations session to do changes
|
|
276
|
-
- Entity hub could also handle '
|
|
277
|
-
- Missing 'statuses' on project
|
|
276
|
+
- Entity hub could also handle 'product', 'version' and 'representation' entities
|
|
278
277
|
- Missing 'status' on folders
|
|
279
278
|
- Missing assignees on tasks
|
|
280
279
|
- Pass docstrings and arguments definitions from `ServerAPI` methods to global functions
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ayon-python-api-0.3.3 → ayon-python-api-0.3.4}/ayon_python_api.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|