PK&NԾpy_linq/__init__.py""" Linq for Python """ __version__ = '0.7.0' try: from py_linq import Enumerable # noqa except ImportError: from py_linq.py_linq import Enumerable3 as Enumerable # noqa PK|Lϝpy_linq/decorators.pyimport warnings import functools def deprecated(reason): """ This is a decorator which can be used to mark function as deprecated. It will result in a warning being emitted when the function is used. :param reason: a reason as a string :return: decorated function """ def deprecated_decorator(func): @functools.wraps(func) def new_func(*args, **kwargs): warnings.simplefilter('always', DeprecationWarning) warnings.warn( u"{0} is deprecated. {1}".format(func.__name__, reason), category=DeprecationWarning, stacklevel=2 ) return func(*args, **kwargs) return new_func return deprecated_decorator PK|L3py_linq/exceptions.pyclass NoElementsError(Exception): pass class NullArgumentError(Exception): pass class NoMatchingElement(Exception): pass class MoreThanOneMatchingElement(Exception): pass PK6&N뗨py_linq/py_linq.pyimport itertools from .decorators import deprecated from .exceptions import NoElementsError, NoMatchingElement, \ NullArgumentError, MoreThanOneMatchingElement class Enumerable(object): def __init__(self, data=None): """ Constructor ** Note: no type checking of the data elements are performed during instantiation. ** :param data: iterable object :return: None """ if data is None: data = [] if not hasattr(data, "__iter__"): raise TypeError( u"Enumerable must be instantiated with an iterable object" ) self._data = data @property def data(self): """ The iterable of the Enumerable instance :return: iterable """ return self._data def __iter__(self): cache = [] for element in self._data: cache.append(element) yield element self._data = cache def __repr__(self): return self._data.__repr__() def to_list(self): """ Converts the iterable into a list :return: list object """ return list(element for element in self) def count(self): """ Returns the number of elements in iterable :return: integer object """ return sum(1 for element in self) def select(self, func=lambda x: x): """ Transforms data into different form :param func: lambda expression on how to perform transformation :return: new Enumerable object containing transformed data """ return Enumerable(itertools.imap(func, self)) def sum(self, func=lambda x: x): """ Returns the sum of af data elements :param func: lambda expression to transform data :return: sum of selected elements """ return sum(self.select(func)) def min(self, func=lambda x: x): """ Returns the min value of data elements :param func: lambda expression to transform data :return: minimum value """ if self.count() == 0: raise NoElementsError(u"Iterable contains no elements") return min(self.select(func)) def max(self, func=lambda x: x): """ Returns the max value of data elements :param func: lambda expression to transform data :return: maximum value """ if self.count() == 0: raise NoElementsError(u"Iterable contains no elements") return max(self.select(func)) def avg(self, func=lambda x: x): """ Returns the average value of data elements :param func: lambda expression to transform data :return: average value as float object """ count = self.count() if count == 0: raise NoElementsError(u"Iterable contains no elements") return float(self.sum(func)) / float(count) def median(self, func=lambda x: x): """ Return the median value of data elements :param func: lambda expression to project and sort data :return: median value """ if self.count() == 0: raise NoElementsError(u"Iterable contains no elements") result = self.order_by(func).select(func).to_list() length = len(result) i = int(length / 2) return result[i] if length % 2 == 1 else (float(result[i - 1]) + float(result[i])) / float(2) def element_at(self, n): """ Returns element at given index. * Raises NoElementsError if no element found at specified position :param n: index as int object :return: Element at given index """ result = list(itertools.islice(self.to_list(), max(0, n), n + 1, 1)) if len(result) == 0: raise NoElementsError(u"No element found at index {0}".format(n)) return result[0] @deprecated(u"Please use element_at instead") def elementAt(self, n): return self.element_at(n) def element_at_or_default(self, n): """ Returns element at given index or None if no element found * Raises IndexError if n is greater than the number of elements in enumerable :param n: index as int object :return: Element at given index """ try: return self.element_at(n) except NoElementsError: return None @deprecated(u"Please use element_at_or_default instead") def elementAtOrDefault(self, n): return self.element_at_or_default(n) def first(self): """ Returns the first element :return: data element as object or NoElementsError if transformed data contains no elements """ return self.element_at(0) def first_or_default(self): """ Return the first element :return: data element as object or None if transformed data contains no elements """ return self.element_at_or_default(0) def last(self): """ Return the last element :return: data element as object or NoElementsError if transformed data contains no elements """ return self.element_at(self.count() - 1) def last_or_default(self): """ Return the last element :return: data element as object or None if transformed data contains no elements """ return self.element_at_or_default(self.count() - 1) def order_by(self, key): """ Returns new Enumerable sorted in ascending order by given key :param key: key to sort by as lambda expression :return: new Enumerable object """ if key is None: raise NullArgumentError(u"No key for sorting given") kf = [OrderingDirection(key, reverse=False)] return SortedEnumerable(key_funcs=kf, data=self._data) def order_by_descending(self, key): """ Returns new Enumerable sorted in descending order by given key :param key: key to sort by as lambda expression :return: new Enumerable object """ if key is None: raise NullArgumentError(u"No key for sorting given") kf = [OrderingDirection(key, reverse=True)] return SortedEnumerable(key_funcs=kf, data=self._data) def skip(self, n): """ Returns new Enumerable where n elements have been skipped :param n: Number of elements to skip as int :return: new Enumerable object """ return Enumerable(itertools.islice(self, n, None, 1)) def take(self, n): """ Return new Enumerable where first n elements are taken :param n: Number of elements to take :return: new Enumerable object """ return Enumerable(itertools.islice(self, 0, n, 1)) def where(self, predicate): """ Returns new Enumerable where elements matching predicate are selected :param predicate: predicate as a lambda expression :return: new Enumerable object """ if predicate is None: raise NullArgumentError("No predicate given for where clause") return Enumerable(itertools.ifilter(predicate, self)) def single(self, predicate): """ Returns single element that matches given predicate. Raises: * NoMatchingElement error if no matching elements are found * MoreThanOneMatchingElement error if more than one matching element is found :param predicate: predicate as a lambda expression :return: Matching element as object """ result = self.where(predicate).to_list() count = len(result) if count == 0: raise NoMatchingElement("No matching element found") if count > 1: raise MoreThanOneMatchingElement( "More than one matching element found. Use where instead" ) return result[0] def single_or_default(self, predicate): """ Return single element that matches given predicate. If no matching element is found, returns None Raises: * MoreThanOneMatchingElement error if more than one matching element is found :param predicate: predicate as a lambda expression :return: Matching element as object or None if no matches are found """ try: return self.single(predicate) except NoMatchingElement: return None def select_many(self, func=lambda x: x): """ Flattens an iterable of iterables returning a new Enumerable :param func: selector as lambda expression :return: new Enumerable object """ return Enumerable(itertools.chain.from_iterable(self.select(func))) def add(self, element): """ Adds an element to the enumerable. :param element: An element :return: new Enumerable object """ if element is None: return self return self.concat(Enumerable([element])) def concat(self, enumerable): """ Adds enumerable to an enumerable ** NOTE ** This operation can be expensive depending on the size of the enumerable to be concatenated. This is because the concatenation algorithm performs type checking to ensure that the same object types are being added. If the self enumerable has n elements and the enumerable to be added has m elements then the type checking takes O(mn) time. :param enumerable: An iterable object :return: new Enumerable object """ if not isinstance(enumerable, Enumerable): raise TypeError( u"enumerable argument must be an instance of Enumerable" ) for element in enumerable.data: element_type = type(element) if self.any(lambda x: type(x) != element_type): raise TypeError( u"type mismatch between concatenated Enumerable objects" ) return Enumerable(itertools.chain(self._data, enumerable.data)) def group_by(self, key_names=[], key=lambda x: x, result_func=lambda x: x): """ Groups an enumerable on given key selector. Index of key name corresponds to index of key lambda function. Usage: Enumerable([1,2,3]).group_by(key_names=['id'], key=lambda x: x) _ .to_list() --> Enumerable object [ Grouping object { key.id: 1, _data: [1] }, Grouping object { key.id: 2, _data: [2] }, Grouping object { key.id: 3, _data: [3] } ] Thus the key names for each grouping object can be referenced through the key property. Using the above example: Enumerable([1,2,3]).group_by(key_names=['id'], key=lambda x: x) _ .select(lambda g: { 'key': g.key.id, 'count': g.count() } :param key_names: list of key names :param key: key selector as lambda expression :param result_func: transformation function as lambda expression :return: Enumerable of grouping objects """ result = [] ordered = sorted(self, key=key) grouped = itertools.groupby(ordered, key) for k, g in grouped: can_enumerate = isinstance(k, list) or isinstance(k, tuple) \ and len(k) > 0 key_prop = {} for i, prop in enumerate(key_names): key_prop.setdefault(prop, k[i] if can_enumerate else k) key_object = Key(key_prop) result.append(Grouping(key_object, list(g))) return Enumerable(result).select(result_func) def distinct(self, key=lambda x: x): """ Returns enumerable containing elements that are distinct based on given key selector :param key: key selector as lambda expression :return: new Enumerable object """ return Enumerable(self.group_by(key=key).select(lambda g: g.first()).to_list()) def join( self, inner_enumerable, outer_key=lambda x: x, inner_key=lambda x: x, result_func=lambda x: x ): """ Return enumerable of inner equi-join between two enumerables :param inner_enumerable: inner enumerable to join to self :param outer_key: key selector of outer enumerable as lambda expression :param inner_key: key selector of inner enumerable as lambda expression :param result_func: lambda expression to transform result of join :return: new Enumerable object """ if not isinstance(inner_enumerable, Enumerable): raise TypeError( u"inner_enumerable parameter must be an instance of Enumerable" ) return Enumerable( itertools.product( itertools.ifilter( lambda x: outer_key(x) in itertools.imap( inner_key, inner_enumerable ), self ), itertools.ifilter( lambda y: inner_key(y) in itertools.imap( outer_key, self), inner_enumerable ) ) )\ .where(lambda x: outer_key(x[0]) == inner_key(x[1])) \ .select( result_func) def default_if_empty(self, value=None): """ Returns an enumerable containing a single None element if enumerable is empty, otherwise the enumerable itself :return: an Enumerable object """ if self.count() == 0: return Enumerable([value]) return self def group_join( self, inner_enumerable, outer_key=lambda x: x, inner_key=lambda x: x, result_func=lambda x: x ): """ Return enumerable of group join between two enumerables :param inner_enumerable: inner enumerable to join to self :param outer_key: key selector of outer enumerable as lambda expression :param inner_key: key selector of inner enumerable as lambda expression :param result_func: lambda expression to transform the result of group join :return: new Enumerable object """ if not isinstance(inner_enumerable, Enumerable): raise TypeError( u"inner enumerable parameter must be an instance of Enumerable" ) return Enumerable( itertools.product( self, inner_enumerable.default_if_empty() ) ).group_by( key_names=['id'], key=lambda x: outer_key(x[0]), result_func=lambda g: ( g.first()[0], g.where( lambda x: inner_key(x[1]) == g.key.id).select( lambda x: x[1] ) ) ).select(result_func) def any(self, predicate): """ Returns true if any elements that satisfy predicate are found :param predicate: condition to satisfy as lambda expression :return: boolean True or False """ if predicate is None: raise NullArgumentError( u"predicate lambda expression is necessary") return self.where(predicate).count() > 0 def intersect(self, enumerable, key=lambda x: x): """ Returns enumerable that is the intersection between given enumerable and self :param enumerable: enumerable object :param key: key selector as lambda expression :return: new Enumerable object """ if not isinstance(enumerable, Enumerable): raise TypeError( u"enumerable parameter must be an instance of Enumerable") return self.join(enumerable, key, key).select(lambda x: x[0]) def union(self, enumerable, key=lambda x: x): """ Returns enumerable that is a union of elements between self and given enumerable :param enumerable: enumerable to union self to :param key: key selector used to determine uniqueness :return: new Enumerable object """ if not isinstance(enumerable, Enumerable): raise TypeError( u"enumerable parameter must be an instance of Enumerable") return self.concat(enumerable).distinct(key) def except_(self, enumerable, key=lambda x: x): """ Returns enumerable that subtracts given enumerable elements from self :param enumerable: enumerable object :param key: key selector as lambda expression :return: new Enumerable object """ if not isinstance(enumerable, Enumerable): raise TypeError( u"enumerable parameter must be an instance of Enumerable") membership = [ 0 if key(element) in enumerable.intersect(self, key).select(key) else 1 for element in self ] return Enumerable(itertools.compress(self, membership)) def contains(self, element, key=lambda x: x): """ Returns True if element is found in enumerable, otherwise False :param element: the element being tested for membership in enumerable :param key: key selector to use for membership comparison :return: boolean True or False """ return self.select(key).any(lambda x: x == key(element)) class Enumerable3(object): """ Could probably optimize code by inheriting from Enumerable and overwriting only the methods that need refactoring to support python 3. Admittedly code duplication here. Thought this would be OK design for separation of concerns since Python 3 is a distinct codebase from Python 2. """ def __init__(self, data=[]): """ Constructor ** Note: no type checking of the data elements are performed during instantiation. ** :param data: iterable object :return: None """ if not hasattr(data, "__iter__"): raise TypeError( u"Enumerable must be instantiated with an iterable object") self._data = data @property def data(self): """ The iterable of the Enumerable instance :return: iterable """ return self._data def __iter__(self): cache = [] for element in self._data: cache.append(element) yield element self._data = cache def __repr__(self): return self._data.__repr__() def to_list(self): """ Converts the iterable into a list :return: list object """ return list(element for element in self) def count(self): """ Returns the number of elements in iterable :return: integer object """ return sum(1 for element in self) def select(self, func=lambda x: x): """ Transforms data into different form :param func: lambda expression on how to perform transformation :return: new Enumerable object containing transformed data """ return Enumerable3(map(func, self)) def sum(self, func=lambda x: x): """ Returns the sum of af data elements :param func: lambda expression to transform data :return: sum of selected elements """ return sum(self.select(func)) def min(self, func=lambda x: x): """ Returns the min value of data elements :param func: lambda expression to transform data :return: minimum value """ if self.count() == 0: raise NoElementsError(u"Iterable contains no elements") return min(self.select(func)) def max(self, func=lambda x: x): """ Returns the max value of data elements :param func: lambda expression to transform data :return: maximum value """ if self.count() == 0: raise NoElementsError(u"Iterable contains no elements") return max(self.select(func)) def avg(self, func=lambda x: x): """ Returns the average value of data elements :param func: lambda expression to transform data :return: average value as float object """ count = self.count() if count == 0: raise NoElementsError(u"Iterable contains no elements") return float(self.sum(func)) / float(count) def median(self, func=lambda x: x): """ Return the median value of data elements :param func: lambda expression to project and sort data :return: median value """ if self.count() == 0: raise NoElementsError(u"Iterable contains no elements") result = self.order_by(func).select(func).to_list() length = len(result) i = int(length / 2) return result[i] if length % 2 == 1 else (float(result[i - 1]) + float(result[i])) / float(2) def element_at(self, n): """ Returns element at given index. * Raises NoElementsError if no element found at specified position :param n: index as int object :return: Element at given index """ result = list(itertools.islice(self.to_list(), max(0, n), n + 1, 1)) if len(result) == 0: raise NoElementsError(u"No element found at index {0}".format(n)) return result[0] @deprecated(u"Please use element_at instead") def elementAt(self, n): return self.element_at(n) def element_at_or_default(self, n): """ Returns element at given index or None if no element found * Raises IndexError if n is greater than the number of elements in enumerable :param n: index as int object :return: Element at given index """ try: return self.element_at(n) except NoElementsError: return None @deprecated(u"Please use element_at_or_default instead") def elementAtOrDefault(self, n): return self.element_at_or_default(n) def first(self): """ Returns the first element :return: data element as object or NoElementsError if transformed data contains no elements """ return self.element_at(0) def first_or_default(self): """ Return the first element :return: data element as object or None if transformed data contains no elements """ return self.element_at_or_default(0) def last(self): """ Return the last element :return: data element as object or NoElementsError if transformed data contains no elements """ return Enumerable3(reversed(self.to_list())).first() def last_or_default(self): """ Return the last element :return: data element as object or None if transformed data contains no elements """ return Enumerable3(reversed(self.to_list())).first_or_default() def order_by(self, key): """ Returns new Enumerable sorted in ascending order by given key :param key: key to sort by as lambda expression :return: new Enumerable object """ if key is None: raise NullArgumentError(u"No key for sorting given") kf = [OrderingDirection(key, reverse=False)] return SortedEnumerable3(kf, self._data) def order_by_descending(self, key): """ Returns new Enumerable sorted in descending order by given key :param key: key to sort by as lambda expression :return: new Enumerable object """ if key is None: raise NullArgumentError(u"No key for sorting given") kf = [OrderingDirection(key, reverse=True)] return SortedEnumerable3(kf, self._data) def skip(self, n): """ Returns new Enumerable where n elements have been skipped :param n: Number of elements to skip as int :return: new Enumerable object """ return Enumerable3(itertools.islice(self, n, None, 1)) def take(self, n): """ Return new Enumerable where first n elements are taken :param n: Number of elements to take :return: new Enumerable object """ return Enumerable3(itertools.islice(self, 0, n, 1)) def where(self, predicate): """ Returns new Enumerable where elements matching predicate are selected :param predicate: predicate as a lambda expression :return: new Enumerable object """ if predicate is None: raise NullArgumentError(u"No predicate given for where clause") return Enumerable3(filter(predicate, self)) def single(self, predicate): """ Returns single element that matches given predicate. Raises: * NoMatchingElement error if no matching elements are found * MoreThanOneMatchingElement error if more than one matching element is found :param predicate: predicate as a lambda expression :return: Matching element as object """ result = self.where(predicate).to_list() count = len(result) if count == 0: raise NoMatchingElement(u"No matching element found") if count > 1: raise MoreThanOneMatchingElement( u"More than one matching element found. Use where instead") return result[0] def single_or_default(self, predicate): """ Return single element that matches given predicate. If no matching element is found, returns None Raises: * MoreThanOneMatchingElement error if more than one matching element is found :param predicate: predicate as a lambda expression :return: Matching element as object or None if no matches are found """ try: return self.single(predicate) except NoMatchingElement: return None def select_many(self, func=lambda x: x): """ Flattens an iterable of iterables returning a new Enumerable :param func: selector as lambda expression :return: new Enumerable object """ return Enumerable3(itertools.chain.from_iterable(self.select(func))) def add(self, element): """ Adds an element to the enumerable. :param element: An element :return: new Enumerable object """ if element is None: return self return self.concat(Enumerable3([element])) def concat(self, enumerable): """ Adds enumerable to an enumerable ** NOTE ** This operation can be expensive depending on the size of the enumerable to be concatenated. This is because the concatenation algorithm performs type checking to ensure that the same object types are being added. If the self enumerable has n elements and the enumerable to be added has m elements then the type checking takes O(mn) time. :param enumerable: An iterable object :return: new Enumerable object """ if not isinstance(enumerable, Enumerable3): raise TypeError( u"enumerable argument must be an instance of Enumerable") for element in enumerable.data: element_type = type(element) if self.any(lambda x: type(x) != element_type): raise TypeError( u"type mismatch between concatenated enumerables") return Enumerable3(itertools.chain(self._data, enumerable.data)) def group_by(self, key_names=[], key=lambda x: x, result_func=lambda x: x): """ Groups an enumerable on given key selector. Index of key name corresponds to index of key lambda function. Usage: Enumerable([1,2,3]).group_by(key_names=['id'], key=lambda x: x) _ .to_list() --> Enumerable object [ Grouping object { key.id: 1, _data: [1] }, Grouping object { key.id: 2, _data: [2] }, Grouping object { key.id: 3, _data: [3] } ] Thus the key names for each grouping object can be referenced through the key property. Using the above example: Enumerable([1,2,3]).group_by(key_names=['id'], key=lambda x: x) _ .select(lambda g: { 'key': g.key.id, 'count': g.count() } :param key_names: list of key names :param key: key selector as lambda expression :param result_func: lambda function to transform group_join into desired structure :return: Enumerable of grouping objects """ result = [] ordered = sorted(self, key=key) grouped = itertools.groupby(ordered, key) for k, g in grouped: can_enumerate = isinstance(k, list) or isinstance(k, tuple) \ and len(k) > 0 key_prop = {} for i, prop in enumerate(key_names): key_prop.setdefault(prop, k[i] if can_enumerate else k) key_object = Key(key_prop) result.append(Grouping3(key_object, list(g))) return Enumerable3(result).select(result_func) def distinct(self, key=lambda x: x): """ Returns enumerable containing elements that are distinct based on given key selector :param key: key selector as lambda expression :return: new Enumerable object """ return Enumerable3(self.group_by(key=key).select(lambda g: g.first()).to_list()) def join( self, inner_enumerable, outer_key=lambda x: x, inner_key=lambda x: x, result_func=lambda x: x ): """ Return enumerable of inner equi-join between two enumerables :param inner_enumerable: inner enumerable to join to self :param outer_key: key selector of outer enumerable as lambda expression :param inner_key: key selector of inner enumerable as lambda expression :param result_func: lambda expression to transform result of join :return: new Enumerable object """ if not isinstance(inner_enumerable, Enumerable3): raise TypeError( u"inner_enumerable parameter must be an instance of Enumerable" ) return Enumerable3( itertools.product( filter( lambda x: outer_key(x) in map(inner_key, inner_enumerable), self ), filter( lambda y: inner_key(y) in map(outer_key, self), inner_enumerable ) ) )\ .where(lambda x: outer_key(x[0]) == inner_key(x[1]))\ .select(result_func) def default_if_empty(self, value=None): """ Returns an enumerable containing a single None element if enumerable is empty, otherwise the enumerable itself :return: an Enumerable object """ if self.count() == 0: return Enumerable3([value]) return self def group_join( self, inner_enumerable, outer_key=lambda x: x, inner_key=lambda x: x, result_func=lambda x: x ): """ Return enumerable of group join between two enumerables :param inner_enumerable: inner enumerable to join to self :param outer_key: key selector of outer enumerable as lambda expression :param inner_key: key selector of inner enumerable as lambda expression :param result_func: lambda expression to transform the result of group join :return: new Enumerable object """ if not isinstance(inner_enumerable, Enumerable3): raise TypeError( u"inner enumerable parameter must be an instance of Enumerable" ) return Enumerable3( itertools.product( self, inner_enumerable.default_if_empty() ) ).group_by( key_names=['id'], key=lambda x: outer_key(x[0]), result_func=lambda g: ( g.first()[0], g.where(lambda x: inner_key(x[1]) == g.key.id).select( lambda x: x[1] ) ) ).select(result_func) def any(self, predicate): """ Returns true if any elements that satisfy predicate are found :param predicate: condition to satisfy as lambda expression :return: boolean True or False """ if predicate is None: raise NullArgumentError( u"predicate lambda expression is necessary") return self.where(predicate).count() > 0 def intersect(self, enumerable, key=lambda x: x): """ Returns enumerable that is the intersection between given enumerable and self :param enumerable: enumerable object :param key: key selector as lambda expression :return: new Enumerable object """ if not isinstance(enumerable, Enumerable3): raise TypeError( u"enumerable parameter must be an instance of Enumerable") return self.join(enumerable, key, key).select(lambda x: x[0]) def union(self, enumerable, key=lambda x: x): """ Returns enumerable that is a union of elements between self and given enumerable :param enumerable: enumerable to union self to :param key: key selector used to determine uniqueness :return: new Enumerable object """ if not isinstance(enumerable, Enumerable3): raise TypeError( u"enumerable parameter must be an instance of Enumerable") if self.count() == 0: return enumerable if enumerable.count() == 0: return self return self.concat(enumerable).distinct(key) def except_(self, enumerable, key=lambda x: x): """ Returns enumerable that subtracts given enumerable elements from self :param enumerable: enumerable object :param key: key selector as lambda expression :return: new Enumerable object """ if not isinstance(enumerable, Enumerable3): raise TypeError( u"enumerable parameter must be an instance of Enumerable") membership = ( 0 if key(element) in enumerable.intersect(self, key).select(key) else 1 for element in self ) return Enumerable3( itertools.compress( self, membership ) ) def contains(self, element, key=lambda x: x): """ Returns True if element is found in enumerable, otherwise False :param element: the element being tested for membership in enumerable :param key: key selector to use for membership comparison :return: boolean True or False """ return self.select(key).any(lambda x: x == key(element)) class Key(object): def __init__(self, key, **kwargs): """ Constructor for Key class. Autogenerates key properties in object given dict or kwargs :param key: dict of name-values :param kwargs: optional keyword arguments :return: void """ key = key if key is not None else kwargs self.__dict__.update(key) def __repr__(self): return self.__dict__.__repr__() class Grouping(Enumerable): def __init__(self, key, data): """ Constructor of Grouping class used for group by operations of Enumerable class :param key: Key instance :param data: iterable object :return: void """ if not isinstance(key, Key): raise Exception("key argument should be a Key instance") self.key = key super(Grouping, self).__init__(data) def __repr__(self): return { 'key': self.key.__repr__(), 'enumerable': self._data.__repr__() }.__repr__() class Grouping3(Enumerable3): def __init__(self, key, data): """ Constructor of Grouping class used for group by operations of Enumerable class :param key: Key instance :param data: iterable object :return: void """ if not isinstance(key, Key): raise Exception("key argument should be a Key instance") self.key = key super(Grouping3, self).__init__(data) def __repr__(self): return { 'key': self.key.__repr__(), 'enumerable': self._data.__repr__() }.__repr__() class OrderingDirection(object): def __init__(self, key, reverse): """ A container to hold the lambda key and sorting direction :param key: lambda function :param reverse: boolean. True for reverse sort """ self.key = key self.descending = reverse class SortedEnumerable(Enumerable): def __init__(self, key_funcs, data): """ Constructor :param key_funcs: list of OrderingDirection instances in order of primary key --> less important keys :param data: data as iterable """ if key_funcs is None: raise NullArgumentError(u"key_funcs argument cannot be None") if not isinstance(key_funcs, list): raise TypeError(u"key_funcs should be a list instance") self._key_funcs = [ f for f in key_funcs if isinstance(f, OrderingDirection) ] super(SortedEnumerable, self).__init__(data) def __iter__(self): for o in reversed(self._key_funcs): self._data = sorted(self._data, key=o.key, reverse=o.descending) cache = [] for d in self._data: cache.append(d) yield d self._data = cache def then_by(self, func): """ Subsequent sorting function in ascending order :param func: lambda expression for secondary sort key :return: SortedEnumerable instance """ if func is None: raise NullArgumentError(u"then by requires a lambda function arg") self._key_funcs.append(OrderingDirection(key=func, reverse=False)) return SortedEnumerable(self._key_funcs, self._data) def then_by_descending(self, func): """ Subsequent sorting function in descending order :param func: lambda function for secondary sort key :return: SortedEnumerable instance """ if func is None: raise NullArgumentError( u"then_by_descending requires a lambda function arg") self._key_funcs.append(OrderingDirection(key=func, reverse=True)) return SortedEnumerable(self._key_funcs, self._data) class SortedEnumerable3(Enumerable3): def __init__(self, key_funcs, data): """ Constructor :param key_funcs: list of OrderingDirection instances in order of primary key --> less important keys :param data: data as iterable """ if key_funcs is None: raise NullArgumentError(u"key_funcs argument cannot be None") if not isinstance(key_funcs, list): raise TypeError(u"key_funcs should be a list instance") self._key_funcs = [ f for f in key_funcs if isinstance(f, OrderingDirection) ] super(SortedEnumerable3, self).__init__(data) def __iter__(self): for o in reversed(self._key_funcs): self._data = sorted(self._data, key=o.key, reverse=o.descending) cache = [] for d in self._data: cache.append(d) yield d self._data = cache def then_by(self, func): """ Subsequent sorting function in ascending order :param func: lambda expression for secondary sort key :return: SortedEnumerable instance """ if func is None: raise NullArgumentError( u"then by requires a lambda function arg") self._key_funcs.append(OrderingDirection(key=func, reverse=False)) return SortedEnumerable3(self._key_funcs, self._data) def then_by_descending(self, func): """ Subsequent sorting function in descending order :param func: lambda function for secondary sort key :return: SortedEnumerable instance """ if func is None: raise NullArgumentError( u"then_by_descending requires a lambda function arg") self._key_funcs.append(OrderingDirection(key=func, reverse=True)) return SortedEnumerable3(self._key_funcs, self._data) PK|L<>KKpy_linq-0.7.0.dist-info/LICENSEThe MIT License (MIT) Copyright (c) 2014 viralogic Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!Hd BUcpy_linq-0.7.0.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,rzd&Y)r$[)T&UD"PK!HS_ py_linq-0.7.0.dist-info/METADATAW[SF~ׯ8f"ƥq )}dt,mYiݕ}JE8xa a_4i^ߜ%{m6O>\RMabQ&f,"ؘL[8@&WLȈp1T\SM2_;Üԩ>|Py;t1a\Or&|TysF3Cg25HG"B9O3Sdc32Kn 3~Lj 1arLɵRjIE ,rIJ:nձ[ L頋!rMƘi6T]/5 %#Œ {SlV{z;Jvv8Ypgĝx` rjLa6b^s%\vOo bqܓ*zG<=~%i&w԰oQm)\U+AIB{\fR׍h[ ۵=8{4{\;{`q./Ɵ21 )]sA&\0ͤX kx ΃dD$`b\h阘 K+H!0SF/ VMt\"X+SΜ|s5fVR!U S, 9*뇅#!mx#Y lS%|sA@JTEI`A`MZ/ʄLƀJ$!1I\,,E)u҅zOly,KRܯ5I]]~Hu;j+d}_LlڀvG)fZ M Q$-y-4T'p(;離2kXO rP}B8:WjQ3 Fx'-:<67vպL G͠~@l k[Xk'Ɨap>rGvҚ6 5/\$>:G1qd1,Re=X,qI{,\X7×֮)'=Ku@  R4L4B;]i \kXa= 7啜?}^&ey )#@Avq5PK&NԾpy_linq/__init__.pyPK|Lϝpy_linq/decorators.pyPK|L3py_linq/exceptions.pyPK6&N뗨py_linq/py_linq.pyPK|L<>KKNpy_linq-0.7.0.dist-info/LICENSEPK!Hd BUcֲpy_linq-0.7.0.dist-info/WHEELPK!HS_ fpy_linq-0.7.0.dist-info/METADATAPK!H旴Wppy_linq-0.7.0.dist-info/RECORDPK92