mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 08:24:53 +01:00
ftrack-api integration initialization
This commit is contained in:
parent
ff8fe86771
commit
0bdc5d6e71
7 changed files with 1 additions and 796 deletions
|
|
@ -2,7 +2,7 @@ import os
|
|||
|
||||
from avalon import api as avalon
|
||||
from pyblish import api as pyblish
|
||||
# from avalon.tools import workfiles
|
||||
from ..vendor import ftrack_api
|
||||
|
||||
PARENT_DIR = os.path.dirname(__file__)
|
||||
PACKAGE_DIR = os.path.dirname(PARENT_DIR)
|
||||
|
|
|
|||
293
pype/vendor/clique/__init__.py
vendored
293
pype/vendor/clique/__init__.py
vendored
|
|
@ -1,293 +0,0 @@
|
|||
# :coding: utf-8
|
||||
# :copyright: Copyright (c) 2013 Martin Pengelly-Phillips
|
||||
# :license: See LICENSE.txt.
|
||||
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
from ._version import __version__
|
||||
from .collection import Collection
|
||||
from .error import CollectionError
|
||||
|
||||
|
||||
#: Pattern for matching an index with optional padding.
|
||||
DIGITS_PATTERN = '(?P<index>(?P<padding>0*)\d+)'
|
||||
|
||||
#: Common patterns that can be passed to :py:func:`~clique.assemble`.
|
||||
PATTERNS = {
|
||||
'frames': '\.{0}\.\D+\d?$'.format(DIGITS_PATTERN),
|
||||
'versions': 'v{0}'.format(DIGITS_PATTERN)
|
||||
}
|
||||
|
||||
|
||||
def assemble(
|
||||
iterable, patterns=None, minimum_items=2, case_sensitive=True,
|
||||
assume_padded_when_ambiguous=False
|
||||
):
|
||||
'''Assemble items in *iterable* into discreet collections.
|
||||
|
||||
*patterns* may be specified as a list of regular expressions to limit
|
||||
the returned collection possibilities. Use this when interested in
|
||||
collections that only match specific patterns. Each pattern must contain
|
||||
the expression from :py:data:`DIGITS_PATTERN` exactly once.
|
||||
|
||||
A selection of common expressions are available in :py:data:`PATTERNS`.
|
||||
|
||||
.. note::
|
||||
|
||||
If a pattern is supplied as a string it will be automatically compiled
|
||||
to a :py:class:`re.RegexObject` instance for convenience.
|
||||
|
||||
When *patterns* is not specified, collections are formed by examining all
|
||||
possible groupings of the items in *iterable* based around common numerical
|
||||
components.
|
||||
|
||||
*minimum_items* dictates the minimum number of items a collection must have
|
||||
in order to be included in the result. The default is 2, filtering out
|
||||
single item collections.
|
||||
|
||||
If *case_sensitive* is False, then items will be treated as part of the same
|
||||
collection when they only differ in casing. To avoid ambiguity, the
|
||||
resulting collection will always be lowercase. For example, "item.0001.dpx"
|
||||
and "Item.0002.dpx" would be part of the same collection, "item.%04d.dpx".
|
||||
|
||||
.. note::
|
||||
|
||||
Any compiled *patterns* will also respect the set case sensitivity.
|
||||
|
||||
For certain collections it may be ambiguous whether they are padded or not.
|
||||
For example, 1000-1010 can be considered either an unpadded collection or a
|
||||
four padded collection. By default, Clique is conservative and assumes that
|
||||
the collection is unpadded. To change this behaviour, set
|
||||
*assume_padded_when_ambiguous* to True and any ambiguous collection will have
|
||||
a relevant padding set.
|
||||
|
||||
.. note::
|
||||
|
||||
*assume_padded_when_ambiguous* has no effect on collections that are
|
||||
unambiguous. For example, 1-100 will always be considered unpadded
|
||||
regardless of the *assume_padded_when_ambiguous* setting.
|
||||
|
||||
Return tuple of two lists (collections, remainder) where 'collections' is a
|
||||
list of assembled :py:class:`~clique.collection.Collection` instances and
|
||||
'remainder' is a list of items that did not belong to any collection.
|
||||
|
||||
'''
|
||||
collection_map = defaultdict(set)
|
||||
collections = []
|
||||
remainder = []
|
||||
|
||||
# Compile patterns.
|
||||
flags = 0
|
||||
if not case_sensitive:
|
||||
flags |= re.IGNORECASE
|
||||
|
||||
compiled_patterns = []
|
||||
|
||||
if patterns is not None:
|
||||
if not patterns:
|
||||
return collections, list(iterable)
|
||||
|
||||
for pattern in patterns:
|
||||
if isinstance(pattern, basestring):
|
||||
compiled_patterns.append(re.compile(pattern, flags=flags))
|
||||
else:
|
||||
compiled_patterns.append(pattern)
|
||||
|
||||
else:
|
||||
compiled_patterns.append(re.compile(DIGITS_PATTERN, flags=flags))
|
||||
|
||||
# Process iterable.
|
||||
for item in iterable:
|
||||
matched = False
|
||||
|
||||
for pattern in compiled_patterns:
|
||||
for match in pattern.finditer(item):
|
||||
index = match.group('index')
|
||||
|
||||
head = item[:match.start('index')]
|
||||
tail = item[match.end('index'):]
|
||||
|
||||
if not case_sensitive:
|
||||
head = head.lower()
|
||||
tail = tail.lower()
|
||||
|
||||
padding = match.group('padding')
|
||||
if padding:
|
||||
padding = len(index)
|
||||
else:
|
||||
padding = 0
|
||||
|
||||
key = (head, tail, padding)
|
||||
collection_map[key].add(int(index))
|
||||
matched = True
|
||||
|
||||
if not matched:
|
||||
remainder.append(item)
|
||||
|
||||
# Form collections.
|
||||
merge_candidates = []
|
||||
for (head, tail, padding), indexes in collection_map.items():
|
||||
collection = Collection(head, tail, padding, indexes)
|
||||
collections.append(collection)
|
||||
|
||||
if collection.padding == 0:
|
||||
merge_candidates.append(collection)
|
||||
|
||||
# Merge together collections that align on padding boundaries. For example,
|
||||
# 0998-0999 and 1000-1001 can be merged into 0998-1001. Note that only
|
||||
# indexes within the padding width limit are merged. If a collection is
|
||||
# entirely merged into another then it will not be included as a separate
|
||||
# collection in the results.
|
||||
fully_merged = []
|
||||
for collection in collections:
|
||||
if collection.padding == 0:
|
||||
continue
|
||||
|
||||
for candidate in merge_candidates:
|
||||
if (
|
||||
candidate.head == collection.head and
|
||||
candidate.tail == collection.tail
|
||||
):
|
||||
merged_index_count = 0
|
||||
for index in candidate.indexes:
|
||||
if len(str(abs(index))) == collection.padding:
|
||||
collection.indexes.add(index)
|
||||
merged_index_count += 1
|
||||
|
||||
if merged_index_count == len(candidate.indexes):
|
||||
fully_merged.append(candidate)
|
||||
|
||||
# Filter out fully merged collections.
|
||||
collections = [collection for collection in collections
|
||||
if collection not in fully_merged]
|
||||
|
||||
# Filter out collections that do not have at least as many indexes as
|
||||
# minimum_items. In addition, add any members of a filtered collection,
|
||||
# which are not members of an unfiltered collection, to the remainder.
|
||||
filtered = []
|
||||
remainder_candidates = []
|
||||
for collection in collections:
|
||||
if len(collection.indexes) >= minimum_items:
|
||||
filtered.append(collection)
|
||||
else:
|
||||
for member in collection:
|
||||
remainder_candidates.append(member)
|
||||
|
||||
for candidate in remainder_candidates:
|
||||
# Check if candidate has already been added to remainder to avoid
|
||||
# duplicate entries.
|
||||
if candidate in remainder:
|
||||
continue
|
||||
|
||||
has_membership = False
|
||||
|
||||
for collection in filtered:
|
||||
if candidate in collection:
|
||||
has_membership = True
|
||||
break
|
||||
|
||||
if not has_membership:
|
||||
remainder.append(candidate)
|
||||
|
||||
# Set padding for all ambiguous collections according to the
|
||||
# assume_padded_when_ambiguous setting.
|
||||
if assume_padded_when_ambiguous:
|
||||
for collection in filtered:
|
||||
if (
|
||||
not collection.padding and collection.indexes
|
||||
):
|
||||
indexes = list(collection.indexes)
|
||||
first_index_width = len(str(indexes[0]))
|
||||
last_index_width = len(str(indexes[-1]))
|
||||
if first_index_width == last_index_width:
|
||||
collection.padding = first_index_width
|
||||
|
||||
return filtered, remainder
|
||||
|
||||
|
||||
def parse(value, pattern='{head}{padding}{tail} [{ranges}]'):
|
||||
'''Parse *value* into a :py:class:`~clique.collection.Collection`.
|
||||
|
||||
Use *pattern* to extract information from *value*. It may make use of the
|
||||
following keys:
|
||||
|
||||
* *head* - Common leading part of the collection.
|
||||
* *tail* - Common trailing part of the collection.
|
||||
* *padding* - Padding value in ``%0d`` format.
|
||||
* *range* - Total range in the form ``start-end``.
|
||||
* *ranges* - Comma separated ranges of indexes.
|
||||
* *holes* - Comma separated ranges of missing indexes.
|
||||
|
||||
.. note::
|
||||
|
||||
*holes* only makes sense if *range* or *ranges* is also present.
|
||||
|
||||
'''
|
||||
# Construct regular expression for given pattern.
|
||||
expressions = {
|
||||
'head': '(?P<head>.*)',
|
||||
'tail': '(?P<tail>.*)',
|
||||
'padding': '%(?P<padding>\d*)d',
|
||||
'range': '(?P<range>\d+-\d+)?',
|
||||
'ranges': '(?P<ranges>[\d ,\-]+)?',
|
||||
'holes': '(?P<holes>[\d ,\-]+)'
|
||||
}
|
||||
|
||||
pattern_regex = re.escape(pattern)
|
||||
for key, expression in expressions.items():
|
||||
pattern_regex = pattern_regex.replace(
|
||||
'\{{{0}\}}'.format(key),
|
||||
expression
|
||||
)
|
||||
pattern_regex = '^{0}$'.format(pattern_regex)
|
||||
|
||||
# Match pattern against value and use results to construct collection.
|
||||
match = re.search(pattern_regex, value)
|
||||
if match is None:
|
||||
raise ValueError('Value did not match pattern.')
|
||||
|
||||
groups = match.groupdict()
|
||||
if 'padding' in groups and groups['padding']:
|
||||
groups['padding'] = int(groups['padding'])
|
||||
else:
|
||||
groups['padding'] = 0
|
||||
|
||||
# Create collection and then add indexes.
|
||||
collection = Collection(
|
||||
groups.get('head', ''),
|
||||
groups.get('tail', ''),
|
||||
groups['padding']
|
||||
)
|
||||
|
||||
if groups.get('range', None) is not None:
|
||||
start, end = map(int, groups['range'].split('-'))
|
||||
collection.indexes.update(range(start, end + 1))
|
||||
|
||||
if groups.get('ranges', None) is not None:
|
||||
parts = [part.strip() for part in groups['ranges'].split(',')]
|
||||
for part in parts:
|
||||
index_range = list(map(int, part.split('-', 2)))
|
||||
|
||||
if len(index_range) > 1:
|
||||
# Index range.
|
||||
for index in range(index_range[0], index_range[1] + 1):
|
||||
collection.indexes.add(index)
|
||||
else:
|
||||
# Single index.
|
||||
collection.indexes.add(index_range[0])
|
||||
|
||||
if 'holes' in groups:
|
||||
parts = [part.strip() for part in groups['holes'].split(',')]
|
||||
for part in parts:
|
||||
index_range = map(int, part.split('-', 2))
|
||||
|
||||
if len(index_range) > 1:
|
||||
# Index range.
|
||||
for index in range(index_range[0], index_range[1] + 1):
|
||||
collection.indexes.remove(index)
|
||||
else:
|
||||
# Single index.
|
||||
collection.indexes.remove(index_range[0])
|
||||
|
||||
return collection
|
||||
2
pype/vendor/clique/_version.py
vendored
2
pype/vendor/clique/_version.py
vendored
|
|
@ -1,2 +0,0 @@
|
|||
__version__ = '1.5.0'
|
||||
|
||||
385
pype/vendor/clique/collection.py
vendored
385
pype/vendor/clique/collection.py
vendored
|
|
@ -1,385 +0,0 @@
|
|||
# :coding: utf-8
|
||||
# :copyright: Copyright (c) 2013 Martin Pengelly-Phillips
|
||||
# :license: See LICENSE.txt.
|
||||
|
||||
import re
|
||||
|
||||
import descriptor
|
||||
import error
|
||||
import sorted_set
|
||||
|
||||
|
||||
class Collection(object):
|
||||
'''Represent group of items that differ only by numerical component.'''
|
||||
|
||||
indexes = descriptor.Unsettable('indexes')
|
||||
|
||||
def __init__(self, head, tail, padding, indexes=None):
|
||||
'''Initialise collection.
|
||||
|
||||
*head* is the leading common part whilst *tail* is the trailing
|
||||
common part.
|
||||
|
||||
*padding* specifies the "width" of the numerical component. An index
|
||||
will be padded with zeros to fill this width. A *padding* of zero
|
||||
implies no padding and width may be any size so long as no leading
|
||||
zeros are present.
|
||||
|
||||
*indexes* can specify a set of numerical indexes to initially populate
|
||||
the collection with.
|
||||
|
||||
.. note::
|
||||
|
||||
After instantiation, the ``indexes`` attribute cannot be set to a
|
||||
new value using assignment::
|
||||
|
||||
>>> collection.indexes = [1, 2, 3]
|
||||
AttributeError: Cannot set attribute defined as unsettable.
|
||||
|
||||
Instead, manipulate it directly::
|
||||
|
||||
>>> collection.indexes.clear()
|
||||
>>> collection.indexes.update([1, 2, 3])
|
||||
|
||||
'''
|
||||
super(Collection, self).__init__()
|
||||
self.__dict__['indexes'] = sorted_set.SortedSet()
|
||||
self._head = head
|
||||
self._tail = tail
|
||||
self.padding = padding
|
||||
self._update_expression()
|
||||
|
||||
if indexes is not None:
|
||||
self.indexes.update(indexes)
|
||||
|
||||
@property
|
||||
def head(self):
|
||||
'''Return common leading part.'''
|
||||
return self._head
|
||||
|
||||
@head.setter
|
||||
def head(self, value):
|
||||
'''Set common leading part to *value*.'''
|
||||
self._head = value
|
||||
self._update_expression()
|
||||
|
||||
@property
|
||||
def tail(self):
|
||||
'''Return common trailing part.'''
|
||||
return self._tail
|
||||
|
||||
@tail.setter
|
||||
def tail(self, value):
|
||||
'''Set common trailing part to *value*.'''
|
||||
self._tail = value
|
||||
self._update_expression()
|
||||
|
||||
def _update_expression(self):
|
||||
'''Update internal expression.'''
|
||||
self._expression = re.compile(
|
||||
'^{0}(?P<index>(?P<padding>0*)\d+?){1}$'
|
||||
.format(re.escape(self.head), re.escape(self.tail))
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
'''Return string represenation.'''
|
||||
return self.format()
|
||||
|
||||
def __repr__(self):
|
||||
'''Return representation.'''
|
||||
return '<{0} "{1}">'.format(self.__class__.__name__, self)
|
||||
|
||||
def __iter__(self):
|
||||
'''Return iterator over items in collection.'''
|
||||
for index in self.indexes:
|
||||
formatted_index = '{0:0{1}d}'.format(index, self.padding)
|
||||
item = '{0}{1}{2}'.format(self.head, formatted_index, self.tail)
|
||||
yield item
|
||||
|
||||
def __contains__(self, item):
|
||||
'''Return whether *item* is present in collection.'''
|
||||
match = self.match(item)
|
||||
if not match:
|
||||
return False
|
||||
|
||||
if not int(match.group('index')) in self.indexes:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __eq__(self, other):
|
||||
'''Return whether *other* collection is equal.'''
|
||||
if not isinstance(other, Collection):
|
||||
return NotImplemented
|
||||
|
||||
return all([
|
||||
other.head == self.head,
|
||||
other.tail == self.tail,
|
||||
other.padding == self.padding,
|
||||
other.indexes == self.indexes
|
||||
])
|
||||
|
||||
def __ne__(self, other):
|
||||
'''Return whether *other* collection is not equal.'''
|
||||
result = self.__eq__(other)
|
||||
if result is NotImplemented:
|
||||
return result
|
||||
|
||||
return not result
|
||||
|
||||
def __gt__(self, other):
|
||||
'''Return whether *other* collection is greater than.'''
|
||||
if not isinstance(other, Collection):
|
||||
return NotImplemented
|
||||
|
||||
a = (self.head, self.tail, self.padding, len(self.indexes))
|
||||
b = (other.head, other.tail, other.padding, len(other.indexes))
|
||||
|
||||
return a > b
|
||||
|
||||
def __lt__(self, other):
|
||||
'''Return whether *other* collection is less than.'''
|
||||
result = self.__gt__(other)
|
||||
if result is NotImplemented:
|
||||
return result
|
||||
|
||||
return not result
|
||||
|
||||
def __ge__(self, other):
|
||||
'''Return whether *other* collection is greater than or equal.'''
|
||||
result = self.__eq__(other)
|
||||
if result is NotImplemented:
|
||||
return result
|
||||
|
||||
if result is False:
|
||||
result = self.__gt__(other)
|
||||
|
||||
return result
|
||||
|
||||
def __le__(self, other):
|
||||
'''Return whether *other* collection is less than or equal.'''
|
||||
result = self.__eq__(other)
|
||||
if result is NotImplemented:
|
||||
return result
|
||||
|
||||
if result is False:
|
||||
result = self.__lt__(other)
|
||||
|
||||
return result
|
||||
|
||||
def match(self, item):
|
||||
'''Return whether *item* matches this collection expression.
|
||||
|
||||
If a match is successful return data about the match otherwise return
|
||||
None.
|
||||
|
||||
'''
|
||||
match = self._expression.match(item)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
index = match.group('index')
|
||||
padded = False
|
||||
if match.group('padding'):
|
||||
padded = True
|
||||
|
||||
if self.padding == 0:
|
||||
if padded:
|
||||
return None
|
||||
|
||||
elif len(index) != self.padding:
|
||||
return None
|
||||
|
||||
return match
|
||||
|
||||
def add(self, item):
|
||||
'''Add *item* to collection.
|
||||
|
||||
raise :py:class:`~error.CollectionError` if *item* cannot be
|
||||
added to the collection.
|
||||
|
||||
'''
|
||||
match = self.match(item)
|
||||
if match is None:
|
||||
raise error.CollectionError(
|
||||
'Item does not match collection expression.'
|
||||
)
|
||||
|
||||
self.indexes.add(int(match.group('index')))
|
||||
|
||||
def remove(self, item):
|
||||
'''Remove *item* from collection.
|
||||
|
||||
raise :py:class:`~error.CollectionError` if *item* cannot be
|
||||
removed from the collection.
|
||||
|
||||
'''
|
||||
match = self.match(item)
|
||||
if match is None:
|
||||
raise error.CollectionError(
|
||||
'Item not present in collection.'
|
||||
)
|
||||
|
||||
index = int(match.group('index'))
|
||||
try:
|
||||
self.indexes.remove(index)
|
||||
except KeyError:
|
||||
raise error.CollectionError(
|
||||
'Item not present in collection.'
|
||||
)
|
||||
|
||||
def format(self, pattern='{head}{padding}{tail} [{ranges}]'):
|
||||
'''Return string representation as specified by *pattern*.
|
||||
|
||||
Pattern can be any format accepted by Python's standard format function
|
||||
and will receive the following keyword arguments as context:
|
||||
|
||||
* *head* - Common leading part of the collection.
|
||||
* *tail* - Common trailing part of the collection.
|
||||
* *padding* - Padding value in ``%0d`` format.
|
||||
* *range* - Total range in the form ``start-end``
|
||||
* *ranges* - Comma separated ranges of indexes.
|
||||
* *holes* - Comma separated ranges of missing indexes.
|
||||
|
||||
'''
|
||||
data = {}
|
||||
data['head'] = self.head
|
||||
data['tail'] = self.tail
|
||||
|
||||
if self.padding:
|
||||
data['padding'] = '%0{0}d'.format(self.padding)
|
||||
else:
|
||||
data['padding'] = '%d'
|
||||
|
||||
if '{holes}' in pattern:
|
||||
data['holes'] = self.holes().format('{ranges}')
|
||||
|
||||
if '{range}' in pattern or '{ranges}' in pattern:
|
||||
indexes = list(self.indexes)
|
||||
indexes_count = len(indexes)
|
||||
|
||||
if indexes_count == 0:
|
||||
data['range'] = ''
|
||||
|
||||
elif indexes_count == 1:
|
||||
data['range'] = '{0}'.format(indexes[0])
|
||||
|
||||
else:
|
||||
data['range'] = '{0}-{1}'.format(
|
||||
indexes[0], indexes[-1]
|
||||
)
|
||||
|
||||
if '{ranges}' in pattern:
|
||||
separated = self.separate()
|
||||
if len(separated) > 1:
|
||||
ranges = [collection.format('{range}')
|
||||
for collection in separated]
|
||||
|
||||
else:
|
||||
ranges = [data['range']]
|
||||
|
||||
data['ranges'] = ', '.join(ranges)
|
||||
|
||||
return pattern.format(**data)
|
||||
|
||||
def is_contiguous(self):
|
||||
'''Return whether entire collection is contiguous.'''
|
||||
previous = None
|
||||
for index in self.indexes:
|
||||
if previous is None:
|
||||
previous = index
|
||||
continue
|
||||
|
||||
if index != (previous + 1):
|
||||
return False
|
||||
|
||||
previous = index
|
||||
|
||||
return True
|
||||
|
||||
def holes(self):
|
||||
'''Return holes in collection.
|
||||
|
||||
Return :py:class:`~collection.Collection` of missing indexes.
|
||||
|
||||
'''
|
||||
missing = set([])
|
||||
previous = None
|
||||
for index in self.indexes:
|
||||
if previous is None:
|
||||
previous = index
|
||||
continue
|
||||
|
||||
if index != (previous + 1):
|
||||
missing.update(range(previous + 1, index))
|
||||
|
||||
previous = index
|
||||
|
||||
return Collection(self.head, self.tail, self.padding, indexes=missing)
|
||||
|
||||
def is_compatible(self, collection):
|
||||
'''Return whether *collection* is compatible with this collection.
|
||||
|
||||
To be compatible *collection* must have the same head, tail and padding
|
||||
properties as this collection.
|
||||
|
||||
'''
|
||||
return all([
|
||||
isinstance(collection, Collection),
|
||||
collection.head == self.head,
|
||||
collection.tail == self.tail,
|
||||
collection.padding == self.padding
|
||||
])
|
||||
|
||||
def merge(self, collection):
|
||||
'''Merge *collection* into this collection.
|
||||
|
||||
If the *collection* is compatible with this collection then update
|
||||
indexes with all indexes in *collection*.
|
||||
|
||||
raise :py:class:`~error.CollectionError` if *collection* is not
|
||||
compatible with this collection.
|
||||
|
||||
'''
|
||||
if not self.is_compatible(collection):
|
||||
raise error.CollectionError('Collection is not compatible '
|
||||
'with this collection.')
|
||||
|
||||
self.indexes.update(collection.indexes)
|
||||
|
||||
def separate(self):
|
||||
'''Return contiguous parts of collection as separate collections.
|
||||
|
||||
Return as list of :py:class:`~collection.Collection` instances.
|
||||
|
||||
'''
|
||||
collections = []
|
||||
start = None
|
||||
end = None
|
||||
|
||||
for index in self.indexes:
|
||||
if start is None:
|
||||
start = index
|
||||
end = start
|
||||
continue
|
||||
|
||||
if index != (end + 1):
|
||||
collections.append(
|
||||
Collection(self.head, self.tail, self.padding,
|
||||
indexes=set(range(start, end + 1)))
|
||||
)
|
||||
start = index
|
||||
|
||||
end = index
|
||||
|
||||
if start is None:
|
||||
collections.append(
|
||||
Collection(self.head, self.tail, self.padding)
|
||||
)
|
||||
else:
|
||||
collections.append(
|
||||
Collection(self.head, self.tail, self.padding,
|
||||
indexes=range(start, end + 1))
|
||||
)
|
||||
|
||||
return collections
|
||||
43
pype/vendor/clique/descriptor.py
vendored
43
pype/vendor/clique/descriptor.py
vendored
|
|
@ -1,43 +0,0 @@
|
|||
# :coding: utf-8
|
||||
# :copyright: Copyright (c) 2013 Martin Pengelly-Phillips
|
||||
# :license: See LICENSE.txt.
|
||||
|
||||
|
||||
class Unsettable(object):
|
||||
'''Prevent standard setting of property.
|
||||
|
||||
Example::
|
||||
|
||||
>>> class Foo(object):
|
||||
...
|
||||
... x = Unsettable('x')
|
||||
...
|
||||
... def __init__(self):
|
||||
... self.__dict__['x'] = True
|
||||
...
|
||||
>>> foo = Foo()
|
||||
>>> print foo.x
|
||||
True
|
||||
>>> foo.x = False
|
||||
AttributeError: Cannot set attribute defined as unsettable.
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, label):
|
||||
'''Initialise descriptor with property *label*.
|
||||
|
||||
*label* should match the name of the property being described::
|
||||
|
||||
x = Unsettable('x')
|
||||
|
||||
'''
|
||||
self.label = label
|
||||
super(Unsettable, self).__init__()
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
'''Return value of property for *instance*.'''
|
||||
return instance.__dict__.get(self.label)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
'''Set *value* for *instance* property.'''
|
||||
raise AttributeError('Cannot set attribute defined as unsettable.')
|
||||
10
pype/vendor/clique/error.py
vendored
10
pype/vendor/clique/error.py
vendored
|
|
@ -1,10 +0,0 @@
|
|||
# :coding: utf-8
|
||||
# :copyright: Copyright (c) 2013 Martin Pengelly-Phillips
|
||||
# :license: See LICENSE.txt.
|
||||
|
||||
'''Custom error classes.'''
|
||||
|
||||
|
||||
class CollectionError(Exception):
|
||||
'''Raise when a collection error occurs.'''
|
||||
|
||||
62
pype/vendor/clique/sorted_set.py
vendored
62
pype/vendor/clique/sorted_set.py
vendored
|
|
@ -1,62 +0,0 @@
|
|||
# :coding: utf-8
|
||||
# :copyright: Copyright (c) 2013 Martin Pengelly-Phillips
|
||||
# :license: See LICENSE.txt.
|
||||
|
||||
import collections
|
||||
import bisect
|
||||
|
||||
|
||||
class SortedSet(collections.MutableSet):
|
||||
'''Maintain sorted collection of unique items.'''
|
||||
|
||||
def __init__(self, iterable=None):
|
||||
'''Initialise with items from *iterable*.'''
|
||||
super(SortedSet, self).__init__()
|
||||
self._members = []
|
||||
if iterable:
|
||||
self.update(iterable)
|
||||
|
||||
def __str__(self):
|
||||
'''Return string representation.'''
|
||||
return str(self._members)
|
||||
|
||||
def __repr__(self):
|
||||
'''Return representation.'''
|
||||
return '<{0} "{1}">'.format(self.__class__.__name__, self)
|
||||
|
||||
def __contains__(self, item):
|
||||
'''Return whether *item* is present.'''
|
||||
return self._index(item) >= 0
|
||||
|
||||
def __len__(self):
|
||||
'''Return number of items.'''
|
||||
return len(self._members)
|
||||
|
||||
def __iter__(self):
|
||||
'''Return iterator over items.'''
|
||||
return iter(self._members)
|
||||
|
||||
def add(self, item):
|
||||
'''Add *item*.'''
|
||||
if not item in self:
|
||||
index = bisect.bisect_right(self._members, item)
|
||||
self._members.insert(index, item)
|
||||
|
||||
def discard(self, item):
|
||||
'''Remove *item*.'''
|
||||
index = self._index(item)
|
||||
if index >= 0:
|
||||
del self._members[index]
|
||||
|
||||
def update(self, iterable):
|
||||
'''Update items with those from *iterable*.'''
|
||||
for item in iterable:
|
||||
self.add(item)
|
||||
|
||||
def _index(self, item):
|
||||
'''Return index of *item* in member list or -1 if not present.'''
|
||||
index = bisect.bisect_left(self._members, item)
|
||||
if index != len(self) and self._members[index] == item:
|
||||
return index
|
||||
|
||||
return -1
|
||||
Loading…
Add table
Add a link
Reference in a new issue