PKBA~HP>`2`2yax/YAXReader.pyimport re import inspect from .condition import Condition import warnings __author__ = 'Móréh, Tamás' # Type of compiled regexes RE = type(re.compile("")) def element_to_string(element, encoding="unicode", method="xml", **kwargs): return YAXReader.etree.tostring(element, encoding=encoding, method=method, **kwargs) def element_to_cmplx_dict(element): # {tag: "", attrib: {}, text: "", children: {}, childlist: []} d = dict() d["tag"] = element.tag d["attrib"] = element.attrib d["text"] = element.text chd = {} chl = [] for child in list(element): cd = element_to_cmplx_dict(child) chl.append(cd) chd[cd["tag"]] = cd d["children"] = chd d["childlist"] = chl return d def element_to_json_dict(element, attrib_prefix="-", text_prefix="#"): tag = element.tag text = [element.text.strip() if element.text is not None else "", ] d = dict() for a, v in element.attrib.items(): if d.get(attrib_prefix + a): c = d[attrib_prefix + a] if isinstance(c, list): c.append(v) else: d[attrib_prefix + a] = [c, v] else: d[attrib_prefix + a] = v for child in list(element): text.append(child.tail.strip() if child.tail is not None else "") ch = element_to_json_dict(child) if d.get(child.tag): c = d[child.tag] if isinstance(c, list): c.append(ch[child.tag]) else: d[child.tag] = [c, ch[child.tag]] else: d[child.tag] = ch[child.tag] # clean text t2 = [] for t in text: if t: t2.append(t) text = t2 if len(text) == 1: text = text[0] # add text if exists if len(d) == 0: d = text elif text: d[text_prefix + "text"] = text return {tag: d} class CallbackRunner: ETREE = 1 STRING = 2 DICT = 3 JSON_DICT = 4 ATTRIB_PREFIX = "-" TEXT_PREFIX = "#" @staticmethod def _default(*args): pass @staticmethod def _convert_to_string(e): return YAXReader.etree.tostring(e) @staticmethod def _convert_to_cmplx_dict(e): # {tag: "", attrib: {}, text: "", children: {}, childlist: []} d = dict() d["tag"] = e.tag d["attrib"] = e.attrib d["text"] = e.text chd = {} chl = [] for child in list(e): cd = CallbackRunner._convert_to_cmplx_dict(child) chl.append(cd) chd[cd["tag"]] = cd d["children"] = chd d["childlist"] = chl return d @staticmethod def _convert_to_json_dict(e): tag = e.tag text = [e.text.strip() if e.text is not None else "", ] d = dict() for a, v in e.attrib.items(): if d.get(CallbackRunner.ATTRIB_PREFIX + a): c = d[CallbackRunner.ATTRIB_PREFIX + a] if isinstance(c, list): c.append(v) else: d[CallbackRunner.ATTRIB_PREFIX + a] = [c, v] else: d[CallbackRunner.ATTRIB_PREFIX + a] = v for child in list(e): text.append(child.tail.strip() if child.tail is not None else "") ch = CallbackRunner._convert_to_json_dict(child) if d.get(child.tag): c = d[child.tag] if isinstance(c, list): c.append(ch[child.tag]) else: d[child.tag] = [c, ch[child.tag]] else: d[child.tag] = ch[child.tag] # clean text t2 = [] for t in text: if t: t2.append(t) text = t2 if len(text) == 1: text = text[0] # add text if exists if len(d) == 0: d = text elif text: d[CallbackRunner.TEXT_PREFIX+"text"] = text return {tag: d} @staticmethod def _convert_to_element(e): return e def __init__(self, t: int, attrib_prefix='-', text_prefix='#', condition: Condition=None): self.condition = condition self._callback = CallbackRunner._default self._type = t CallbackRunner.ATTRIB_PREFIX = attrib_prefix CallbackRunner.TEXT_PREFIX = text_prefix if t == CallbackRunner.ETREE: self._convert = CallbackRunner._convert_to_element elif t == CallbackRunner.STRING: self._convert = CallbackRunner._convert_to_string elif t == CallbackRunner.DICT: self._convert = CallbackRunner._convert_to_cmplx_dict elif t == CallbackRunner.JSON_DICT: self._convert = CallbackRunner._convert_to_json_dict else: raise Exception("CallbackRunner type must be one of CallbackRunner.ETREE, " + "CallbackRunner.STRING, CallbackRunner.JSON_DICT and " + "CallbackRunner.DICT!") def inverted(self) -> Condition: warnings.warn("This feature is waiting for a better implementation", FutureWarning) self.condition.inverse() return self # TODO itt kell megvalósítani a visszaírást def calls(self, callback): if not callable(callback): raise Exception("The callback argument must be callable!") ins = inspect.getfullargspec(callback) if len(ins.args) < 2 and ins.varargs is None: raise Exception("The callback funciton must can accept at least 2 arguments!\n" + "First: The element itself, Second: the line number.") self._callback = callback def __call__(self, element, line: int=0): self._callback(self._convert(element), line) class YAXReader: etree = None # todo példány szintre! (egyébként működik) def __init__(self, stream=None, use_lxml=False): self._cnds = [] self.stream = stream if use_lxml: try: import lxml.etree as etree Condition.LXML = True except ImportError: import xml.etree.ElementTree as etree Condition.LXML = False else: import xml.etree.ElementTree as etree Condition.LXML = False YAXReader.etree = etree @staticmethod def lxml_in_use(): return Condition.LXML def start(self, chunk_size=8192): if not self.stream: raise Exception("Input stream is not initialized.") elif self.stream.closed: raise Exception("The input stream is closed.") if Condition.LXML: parser = YAXReader.etree.XMLPullParser(events=('end',)) prev_parent = None prev_element = None keep = False chunk = self.stream.read(chunk_size) while chunk: parser.feed(chunk) for action, element in parser.read_events(): if not keep and prev_parent is not None: prev_parent.remove(prev_element) keep = False for cond, cb_runner in self._cnds: if cond.check(element): cb_runner(element) if not keep and cond.keep(element): keep = True prev_parent = element.getparent() prev_element = element chunk = self.stream.read(chunk_size) else: parser = YAXReader.etree.XMLPullParser(events=('end', 'start')) parents = [] chunk = self.stream.read(chunk_size) while chunk: parser.feed(chunk) for action, element in parser.read_events(): if action == 'start': parents.append(element) else: parents.pop() keep = False # Do not keep anything by default. for cond, cb_runner in self._cnds: # For all conditions. if cond.check(element, parents): cb_runner(element) if not keep and cond.keep(element, parents): keep = True if not keep and len(parents) > 0: parents[-1].remove(element) chunk = self.stream.read(chunk_size) self.stream.close() def find(self, tag=None, attrib: dict=None, text=None, parent=None, children=None, keep_children=None) -> CallbackRunner: tup = (Condition(tag, attrib, text, parent, children, keep_children), CallbackRunner(CallbackRunner.ETREE)) self._cnds.append(tup) tup[1].condition = tup[0] return tup[1] def match(self, cond: Condition) -> CallbackRunner: tup = (cond, CallbackRunner(CallbackRunner.ETREE)) self._cnds.append(tup) tup[1].condition = tup[0] return tup[1] # TODO remove deprecated funcs def find_as_element(self, tag=None, attrib: dict=None, text=None, parent=None, children=None, keep_children=None) -> CallbackRunner: warnings.warn("Deprecated: this method will be removed in version 2.0.\n" "Use the new converter methods.", DeprecationWarning) tup = (Condition(tag, attrib, text, parent, children, keep_children), CallbackRunner(CallbackRunner.ETREE)) self._cnds.append(tup) tup[1].condition = tup[0] return tup[1] def find_as_str(self, tag=None, attrib: dict=None, text=None, parent=None, children=None, keep_children=None) -> CallbackRunner: warnings.warn("Deprecated: this method will be removed in version 2.0.\n" "Use the new converter methods.", DeprecationWarning) tup = (Condition(tag, attrib, text, parent, children, keep_children), CallbackRunner(CallbackRunner.STRING)) self._cnds.append(tup) tup[1].condition = tup[0] return tup[1] def find_as_dict(self, tag=None, attrib: dict=None, text=None, parent=None, children=None, keep_children=None) -> CallbackRunner: warnings.warn("Deprecated: this method will be removed in version 2.0.\n" "Use the new converter methods.", DeprecationWarning) tup = (Condition(tag, attrib, text, parent, children, keep_children), CallbackRunner(CallbackRunner.DICT)) self._cnds.append(tup) tup[1].condition = tup[0] return tup[1] def find_as_json_dict(self, tag=None, attrib: dict=None, text=None, parent=None, children=None, keep_children=None) -> CallbackRunner: warnings.warn("Deprecated: this method will be removed in version 2.0.\n" "Use the new converter methods.", DeprecationWarning) tup = (Condition(tag, attrib, text, parent, children, keep_children), CallbackRunner(CallbackRunner.JSON_DICT)) self._cnds.append(tup) tup[1].condition = tup[0] return tup[1] def match_as_element(self, cond: Condition) -> CallbackRunner: warnings.warn("Deprecated: this method will be removed in version 2.0.\n" "Use the new converter methods.", DeprecationWarning) tup = (cond, CallbackRunner(CallbackRunner.ETREE)) self._cnds.append(tup) tup[1].condition = tup[0] return tup[1] def match_as_str(self, cond: Condition) -> CallbackRunner: warnings.warn("Deprecated: this method will be removed in version 2.0.\n" "Use the new converter methods.", DeprecationWarning) tup = (cond, CallbackRunner(CallbackRunner.STRING)) self._cnds.append(tup) tup[1].condition = tup[0] return tup[1] def match_as_dict(self, cond: Condition) -> CallbackRunner: warnings.warn("Deprecated: this method will be removed in version 2.0.\n" "Use the new converter methods.", DeprecationWarning) tup = (cond, CallbackRunner(CallbackRunner.DICT)) self._cnds.append(tup) tup[1].condition = tup[0] return tup[1] def match_as_json_dict(self, cond: Condition) -> CallbackRunner: warnings.warn("Deprecated: this method will be removed in version 2.0.\n" "Use the new converter methods.", DeprecationWarning) tup = (cond, CallbackRunner(CallbackRunner.JSON_DICT)) self._cnds.append(tup) tup[1].condition = tup[0] return tup[1] PKuHM ppyax/__init__.pyfrom .YAXReader import * from .condition import * __author__ = 'morta' # __all__ = ["condition", "YAXReader"] PKiuHIl,l,yax/condition.pyimport re __author__ = 'Móréh, Tamás' RE = type(re.compile("")) class ConditionException(Exception): pass class CheckListItems: def __init__(self, callables: list): self.callables = callables def __call__(self, t): for tag_cond in self.callables: if tag_cond(t): return True return False class CheckAttrib: def __init__(self, attrib: dict): self.attrib = attrib def __call__(self, attr): for key, check in self.attrib.items(): val = attr.get(key) if not check(val): return False return True class EmptyCondition: def __init__(self, b: bool): self._return_default = b def check(self, *_): return self._return_default class Condition: LXML = False """ Condition class for filtering the XML parse events. If the condition satisfies the callback function will be called. """ @staticmethod def normalize_condition(cnd, allow_parents=True, allow_children=True, none_answer=True, allow_none=True): """ Converts all possible condition input to a valid condition :param cnd: Condition, EmptyCondition or tag-filter type :param allow_parents: condition can have condition for its parent :param allow_children: condition can have condition for its children :param none_answer: the default result of EmptyCondition when cnd is None :param allow_none: :return: a valid Condition object :raises ConditionException: if the cnd cannot be converted to a valid Condition """ def check_child(cc): if not allow_children: if len(cc._children) != 0: raise ConditionException("Checking children of parents not allowed!") def check_parent(cp): if not allow_parents: if not isinstance(cp._parent, EmptyCondition): raise ConditionException("Checking parents of child not allowed!") if isinstance(cnd, Condition): check_child(cnd) check_parent(cnd) return cnd if isinstance(cnd, EmptyCondition): return cnd if isinstance(cnd, (str, RE, list)): return Condition(cnd) if isinstance(cnd, bool): return EmptyCondition(cnd) if cnd is None and allow_none: return EmptyCondition(none_answer) if isinstance(cnd, dict): c = Condition(cnd.get('tag'), cnd.get('attrib'), cnd.get('text'), cnd.get('parent'), cnd.get('children'), cnd.get("keep_children")) check_child(c) check_parent(c) return c if isinstance(cnd, tuple): if len(cnd) == 0: return EmptyCondition(none_answer) c = Condition(*cnd) check_child(c) check_parent(c) return c raise ConditionException("Invalid type as condition! {}".format(type(cnd))) @staticmethod def normalize_children(cnd) -> list: if cnd is None: return [] if isinstance(cnd, list): return [Condition.normalize_condition(c, allow_parents=False, allow_none=False) for c in cnd] else: return [Condition.normalize_condition(cnd, allow_parents=False, allow_none=False), ] @staticmethod def normalize_tag(tag, firstlevel=True): """ Converts all possible tag-condition definition to a callable object. :param tag: Condition for tag name/text content, can be str, compiled regexp or list of them. It can be also True and None. None condition will be always satisfied, True if the tag/text exists. :param firstlevel: A flag for this recursive function. Do not use directly! :return: a callable object which returns True iff the condition satisfied """ if callable(tag): # A (hopefully) bool expression return tag elif isinstance(tag, str): # The lam.expr. will compare return lambda s: s == tag elif isinstance(tag, RE): # The l.exp. checks with fullmatch # return lambda s: s is not None and tag.fullmatch(s) is not None return lambda s: tag.fullmatch(s) is not None elif isinstance(tag, list) and firstlevel: # The l.ex. checks the containing return CheckListItems([Condition.normalize_tag(t, firstlevel=False) for t in tag]) elif tag is True and firstlevel: # True only if exists. Only for text is relevant. return lambda s: not not s elif tag is None and firstlevel: # If none, everything will be accepted. return lambda s: True else: raise ConditionException("Invalid parameter as tag/text name filter! {}". format(type(tag))) @staticmethod def normalize_attrib(attrib): """ Converts the attribute-condition definition to a callable object. :param attrib: attribute-condition dict. Keys must be str, values can be str, compiled regexp or list of them. It can be also True and it's satisfied if the attribute exists. :return: a callable object which returns True iff the condition satisfied """ if not attrib: # Empty dict or None accepts everything. return lambda d: True elif isinstance(attrib, dict): return CheckAttrib({k: Condition.normalize_tag(v) for k, v in attrib.items()}) else: raise ConditionException("Invalid parameter as attribute filter! {}". format(type(attrib))) def __init__(self, tag=None, attrib=None, text=None, parent=None, children=None, keep_children=None): self._inverted = False # self doesn't matches if would be match # The LXML way has different methods: if Condition.LXML: self.check = self._check_lxml self.keep = self._keep_lxml self._check_children = self._check_children_lxml else: self.check = self._check_xml self.keep = self._keep_xml self._check_children = self._check_children_xml # condition attributes (check callables will be created): self._tag = Condition.normalize_tag(tag) self._attrib = Condition.normalize_attrib(attrib) self._text = Condition.normalize_tag(text) self._parent = Condition.normalize_condition(parent, allow_children=False) self._children = Condition.normalize_children(children) self._keep = Condition.normalize_children(keep_children) def inverse(self): """ Negate the current condition :return: The negated condition itself. """ self._inverted = not self._inverted if self._inverted: self.check = self._inverted_check_lxml if Condition.LXML \ else self._inverted_check_xml else: self.check = self._check_lxml if Condition.LXML \ else self._check_xml return self def _check_children_lxml(self, element): children = list(element) # Every child-condition must be matching to a for ch_cond in self._children: # child found = False for child in children: if ch_cond.check(child): found = True break if not found: return False return True def _check_children_xml(self, element): children = list(element) # Every child-condition must be matching to a for ch_cond in self._children: # child found = False for child in children: if ch_cond.check(child, []): # There is no way to check children's parents! found = True break if not found: return False return True def _check_lxml(self, element) -> bool: try: # If any part of condition is false, return with false. if not self._tag(element.tag): # Checking tagname return False if not self._attrib(element.attrib): # Checking attribs return False if element.text is None: # Checking text if not self._text(None): return False else: if not self._text(element.text.strip()): return False if not self._parent.check(element.getparent()): # Checking parent return False if not self._check_children(element): # Checking children return False except: return False return True def _check_xml(self, element, parents) -> bool: try: # If any part of condition is false, return with false. if not self._tag(element.tag): # Checking tagname return False if not self._attrib(element.attrib): # Checking attribs return False if element.text is None: # Checking text if not self._text(None): return False else: if not self._text(element.text.strip()): return False if not self._parent.check(parents[-1] if len(parents) > 0 else None, parents[:-1]): return False if not self._check_children(element): # Checking children return False except: return False return True def _inverted_check_lxml(self, element) -> bool: return not self._check_lxml(element) def _inverted_check_xml(self, element, parents) -> bool: return not self._check_xml(element, parents) def _keep_lxml(self, element) -> bool: parent = element.getparent() if parent is None: return True if not self._tag(parent.tag): # Element's parent must be match return False for ch_cond in self._children: # Keep if it is in the children conditions if ch_cond.check(element): return True for keep_cond in self._keep: # Keep if it is in the keep conditions if keep_cond.check(element): return True return False def _keep_xml(self, element, parents) -> bool: if not len(parents) > 0: return True if not self._tag(parents[-1].tag): # Element's parent must be match return False for ch_cond in self._children: # Keep if it is in the children conditions if ch_cond.check(element, parents): return True for keep_cond in self._keep: # Keep if it is in the keep conditions if keep_cond.check(element, parents): return True return False PKl~He>>#YAX-1.2.0.dist-info/DESCRIPTION.rst=== YAX === **Yet Another XML parser** is a powerful event-based memory-efficient Python module. It analyses the XML stream node by node and builds subtrees only if it is really needed. In case of record-oriented XML input (when some subtree structure is repeated many times), it processes the file in a sequential manner similar to that of *SAX*. However, conditions can be defined which trigger a *DOM* like processing where subtrees are created. This method is also efficient on very large data (which do not fit into the memory) both in terms of storage and computational time complexity. Dependencies ~~~~~~~~~~~~ YAX uses Python 3.x and above. It doesn't depend on any third party module. However, if you have *lxml* installed you can use it as back-end. (See the documentation about performance.) Installation ~~~~~~~~~~~~ * Download as a zip archive, uncompress and use it. * ``pip3 install yax`` * (Soon...) Downolad the deb package and install it. Usage ~~~~~ A simple example which prints all the elements with tagname "a" and containing "href" attribute: .. code:: python import yax yr = yax.YAXReader(open("file.xml")) yr.find("a", {"href": True}).calls( lambda e, i: print(yax.element_to_string(e, with_tail=False)) # with_tail only whith lxml! ) yr.start() A bit more complex example which filters a gpx record. It prints the elevation values of the trackpoints in a specified area: .. code:: python import yax yr = yax.YAXReader(open("route.gpx")) yr.find("trkpt", {"lat": lambda v: 47 < float(v) < 48, "lon": lambda v: 16 < float(v) < 17}, keep_children="ele" ).calls(lambda e, i: print(e.find("ele").text)) yr.start() This example shows that it would be erease all unneccessary children from the subtree to save memory but in this case we need the child called "ele". For more example or the complete reference see the documentation. See also ~~~~~~~~ * `External documentation `_ * `Issue tracker `_ (feel free to use it!) PKl~HtK`$!YAX-1.2.0.dist-info/metadata.json{"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Information Technology", "Intended Audience :: Science/Research", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Text Processing :: Markup :: XML"], "extensions": {"python.details": {"contacts": [{"email": "morta@digitus.itk.ppke.hu", "name": "M\u00f3r\u00e9h Tam\u00e1s, MTA-PPKE-NLPG", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/morta-code/YAX"}}}, "generator": "bdist_wheel (0.26.0)", "keywords": ["xml", "lxml", "parser", "event-based", "record-oriented"], "license": "LGPLv3", "metadata_version": "2.0", "name": "YAX", "summary": "Yet Another XML parser with the power of event-based memory-safe mechanism.", "version": "1.2.0"}PKl~HUy!YAX-1.2.0.dist-info/top_level.txtyax PKl~H}\\YAX-1.2.0.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py3-none-any PKl~HBK YAX-1.2.0.dist-info/METADATAMetadata-Version: 2.0 Name: YAX Version: 1.2.0 Summary: Yet Another XML parser with the power of event-based memory-safe mechanism. Home-page: https://github.com/morta-code/YAX Author: Móréh Tamás, MTA-PPKE-NLPG Author-email: morta@digitus.itk.ppke.hu License: LGPLv3 Keywords: xml lxml parser event-based record-oriented Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: Science/Research Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Text Processing :: Markup :: XML === YAX === **Yet Another XML parser** is a powerful event-based memory-efficient Python module. It analyses the XML stream node by node and builds subtrees only if it is really needed. In case of record-oriented XML input (when some subtree structure is repeated many times), it processes the file in a sequential manner similar to that of *SAX*. However, conditions can be defined which trigger a *DOM* like processing where subtrees are created. This method is also efficient on very large data (which do not fit into the memory) both in terms of storage and computational time complexity. Dependencies ~~~~~~~~~~~~ YAX uses Python 3.x and above. It doesn't depend on any third party module. However, if you have *lxml* installed you can use it as back-end. (See the documentation about performance.) Installation ~~~~~~~~~~~~ * Download as a zip archive, uncompress and use it. * ``pip3 install yax`` * (Soon...) Downolad the deb package and install it. Usage ~~~~~ A simple example which prints all the elements with tagname "a" and containing "href" attribute: .. code:: python import yax yr = yax.YAXReader(open("file.xml")) yr.find("a", {"href": True}).calls( lambda e, i: print(yax.element_to_string(e, with_tail=False)) # with_tail only whith lxml! ) yr.start() A bit more complex example which filters a gpx record. It prints the elevation values of the trackpoints in a specified area: .. code:: python import yax yr = yax.YAXReader(open("route.gpx")) yr.find("trkpt", {"lat": lambda v: 47 < float(v) < 48, "lon": lambda v: 16 < float(v) < 17}, keep_children="ele" ).calls(lambda e, i: print(e.find("ele").text)) yr.start() This example shows that it would be erease all unneccessary children from the subtree to save memory but in this case we need the child called "ele". For more example or the complete reference see the documentation. See also ~~~~~~~~ * `External documentation `_ * `Issue tracker `_ (feel free to use it!) PKl~HYAX-1.2.0.dist-info/RECORDYAX-1.2.0.dist-info/DESCRIPTION.rst,sha256=CDlZr6jc0yO9P_hdOyGmzsjl4o0vmDi1Q1m1qC_vQhA,2110 YAX-1.2.0.dist-info/METADATA,sha256=AoD-lIkOPMDm-X7b2uHRQko-sOtJCV7aaWetZq3wYsE,2955 YAX-1.2.0.dist-info/RECORD,, YAX-1.2.0.dist-info/WHEEL,sha256=zX7PHtH_7K-lEzyK75et0UBa3Bj8egCBMXe1M4gc6SU,92 YAX-1.2.0.dist-info/metadata.json,sha256=3wsr_tnSiu4S2t1A0oU2QBGyawT22zZvl14PZoi_LAg,998 YAX-1.2.0.dist-info/top_level.txt,sha256=SAUl68BquFxpX44OThrqsSb-6o3XyQMlHCokemmYFdE,4 yax/YAXReader.py,sha256=R7mwavkrmMKIVneFwulr_s0Mf8o_B6uw2LogN4p9gys,12896 yax/__init__.py,sha256=IgMDgCcMVtnhZ9E7wjJGU1i-rrpq0fEiqTJ8Qn1cgVw,112 yax/condition.py,sha256=ggPabHb8I2nbeNWzsa8DqLFLcmg1GMlOuLgL6M2Bf_E,11372 PKBA~HP>`2`2yax/YAXReader.pyPKuHM pp2yax/__init__.pyPKiuHIl,l,+3yax/condition.pyPKl~He>>#_YAX-1.2.0.dist-info/DESCRIPTION.rstPKl~HtK`$!DhYAX-1.2.0.dist-info/metadata.jsonPKl~HUy!ilYAX-1.2.0.dist-info/top_level.txtPKl~H}\\lYAX-1.2.0.dist-info/WHEELPKl~HBK ?mYAX-1.2.0.dist-info/METADATAPKl~HyYAX-1.2.0.dist-info/RECORDPK {