[
  {
    "path": ".github/workflows/workflow.yml",
    "content": "name: test\n\non: [push]\n\nenv:\n  MODE: Test\njobs:\n  tests:\n    name: \"Python ${{ matrix.python-version }} on ${{ matrix.os }}\"\n    runs-on: \"${{ matrix.os }}\"\n\n    strategy:\n      fail-fast: false\n      matrix:\n        os:\n          - ubuntu-latest\n          - macos-latest\n          - windows-latest\n        python-version:\n          - \"3.8\"\n          - \"3.9\"\n          - \"3.10\"\n          - \"3.11\"\n          - \"3.12\"\n          - \"3.13\"\n    steps:\n      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it\n      - name: Checkout code\n        uses: actions/checkout@v2\n      - name: Setup Python ${{ matrix.python-version }}\n        uses: actions/setup-python@v2\n        with:\n          python-version: \"${{ matrix.python-version }}\"\n      - name: Install requirements\n        run: |\n          pip install pytest pytest-cov\n      - name: Run tests\n        run: |\n          pytest --cov=leopards tests/  --cov-report term-missing\n"
  },
  {
    "path": ".gitignore",
    "content": "env/\n.idea/\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Mohamed El-Kalioby\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Leopards\n\n[![PyPI version](https://badge.fury.io/py/leopards.svg)](https://badge.fury.io/py/leopards)\n[![Python Versions](https://img.shields.io/pypi/pyversions/leopards.svg)](https://img.shields.io/pypi/pyversions/leopards.svg)\n![Coverage](https://img.shields.io/badge/coverage-100%25-success)\n![build status](https://github.com/mkalioby/leopards/actions/workflows/workflow.yml/badge.svg)\n\nLeopards is a way to query list of dictionaries or objects as if you are filtering in  DBMS. \nYou can get dicts/objects that are matched by OR, AND or NOT or all of them.\nAs you can see in the comparison they are much faster than Pandas.\n\n\n## Installation\n\n```shell\npip install leopards\n```\n\n## Usage\n\n```python\nfrom leopards import Q\nl = [{\"name\":\"John\",\"age\":\"16\"}, {\"name\":\"Mike\",\"age\":\"19\"},{\"name\":\"Sarah\",\"age\":\"21\"}]\nfiltered= Q(l,{'name__contains':\"k\", \"age__lt\":20})\nprint(list(filtered))\n```\noutput\n```python\n[{'name': 'Mike', 'age': '19'}]\n```\n\nThe above filtration can be written as\n\n```python\nfrom leopards import Q\n\nl = [{\"name\": \"John\", \"age\": \"16\"}, {\"name\": \"Mike\", \"age\": \"19\"}, {\"name\": \"Sarah\", \"age\": \"21\"}]\nfiltered = Q(l, name__contains=\"k\", age__lt=20)\n\n```\n\n**Notes:** \n1. `Q` returns an iterator which can be converted to a list by calling `list`.\n2. Even though, age was `str` in the dict, as the value of in the query dict was `int`, Leopards converted the value in dict automatically to match the query data type. This behaviour can be stopped by passing `False` to `convert_types` parameter.\n\n## Supported filters\n* `eq`: equals and this default filter\n* `gt`: greater than.\n* `gte`: greater than or equal.\n* `lt`: less than \n* `lte`: less than or equal \n* `in`: the value in a list of a tuple.\n    * e.g.  age__in=[10,20,30]\n* `contains`: contains a substring as in the example.\n* `icontains`: case-insensitive `contains`.\n* `startswith`: checks if a value starts with a query strings.\n* `istartswith`: case-insensitive `startswith`.\n* `endswith`: checks if a value ends with a query strings.\n* `iendswith`: case-insensitive `endswith`.\n* `isnull`:  checks if the value matches any of NULL_VALUES which are `('', '.', None, \"None\", \"null\", \"NULL\")`\n  * e.g. `filter__isnull=True` or `filter__isnull=False`\n\nFor `eq`,`gt`,`gte`,`lt`,`lte`, `in`, `contains`, `icontains`, `startswith`,`istartswith`, `endswith` and `iendswith`, you can add a `n` to negate the results. e.g  `nin` which is equivalent to `not in` \n\n   \n## Advanced examples\nThis section will cover the use of `OR`, `AND` and `NOT`\n\n### Usage of `OR`\n`OR` or `__or__` takes a list of dictionaries to evaluate and returns with the first `True`.\n\n```python\nfrom leopards import Q\n\nl = [{\"name\": \"John\", \"age\": \"16\"}, {\"name\": \"Mike\", \"age\": \"19\"}, {\"name\": \"Sarah\", \"age\": \"21\"}]\nfiltered = Q(l, {\"OR\": [{\"name__contains\": \"k\"}, {\"age__gte\": 21}]})\nprint(list(filtered))\n```\noutput\n```python\n[{'name': 'Mike', 'age': '19'}, {'name': 'Sarah', 'age': '21'}]\n```\n\n### Usage of `NOT`\n`NOT` or `__not__` takes a dict for query run.\n\n```python\nfrom leopards import Q\n\nl = [{\"name\": \"John\", \"age\": \"16\"}, {\"name\": \"Mike\", \"age\": \"19\"}, {\"name\": \"Sarah\", \"age\": \"21\"}]\nfiltered = Q(l, {\"age__gt\": 15, \"NOT\": {\"age__eq\": 19}})\nprint(list(filtered))\n```\noutput\n```python\n[{'name': 'John', 'age': '16'}, {'name': 'Sarah', 'age': '21'}]\n```\n\n### Usage of `AND`\n`AND` or `__and__` takes a list of dict for query run, returns with the first `False`.\n\n```python\nfrom leopards import Q\n\nl = [{\"name\": \"John\", \"age\": \"16\"}, {\"name\": \"Mike\", \"age\": \"19\"}, {\"name\": \"Sarah\", \"age\": \"21\"}]\nfiltered = Q(l, {\"__and__\": [{\"age__gte\": 15}, {\"age__lt\": 21}]})\nprint(list(filtered))\n```\noutput\n```python\n[{'name': 'John', 'age': '16'}, {'name': 'Mike', 'age': '19'}]\n```\n\n## Aggregating Data\n\nYou  can run the following aggregations\n* Count\n* Max\n* Min\n* Sum\n* Avg\n\n### Count\n\nFind the count of certain aggregated column\n```python\nl = [{\"name\": \"John\", \"age\": \"16\"}, {\"name\": \"Mike\", \"age\": \"19\"}, {\"name\": \"Sarah\", \"age\": \"21\"},{\"name\":\"John\",\"age\":\"19\"}]\nfrom leopards import Count\ncount = Count(l,['age'])\n```\noutput\n```python\n[{\"age\":\"16\",\"count\":1},{\"age\":\"19\",\"count\":2}, {\"age\":\"21\",\"count\":1}]\n```\n\n### Max\n\nFind the Max value for a certain column in  certain aggregated columns\n```python\nl = [{\"name\": \"John\", \"age\": \"16\"}, {\"name\": \"Mike\", \"age\": \"19\"}, {\"name\": \"Sarah\", \"age\": \"21\"},{\"name\":\"John\",\"age\":\"19\"}]\nfrom leopards import Max\ncount = Max(l,\"age\",['name'],dtype=int)\n```\noutput\n```python\n[{'name': 'John', 'age': '19'}, {'name': 'Mike', 'age': '19'}, {'name': 'Sarah', 'age': '21'}]\n```\n\n**Notes:**\n* If you don't pass the aggregation columns, the maximum will be found across dataset.\n* You can pass the datatype of the column to convert it on the fly while evaluating\n```python\nl = [{\"name\": \"John\", \"age\": \"16\"}, {\"name\": \"Mike\", \"age\": \"19\"}, {\"name\": \"Sarah\", \"age\": \"21\"},{\"name\":\"John\",\"age\":\"19\"}]\nfrom leopards import Max\nm = Max(l,\"age\",dtype=int)\n```\n\noutput\n```python\n[{'age': 21}]\n```\n\n\n### Min\n\nFind the Max value for a certain column in  certain aggregated columns\n```python\nl = [{\"name\": \"John\", \"age\": \"16\"}, {\"name\": \"Mike\", \"age\": \"19\"}, {\"name\": \"Sarah\", \"age\": \"21\"},{\"name\":\"John\",\"age\":\"19\"}]\nfrom leopards import Min\nm = Min(l,\"age\",['name'])\n```\noutput\n```python\n[{'name': 'John', 'age': '16'}, {'name': 'Mike', 'age': '19'}, {'name': 'Sarah', 'age': '21'}]\n```\n**Note:** \n* If you don't pass the aggregation columns, the min will be found across dataset.\n* You can pass the datatype of the column to convert it on the fly while evaluating\n\n\n## Sum and Avg\n\nLike Min and Max but only works with integers and floats.\n\n## Comparison with Pandas\n\nThis is done on Python 3.8 running on Ubuntu 22.04 on i7 11th generation and 32 GB of RAM.\n\n| Comparison                                                  | Pandas   | Leopards    |\n|-------------------------------------------------------------|----------|-------------|\n| Package Size     <br/> (Lower is better)                    | 29.8 MB  | **7.5 KB**  |\n| import Time (Worst) <br/> (Lower is better)                 | 146 ms   | **1.05 ms** |\n| load 10k CSV lines<br/> (Lower is better) <sup>[1]</sup>    | 0.295s   | **0.138s**  |\n| get first matched record<br/> (Lower is better)             | 0.310s   | **0.017s**  |\n| print all filtered records (10/10k) <br/> (Lower is better) | 0.310s   | **0.137s**  | \n| filter by integers <br/>(Lower is better)                   | 0.316s   | **0.138s**  |\n\n<sup>[1]</sup> This was loading the whole csv in memory which was for sake of fair comparison. \nNevertheless,  Leopards can work with DictReader as an iterable which executes in **0.014s**, then it handles line by line.\n\nThanks for [Asma Tahir](https://github.com/tahirasma) for Pandas stats.\n\n\n## Contributors \n\n* [saeedesmaili](https://github.com/saeedesmaili)\n\n## Tutorials\n\n* [Usage with different file types](https://dev.to/mkalioby/leopards-with-different-file-types-1d3)\n* [Work on CSV Files with Leopards](https://dev.to/mkalioby/working-with-csv-by-leopards-5bmd)\n"
  },
  {
    "path": "USAGE.md",
    "content": "# Usage \n\nThis document covers how to use leopards with different file types\n\n## CSV\n\n`DictReader` from `csv` module can be used to read csv files as dictionaries as shown below.\n\n```python\nimport csv\nfrom leopards import Q\n\ndata = csv.DictReader(open(\"data.csv\"))\nres = Q(data, {\"age__gt\": 15})\n```\n\n## TSV\n`DictReader` from `csv` module can be used to read tsv files as dictionaries as shown below.\n\n```python\nimport csv\nfrom leopards import Q\n\ndata = csv.DictReader(open(\"data.csv\"), delimiter=\"\\t\")\nres = Q(data, {\"age__gt\": 15})\n```\n\n## JSON\n`json.load` can be used to read json files as dictionaries as shown below.\n\n```python\n\nimport json\nfrom leopards import Q\n\ndata = json.load(open(\"data.json\"))\nres = Q(data, {\"age__gt\": 15})\n```\n\n## XLS\n\n`xlrd` library can be used to read xls files as dictionaries as shown below.\n\n```python\nimport xlrd\nfrom leopards import Q\n\nwb = xlrd.open_workbook(\"data.xls\")\nsh = wb.sheets()[0]\nkeys = sh.row_values(0)\ndata =[]\nfor n in range(1, sh.nrows):\n    data.append({key: sh.row_values(n)[n2] for n2, key in enumerate(keys)})\nres = Q(data, {\"age__gt\": 15})                \n```\n\n## ClickHouse\n'clickhouse_driver' library can be used to read data from ClickHouse as dictionaries as shown below.\n```python\nimport clickhouse_connect\nclient = clickhouse_connect.get_client( host='localhost',  username='default',password='' )\nrows = client.execute(\"SELECT * FROM TABLE\")\ndata = rows.named_results()\nres = Q(data, {\"age__gt\": 15})\n```\n\n## MySQL\n\n`mysql-client` library can be used to read data from MySQL as dictionaries as shown below.\n\n```python\nimport MySQLdb\nfrom MySQLdb.cursors import DictCursor\nfrom leopards import Q\n\ndb=MySQLdb.connect(user='root',password='PASS', database=\"db\", cursorclass=DictCursor)\ncursor = db.cursor()\ncursor.execute(\"SELECT * FROM TABLE\")\ndata = cursor.fetchall()\nres = Q(data, {\"age__gt\": 15})\n```"
  },
  {
    "path": "leopards/Q.py",
    "content": "NULL_VALUES = ('', '.', None, \"None\", \"null\", \"NULL\")\n\n\ndef get_key_op(key:str):\n    \"\"\"Separate at the key to name and op\"\"\"\n    if \"__\" in key:\n        cols = key.split(\"__\")\n        k = cols[0]\n        op = cols[1]\n    else:\n        k = key\n        op = \"eq\"\n    return k, op\n\n\ndef convert_value(v:str, value_type:type):\n    \"\"\"Converts str to the data type of the value\"\"\"\n    from decimal import Decimal\n    if value_type is float:\n        return float(v)\n    elif value_type is int:\n        return int(v)\n    elif value_type is bytes:\n        return bytes(v, 'ascii')\n    elif value_type is Decimal:\n        return Decimal(v)\n    return str(v)\n\n\ndef evaluate(value:type, op:str, qv:type):\n    \"\"\"Evaluate the current value again the query value based on type.\"\"\"\n    if op == \"eq\":\n        return value == qv\n    elif op == \"gt\":\n        return value > qv\n    elif op == \"gte\":\n        return value >= qv\n    elif op == \"lt\":\n        return value < qv\n    elif op == \"lte\":\n        return value <= qv\n    elif op == \"in\":\n        return value in qv\n    elif op == \"contains\":\n        return qv in value\n    elif op == \"icontains\":\n        return qv.lower() in value.lower()\n    elif op == \"startswith\":\n        return value.startswith(qv)\n    elif op == \"istartswith\":\n        return value.lower().startswith(qv.lower())\n    elif op == \"endswith\":\n        return value.endswith(qv)\n    elif op == \"iendswith\":\n        return value.lower().endswith(qv.lower())\n    elif op == \"isnull\":\n        res = value in NULL_VALUES\n        return res == qv\n\n\ndef check(value:type, op:str, qv:type):\n    \"\"\"Checks for negation\"\"\"\n\n    if op.startswith(\"n\"):\n        op = op[1:]\n        return not evaluate(value, op, qv)\n    return evaluate(value, op, qv)\n"
  },
  {
    "path": "leopards/Query.py",
    "content": "from functools import partial\ntry:\n    from Q import get_key_op, convert_value,check\nexcept ModuleNotFoundError:  # pragma: no cover\n    from .Q import get_key_op, convert_value,check # pragma: no cover\n\ndef Q(iterable:list, query:dict=None, convert_types=True, **kwargs):\n    \"\"\"\n     Query a list of dictionary or objects by a query dict.\n    :param iterable: the iterable of dicts\n    :param query: dictionary holding your query\n    :param convert_types: try to convert the field in data to match query type\n    :return: Iterable of type filter\n    \"\"\"\n    def filter_list(item, **kwargs):\n        if type(item)  in (str, int, float, list, tuple):\n            raise TypeError(\"The item in the list shall be dict or object\")\n        if type(item) is not dict:\n            item= item.__dict__\n        for k,v in kwargs.items():\n            if k in(\"OR\" , \"__or__\"):\n                for q in v:\n                    if filter_list(item,**q):\n                        return True\n                return False\n            elif k in(\"AND\",\"__and__\"):\n                for q in v:\n                    if not filter_list(item,**q):\n                        return False\n                return True\n            elif k in (\"NOT\", \"__not__\"):\n               return  not filter_list(item,**v)\n            key, op = get_key_op(k)\n            value = item[key]\n            if convert_types and type(v) != type(value):\n                value = convert_value(value, type(v))\n            if not check(value, op, v):\n                return False\n        return True\n    if query is None: query={}\n    query.update(kwargs)\n    p = partial(filter_list, **query)\n    return filter(p, iterable)\n\ndef Count(iterable:list, cols:list=None, col_name:str='count'):\n    \"\"\"\n    :param iterable: iterable to count\n    :param cols: columns used to aggregated\n    :param col_name: the name of count column, default: count\n    :return: iterable of dicts\n    \"\"\"\n    new_dict={}\n\n    for item in iterable:\n        if type(item) is not dict:\n            item=item.__dict__  # pragma: no cover\n        if cols:\n            d={k:v for k,v in item.items() if k in cols}\n            k = \":\".join(d.values())\n        else:\n            k=\"ALL\"\n            d={}\n        if not k in new_dict:\n            new_dict[k]=d\n            new_dict[k][col_name]=0\n        new_dict[k][col_name]+=1\n    return  new_dict.values()\n\n\n\ndef Max(iterable:list, col_name:str, cols:list=None, dtype=str):\n    \"\"\"\n\n    :param iterable: iterable to loop through\n    :param col_name: the name of the column that Max shall be computed against\n    :param cols: columns to aggregate on\n    :return: iterable of dicts\n    \"\"\"\n    new_dict={}\n\n    for item in iterable:\n        if type(item) is not dict:\n            item=item.__dict__  # pragma: no cover\n        if cols:\n            d={k:v for k,v in item.items() if k in cols}\n            k = \":\".join(d.values())\n        else:\n            k='ALL'\n            d={}\n        v: str = item[col_name]\n        if dtype!=str:\n            v = dtype(v)\n        if not k in new_dict:\n            new_dict[k]=d\n            new_dict[k][col_name]=v\n        if v>new_dict[k][col_name]:\n            new_dict[k][col_name] = v\n    return new_dict.values()\n\n\ndef Min(iterable:list, col_name:str, cols:list=None, dtype=str):\n    \"\"\"\n\n    :param iterable: iterable to loop through\n    :param col_name: the name of the column that Min shall be computed against\n    :param cols: columns to aggregate on\n    :param dtype: data type of the cols\n\n    :return: iterable of dicts\n    \"\"\"\n    new_dict={}\n\n    for item in iterable:\n        if type(item) is not dict:\n            item=item.__dict__  # pragma: no cover\n        if cols:\n            d={k:v for k,v in item.items() if k in cols}\n            k = \":\".join(d.values())\n        else:\n            k=\"ALL\"\n            d={}\n        v:str=item[col_name]\n        if v != str:\n            v=dtype(v)\n        if not k in new_dict:\n            new_dict[k]=d\n            new_dict[k][col_name]=v\n        if v<new_dict[k][col_name]:\n            new_dict[k][col_name] = v\n    return new_dict.values()\n\ndef Sum(iterable:list, col_name:str, cols:list=None):\n    \"\"\"\n\n    :param iterable: iterable to loop through\n    :param col_name: the name of the column that Sum shall be computed against\n    :param cols: columns to aggregate on\n    :return: iterable of dicts\n    \"\"\"\n    new_dict={}\n\n    for item in iterable:\n        if type(item) is not dict:\n            item=item.__dict__  # pragma: no cover\n        if cols:\n            d={k:v for k,v in item.items() if k in cols}\n            k = \":\".join(d.values())\n        else:\n            k=\"ALL\"\n            d={}\n        if not k in new_dict:\n            new_dict[k]=d\n            new_dict[k][col_name]=float(0)\n        new_dict[k][col_name] += float(item[col_name])\n    return new_dict.values()\n\n\ndef Avg(iterable:list, col_name:str, cols:list=None):\n    \"\"\"\n\n    :param iterable: iterable to loop through\n    :param col_name: the name of the column that Average shall be computed against\n    :param cols: columns to aggregate on\n    :return: iterable of dicts\n    \"\"\"\n    new_dict={}\n\n    for item in iterable:\n        if type(item) is not dict:\n            item=item.__dict__   # pragma: no cover\n        if cols:\n            d={k:v for k,v in item.items() if k in cols}\n            k = \":\".join(d.values())\n        else:\n            k='ALL'\n            d={}\n        if not k in new_dict:\n            new_dict[k]=d\n            new_dict[k][col_name+\"__sum\"]=float(0)\n            new_dict[k][col_name+\"__count\"]=float(0)\n        new_dict[k][col_name+\"__sum\"] += float(item[col_name])\n        new_dict[k][col_name+\"__count\"] += 1\n\n    res = []\n    for d,v in new_dict.items():\n        v[col_name] = v.pop(col_name+\"__sum\")/ v.pop(col_name+\"__count\")\n        res.append(v)\n    return res"
  },
  {
    "path": "leopards/__init__.py",
    "content": "from .Query import *"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/env python\n\nfrom setuptools import find_packages, setup\n\nsetup(\n    name='leopards',\n    version='1.0.0',\n    description='Allows filtering & aggregation iterable of dictionary by another dictionary. Much faster than pandas',\n    long_description=open(\"README.md\").read(),\n    long_description_content_type=\"text/markdown\",\n    #long_description=\"Filters List of Dictionaries by a query dictionary\",\n    author='Mohamed El-Kalioby',\n    author_email = 'mkalioby@mkalioby.com',\n    url = 'https://github.com/mkalioby/leopards/',\n    download_url='https://github.com/mkalioby/leopards/',\n    license='MIT',\n    packages=find_packages(),\n    install_requires=[\n       ],\n    python_requires=\">=3.7\",\n    include_package_data=True,\n    zip_safe=False, # because we're including static files\n    classifiers=[\n        #\"Development Status :: 4 - Beta\",\n        \"Development Status :: 5 - Production/Stable\",\n        \"Intended Audience :: Developers\",\n        \"Operating System :: OS Independent\",\n        \"Programming Language :: Python\",\n        \"Programming Language :: Python :: 3\",\n        \"Programming Language :: Python :: 3.7\",\n        \"Programming Language :: Python :: 3.8\",\n        \"Programming Language :: Python :: 3.9\",\n        \"Programming Language :: Python :: 3.10\",\n        \"Programming Language :: Python :: 3.11\",\n        \"Programming Language :: Python :: 3.12\",\n        \"Programming Language :: Python :: 3.13\",\n        \"Topic :: Software Development :: Libraries :: Python Modules\",\n]\n)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_file.py",
    "content": "l=[{\"chromosome\": \"chr1\", \"start pos\": \"113460677\", \"end pos\": \"113460684\", \"reference\": \"AATAAATA\", \"observed\": \"-\", \"quality\": \"116.74\", \"filter\": \"PASS\", \"zygosity\": \"hom\", \"refGene function\": \"splicing\", \"refGene gene\": \"SLC16A1\", \"refGene splice info\": \"NM_001166496:exon4:r.spl\", \"refGene exonic function\": \"\", \"refGene AA change\": \"\", \"refGene exonic;splicing Info\": \"NM_001166496:exon4:113459799-113460666:877:+11;NM_003051:exon4:113459799-113460666:877:+11\"}, {\"chromosome\": \"chr1\", \"start pos\": \"114372214\", \"end pos\": \"114372214\", \"reference\": \"C\", \"observed\": \"G\", \"quality\": \"48.75\", \"filter\": \"PASS\", \"zygosity\": \"het\", \"refGene function\": \"exonic;splicing\", \"refGene gene\": \"PTPN22;PTPN22\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"PTPN22:NM_012411:exon16:c.2085G>C:p.Lys695Asn,PTPN22:NM_001308297:exon17:c.2178G>C:p.Lys726Asn,PTPN22:NM_001193431:exon18:c.2166G>C:p.Lys722Asn,PTPN22:NM_015967:exon18:c.2250G>C:p.Lys750Asn\", \"refGene exonic;splicing Info\": \"NM_012411:exon16:114372213-114372329:0:115;NM_015967:exon18:114372213-114372329:0:115;NM_001308297:exon17:114372213-114372329:0:115;NM_001193431:exon18:114372213-114372329:0:115\"}, {\"chromosome\": \"chr1\", \"start pos\": \"145360608\", \"end pos\": \"145360608\", \"reference\": \"T\", \"observed\": \"A\", \"quality\": \"52.12\", \"filter\": \"PASS\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"NBPF19\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"NBPF19:NM_001351365:exon69:c.8414T>A:p.Leu2805Gln\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chr1\", \"start pos\": \"145360700\", \"end pos\": \"145360700\", \"reference\": \"C\", \"observed\": \"T\", \"quality\": \"9.32\", \"filter\": \"MG_SNP_Filter\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"NBPF19\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"NBPF19:NM_001351365:exon69:c.8506C>T:p.Arg2836Cys\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chr10\", \"start pos\": \"125780762\", \"end pos\": \"125780762\", \"reference\": \"-\", \"observed\": \"GGGC\", \"quality\": \"159.95\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"exonic;splicing\", \"refGene gene\": \"CHST15;CHST15\", \"refGene splice info\": \"NM_015892:exon6:c.1347+10->GCCC;NM_001270764:exon6:c.1347+10->GCCC\", \"refGene exonic function\": \"frameshift insertion\", \"refGene AA change\": \"CHST15:NM_001270765:exon6:c.1356_1357insGCCC:p.Pro453fs,CHST15:NM_014863:exon6:c.1356_1357insGCCC:p.Pro453fs\", \"refGene exonic;splicing Info\": \"NM_015892:exon6:125780771-125780928:-10:166;NM_001270765:exon6:125779168-125780928:1593:166;NM_014863:exon6:125779168-125780928:1593:166;NM_001270764:exon6:125780771-125780928:-10:166\"}, {\"chromosome\": \"chr10\", \"start pos\": \"126714752\", \"end pos\": \"126714752\", \"reference\": \"G\", \"observed\": \"A\", \"quality\": \"140.3\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"CTBP2\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"CTBP2:NM_022802:exon1:c.1577C>T:p.Pro526Leu\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chr11\", \"start pos\": \"12316344\", \"end pos\": \"12316344\", \"reference\": \"-\", \"observed\": \"CTCCTCCTCCTC\", \"quality\": \"68.98\", \"filter\": \"PASS\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"MICALCL\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonframeshift insertion\", \"refGene AA change\": \"MICALCL:NM_032867:exon3:c.1366_1367insCTCCTCCTCCTC:p.Ala456delinsAlaProProProPro\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chr11\", \"start pos\": \"60899366\", \"end pos\": \"60899366\", \"reference\": \"G\", \"observed\": \"T\", \"quality\": \"7.79\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"VPS37C\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"VPS37C:NM_017966:exon5:c.994C>A:p.Pro332Thr\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chr11\", \"start pos\": \"60899396\", \"end pos\": \"60899396\", \"reference\": \"C\", \"observed\": \"T\", \"quality\": \"7.79\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"VPS37C\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"VPS37C:NM_017966:exon5:c.964G>A:p.Gly322Ser\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chr12\", \"start pos\": \"21375307\", \"end pos\": \"21375309\", \"reference\": \"AAA\", \"observed\": \"-\", \"quality\": \"63.71\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"splicing\", \"refGene gene\": \"SLCO1B1\", \"refGene splice info\": \"NM_006446:exon13:r.spl\", \"refGene exonic function\": \"\", \"refGene AA change\": \"\", \"refGene exonic;splicing Info\": \"NM_006446:exon13:21375233-21375298:73:+9\"}, {\"chromosome\": \"chr13\", \"start pos\": \"32972752\", \"end pos\": \"32972752\", \"reference\": \"T\", \"observed\": \"C\", \"quality\": \"245.66\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"BRCA2\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"BRCA2:NM_000059:exon27:c.10102T>C:p.Ser3368Pro\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chr19\", \"start pos\": \"43865321\", \"end pos\": \"43865321\", \"reference\": \"T\", \"observed\": \"A\", \"quality\": \"17.17\", \"filter\": \"PASS\", \"zygosity\": \"het\", \"refGene function\": \"exonic\", \"refGene gene\": \"CD177\", \"refGene splice info\": \"\", \"refGene exonic function\": \"unknown\", \"refGene AA change\": \"UNKNOWN\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chr20\", \"start pos\": \"10389422\", \"end pos\": \"10389422\", \"reference\": \"T\", \"observed\": \"C\", \"quality\": \"49.79\", \"filter\": \"PASS\", \"zygosity\": \"het\", \"refGene function\": \"exonic\", \"refGene gene\": \"MKKS\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"MKKS:NM_018848:exon4:c.1015A>G:p.Ile339Val,MKKS:NM_170784:exon4:c.1015A>G:p.Ile339Val\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chr20\", \"start pos\": \"31672703\", \"end pos\": \"31672703\", \"reference\": \"G\", \"observed\": \"A\", \"quality\": \"175.97\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"exonic;splicing\", \"refGene gene\": \"BPIFB4;BPIFB4\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"BPIFB4:NM_182519:exon4:c.683G>A:p.Arg228His\", \"refGene exonic;splicing Info\": \"NM_182519:exon4:31672697-31672802:5:99\"}, {\"chromosome\": \"chr22\", \"start pos\": \"29885581\", \"end pos\": \"29885604\", \"reference\": \"AGGCCAAGTCCCCAGAGAAGGAAG\", \"observed\": \"-\", \"quality\": \"32.62\", \"filter\": \"PASS\", \"zygosity\": \"het\", \"refGene function\": \"exonic\", \"refGene gene\": \"NEFH\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonframeshift deletion\", \"refGene AA change\": \"NEFH:NM_021076:exon4:c.1952_1975del:p.651_659del\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chrX\", \"start pos\": \"7811643\", \"end pos\": \"7811643\", \"reference\": \"C\", \"observed\": \"A\", \"quality\": \"118.3\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"VCX\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"VCX:NM_013452:exon3:c.207C>A:p.Ser69Arg\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chrX\", \"start pos\": \"38145640\", \"end pos\": \"38145640\", \"reference\": \"C\", \"observed\": \"T\", \"quality\": \"7.77\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"RPGR\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"RPGR:NM_001034853:exon15:c.2612G>A:p.Gly871Glu\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chrX\", \"start pos\": \"66765158\", \"end pos\": \"66765158\", \"reference\": \"-\", \"observed\": \"GCA\", \"quality\": \"73.91\", \"filter\": \"PASS\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"AR\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonframeshift insertion\", \"refGene AA change\": \"AR:NM_000044:exon1:c.170_171insGCA:p.Leu57delinsLeuGln,AR:NM_001348061:exon1:c.170_171insGCA:p.Leu57delinsLeuGln,AR:NM_001348063:exon1:c.170_171insGCA:p.Leu57delinsLeuGln,AR:NM_001348064:exon1:c.170_171insGCA:p.Leu57delinsLeuGln\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chrX\", \"start pos\": \"71379704\", \"end pos\": \"71379704\", \"reference\": \"T\", \"observed\": \"C\", \"quality\": \"80.11\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"exonic;splicing\", \"refGene gene\": \"FLJ44635;FLJ44635\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"FLJ44635:NM_207422:exon2:c.25T>C:p.Tyr9His\", \"refGene exonic;splicing Info\": \"NM_207422:exon2:71379686-71381600:17:1896\"}, {\"chromosome\": \"chrX\", \"start pos\": \"100749053\", \"end pos\": \"100749053\", \"reference\": \"-\", \"observed\": \"GACTGA\", \"quality\": \"34.48\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"ARMCX4\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonframeshift insertion\", \"refGene AA change\": \"ARMCX4:NM_001256155:exon2:c.5477_5478insGACTGA:p.Gly1826delinsGlyThrGlu\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chrX\", \"start pos\": \"100749065\", \"end pos\": \"100749065\", \"reference\": \"A\", \"observed\": \"G\", \"quality\": \"44.99\", \"filter\": \"PASS\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"ARMCX4\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"ARMCX4:NM_001256155:exon2:c.5489A>G:p.Glu1830Gly\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chrX\", \"start pos\": \"101573515\", \"end pos\": \"101573515\", \"reference\": \"C\", \"observed\": \"T\", \"quality\": \"6.99\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"splicing\", \"refGene gene\": \"NXF2\", \"refGene splice info\": \"NM_022053:exon9:c.724+14C>T\", \"refGene exonic function\": \"\", \"refGene AA change\": \"\", \"refGene exonic;splicing Info\": \"NM_022053:exon9:101573431-101573501:83:+14\"}, {\"chromosome\": \"chrX\", \"start pos\": \"103349504\", \"end pos\": \"103349504\", \"reference\": \"C\", \"observed\": \"T\", \"quality\": \"115.48\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"SLC25A53\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"SLC25A53:NM_001012755:exon2:c.437G>A:p.Arg146His\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chrX\", \"start pos\": \"111000973\", \"end pos\": \"111000973\", \"reference\": \"C\", \"observed\": \"T\", \"quality\": \"77.11\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"exonic;splicing\", \"refGene gene\": \"ALG13;ALG13\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"ALG13:NM_001324293:exon23:c.2408C>T:p.Ala803Val,ALG13:NM_001257230:exon25:c.2582C>T:p.Ala861Val,ALG13:NM_001257234:exon25:c.2582C>T:p.Ala861Val,ALG13:NM_001257237:exon25:c.2582C>T:p.Ala861Val,ALG13:NM_001324292:exon25:c.2894C>T:p.Ala965Val,ALG13:NM_001099922:exon26:c.3131C>T:p.Ala1044Val,ALG13:NM_001257231:exon26:c.2897C>T:p.Ala966Val\", \"refGene exonic;splicing Info\": \"NM_001099922:exon26:111000815-111000990:157:17;NM_001257230:exon25:111000815-111000990:157:17;NM_001324292:exon25:111000815-111000990:157:17;NM_001257231:exon26:111000815-111000990:157:17;NM_001257234:exon25:111000815-111000990:157:17;NM_001257237:exon25:111000815-111000990:157:17;NM_001324293:exon23:111000815-111000990:157:17\"}, {\"chromosome\": \"chrX\", \"start pos\": \"118985711\", \"end pos\": \"118985711\", \"reference\": \"-\", \"observed\": \"TTTTTTT\", \"quality\": \"93.71\", \"filter\": \"PASS\", \"zygosity\": \"hom\", \"refGene function\": \"splicing\", \"refGene gene\": \"UPF3B\", \"refGene splice info\": \"NM_023010:exon2:c.263+19->AAAAAAA\", \"refGene exonic function\": \"\", \"refGene AA change\": \"\", \"refGene exonic;splicing Info\": \"NM_023010:exon2:118985729-118985836:-19:125;NM_080632:exon2:118985729-118985836:-19:125\"}, {\"chromosome\": \"chrX\", \"start pos\": \"128896018\", \"end pos\": \"128896018\", \"reference\": \"C\", \"observed\": \"G\", \"quality\": \"133.37\", \"filter\": \"PASS\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"XPNPEP2\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"XPNPEP2:NM_003399:exon18:c.1640C>G:p.Ala547Gly\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chrX\", \"start pos\": \"135429119\", \"end pos\": \"135429119\", \"reference\": \"T\", \"observed\": \"G\", \"quality\": \"142.39\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"exonic\", \"refGene gene\": \"ADGRG4\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"ADGRG4:NM_153834:exon6:c.3254T>G:p.Ile1085Ser\", \"refGene exonic;splicing Info\": \"\"}, {\"chromosome\": \"chrY\", \"start pos\": \"9175204\", \"end pos\": \"9175204\", \"reference\": \"T\", \"observed\": \"G\", \"quality\": \"7.77\", \"filter\": \".\", \"zygosity\": \"hom\", \"refGene function\": \"exonic;splicing\", \"refGene gene\": \"TSPY1;TSPY10\", \"refGene splice info\": \"\", \"refGene exonic function\": \"nonsynonymous SNV\", \"refGene AA change\": \"TSPY10:NM_001282469:exon1:c.86T>G:p.Leu29Trp,TSPY10:NM_001320962:exon1:c.86T>G:p.Leu29Trp,TSPY1:NM_001320964:exon1:c.86T>G:p.Leu29Trp\", \"refGene exonic;splicing Info\": \"NM_001320962:exon1:9175072-9175337:131:133;NM_001282469:exon1:9175072-9175337:131:133;NM_001320964:exon1:9175072-9175205:131:1\"}]\n"
  },
  {
    "path": "tests/test_leopards.py",
    "content": "#! /usr/bin/env python\nimport unittest\nfrom decimal import Decimal\n\ntry:\n    from .test_file import *\nexcept ModuleNotFoundError:\n    from test_file import *\n\ndef Q(data,query=None,convert_types=True,**kwargs):\n    from leopards.Query import Q\n    return list(Q(data,query,convert_types,**kwargs))\n\nclass Employee:\n    def __init__(self,name,age):\n        self.name = name\n        self.age = age\n\nemployees = [Employee('Ahmed',12),Employee('Mohamed',24)]\nclass TestConditions(unittest.TestCase):\n    def test_eq(self):\n        self.assertEqual(len(Q(l, {\"chromosome\": 'chr1', 'start pos': '114372214', 'end pos': '114372214'})), 1)\n\n    def test_neq(self):\n        self.assertEqual(len(Q(l, chromosome__neq='chr1')), 24)\n\n    def test_neq2(self):\n        self.assertEqual(len(Q(l, {\"chromosome\": 'chr1', 'start pos__neq': '114372214'})), 3)\n\n    def test_gt(self):\n        self.assertEqual(len(Q(l, {\"chromosome\": 'chr1', 'start pos__gt': '114372214', 'end pos__gt': '114372214'})), 2)\n\n    def test_gte(self):\n        self.assertEqual(len(Q(l, {\"chromosome\": 'chr1', 'start pos__gte': '114372214', 'end pos__gte': '114372214'})),\n                         3)\n\n    def test_lt(self):\n        self.assertEqual(len(Q(l, {\"chromosome\": 'chr1', 'start pos__lt': '114372214'})), 1)\n\n    def test_lte(self):\n        self.assertEqual(len(Q(l, {\"chromosome\": 'chr1', 'end pos__lte': '114372214'})), 2)\n\n    def test_in(self):\n        self.assertEqual(len(Q(l, chromosome__in=('chr1', 'chr22'))), 5)\n\n    def test_nin(self):\n        self.assertEqual(len(Q(l, chromosome__nin=('chr1', 'chr22'))), 23)\n\n    def test_contains(self):\n        self.assertEqual(len(Q(l, chromosome__contains='chr2')), 3)\n\n    def test_ncontains(self):\n        self.assertEqual(len(Q(l, chromosome__ncontains='chr1')), 16)\n\n    def test_contains2(self):\n        self.assertEqual(len(Q(l, chromosome__contains='Chr2')), 0)\n\n    def test_ncontains2(self):\n        self.assertEqual(len(Q(l, chromosome__ncontains='Chr2')), 28)\n\n    def test_icontains(self):\n        self.assertEqual(len(Q(l, chromosome__icontains='Chr2')), 3)\n\n    def test_icontains2(self):\n        self.assertEqual(len(Q(l, chromosome__icontains='chr2')), 3)\n\n    def test_nicontains(self):\n        self.assertEqual(len(Q(l, chromosome__nicontains='Chr2')), 25)\n\n    def test_nicontains2(self):\n        self.assertEqual(len(Q(l, chromosome__nicontains='chr2')), 25)\n\n    def test_null(self):\n        self.assertEqual(len(Q(l, filter__isnull=True)), 16)\n\n    def test_null2(self):\n        self.assertEqual(len(Q(l, {\"filter__isnull\": False})), 12)\n\n    def test_startswith(self):\n        self.assertEqual(len(Q(l, {\"reference__startswith\": 'T'})), 7)\n\n    def test_istartswith(self):\n        self.assertEqual(len(Q(l, {\"reference__istartswith\": 't'})), 7)\n\n    def test_nstartswith(self):\n        self.assertEqual(len(Q(l, {\"reference__nstartswith\": 'T'})), 21)\n\n    def test_endswith(self):\n        self.assertEqual(len(Q(l, {\"observed__endswith\": 'C'})), 5)\n\n    def test_iendsswith(self):\n        self.assertEqual(len(Q(l, {\"observed__iendswith\": 'C'})), 5)\n\n    def test_nendswith(self):\n        self.assertEqual(len(Q(l, {\"observed__nendswith\": 'C'})), 23)\n\nclass TestConverations(unittest.TestCase):\n    def test_int(self):\n        self.assertEqual(len(Q(l, {\"chromosome\": 'chr1', 'start pos': 114372214, 'end pos': 114372214})), 1)\n\n    def test_bytes(self):\n        self.assertEqual(len(Q(l, {\"chromosome\": b'chr1', 'start pos': 114372214, 'end pos': 114372214})), 1)\n\n    def test_float(self):\n        res = Q(l, {\"quality__gte\": 133.47, \"quality__lt\": 142.39})\n        self.assertEqual(len(res), 1)\n\n    def test_decimal(self):\n        res = Q(l, {\"quality__gte\": Decimal(133.47), \"quality__lte\": Decimal(142.39)})\n        self.assertEqual(len(res), 1)\n\n\nclass TestCombination(unittest.TestCase):\n    def test_or(self):\n        res = Q(l, {\"OR\": [{\"chromosome\": 'chr1', 'start pos': 114372214, 'end pos': 114372214},\n                           {\"reference\": 'C', 'observed': 'A'}]})\n\n        self.assertEqual(len(res), 2)\n\n    def test_and(self):\n        res = Q(l, {\"__and__\": [{\"chromosome\": 'chr1', 'start pos': 114372214, 'end pos': 114372214},\n                                {\"reference__neq\": 'G'}]})\n        self.assertEqual(len(res), 1)\n\n    def test_not(self):\n        self.assertEqual(len(Q(l, {\"reference\": \"T\", \"NOT\": {\"observed\": \"C\"}})), 4)\n\n\nclass TestObjects(unittest.TestCase):\n    def test_obj(self):\n        self.assertEqual(len(Q(employees,age__gt=13)),1)\n\nclass TestException(unittest.TestCase):\n    def testInt(self):\n        self.assertRaises(TypeError, Q, [1,2,3],{\"i__gt\":1})\n\nclass TestAgg(unittest.TestCase):\n    def testCountDefault(self):\n        from leopards import Count\n        res = Count(l, [\"chromosome\"])\n        self.assertEqual(list(Q(res, chromosome=\"chr20\"))[0][\"count\"], 2)\n    def testCountDefault2(self):\n        from leopards import Count\n        res = Count(l)\n        self.assertEqual(list(res)[0][\"count\"], 28)\n    def testCountCol(self):\n        from leopards import Count\n        res= Count(l,[\"chromosome\"],\"chr_count\")\n        self.assertEqual(list(Q(res,chromosome=\"chr1\"))[0][\"chr_count\"],4)\n\n    def testCountCol2(self):\n        from leopards import Count\n        res= Count(l,col_name=\"chr_count\")\n        self.assertEqual(list(res)[0]['chr_count'],28)\n\n    def testMax(self):\n        from leopards import Max\n        res = Max(l, \"start pos\", [\"chromosome\"])\n        self.assertEqual(list(Q(res, chromosome=\"chr10\"))[0][\"start pos\"], \"126714752\")\n\n    def testMax2(self):\n        from leopards import Max\n        res = Max(l, \"quality\",dtype=float)\n        self.assertEqual(list(res)[0]['quality'], 245.66)\n\n    def testMin(self):\n        from leopards import Min\n        res = Min(l, \"end pos\", [\"chromosome\"],dtype=float)\n        self.assertEqual(list(Q(res, chromosome=\"chrX\"))[0][\"end pos\"], 7811643)\n\n    def testMin2(self):\n        from leopards import Min\n        res = Min(l, \"quality\", dtype=float)\n        self.assertEqual(list(res)[0][\"quality\"], 6.99)\n\n\n    def testSum(self):\n        from leopards import Sum\n        res = Sum(l, \"quality\", [\"chromosome\"])\n        v=\"%.2f\" % list(Q(res, chromosome=\"chr11\"))[0][\"quality\"]\n        self.assertEqual(v, \"84.56\")\n\n    def testSum2(self):\n        from leopards import Sum\n        res = Sum(l, \"quality\")\n        v=\"%.2f\" % list(res)[0][\"quality\"]\n        self.assertEqual(v, \"2133.04\")\n\n    def testAvg(self):\n        from leopards import Avg\n        res = Avg(l, \"quality\", [\"chromosome\"])\n        v=\"%.2f\" % list(Q(res, chromosome=\"chr11\"))[0][\"quality\"]\n        self.assertEqual(v, \"28.19\")\n\n    def testAvg2(self):\n        from leopards import Avg\n        res = Avg(l, \"quality\")\n        v=\"%.2f\" % list(res)[0][\"quality\"]\n        self.assertEqual(v, \"76.18\")\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist=\n    py37,\n    py38,\n    py39\n    py310\n    py311\n    py312\n    py313\n\n\n[testenv]\ndeps =\n\nallowlist_externals =\n    pytest\n\ncommands =\n    pytest --cov=leopards tests/ --cov-report term-missing\n"
  }
]