{ "info": { "author": "Fabian Schindler", "author_email": "fabian.schindler@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Scientific/Engineering :: GIS" ], "description": "# pycql\n\n[![PyPI version](https://badge.fury.io/py/pycql.svg)](https://badge.fury.io/py/pycql)\n[![Build Status](https://travis-ci.org/geopython/pycql.svg?branch=master)](https://travis-ci.org/geopython/pycql)\n[![Documentation Status](https://readthedocs.org/projects/pycql/badge/?version=latest)](https://pycql.readthedocs.io/en/latest/?badge=latest)\n\n\nA pure python CQL parser.\n\n## Installation\n\n```bash\npip install pycql\n```\n\n## Usage\n\nThe basic functionality parses the input string to an abstract syntax tree (AST) representation.\nThis AST can then be used to build database filters or similar functionality.\n\n```python\n>>> import pycql\n>>> ast = pycql.parse(filter_expression)\n```\n\n### Inspection\n\nThe easiest way to inspect the resulting AST is to use the `get_repr` function, which returns a\nnice string representation of what was parsed:\n\n```python\n>>> ast = pycql.parse('id = 10')\n>>> print(pycql.get_repr(ast))\nATTRIBUTE id = LITERAL 10.0\n>>>\n>>>\n>>> filter_expr = '(number BETWEEN 5 AND 10 AND string NOT LIKE \"%B\") OR INTERSECTS(geometry, LINESTRING(0 0, 1 1))'\n>>> print(pycql.get_repr(pycql.parse(filter_expr)))\n(\n (\n ATTRIBUTE number BETWEEN LITERAL 5.0 AND LITERAL 10.0\n ) AND (\n ATTRIBUTE string NOT ILIKE LITERAL '%B'\n )\n) OR (\n INTERSECTS(ATTRIBUTE geometry, LITERAL GEOMETRY 'LINESTRING(0 0, 1 1)')\n)\n```\n\n### Evaluation\n\nIn order to create useful filters from the resulting AST, it has to be evaluated. For the\nDjango integration, this was done using a recursive descent into the AST, evaluating the\nsubnodes first and constructing a `Q` object. Consider having a `filters` API (for an\nexample look at the Django one) which creates the filter. Now the evaluator looks something\nlike this:\n\n```python\n\nfrom pycql.ast import *\nfrom myapi import filters # <- this is where the filters are created.\n # of course, this can also be done in the\n # evaluator itself\nclass FilterEvaluator(object):\n def __init__(self, field_mapping=None, mapping_choices=None):\n self.field_mapping = field_mapping\n self.mapping_choices = mapping_choices\n\n def to_filter(self, node):\n to_filter = self.to_filter\n if isinstance(node, NotConditionNode):\n return filters.negate(to_filter(node.sub_node))\n elif isinstance(node, CombinationConditionNode):\n return filters.combine(\n (to_filter(node.lhs), to_filter(node.rhs)), node.op\n )\n elif isinstance(node, ComparisonPredicateNode):\n return filters.compare(\n to_filter(node.lhs), to_filter(node.rhs), node.op,\n self.mapping_choices\n )\n elif isinstance(node, BetweenPredicateNode):\n return filters.between(\n to_filter(node.lhs), to_filter(node.low),\n to_filter(node.high), node.not_\n )\n elif isinstance(node, BetweenPredicateNode):\n return filters.between(\n to_filter(node.lhs), to_filter(node.low),\n to_filter(node.high), node.not_\n )\n\n # ... Some nodes are left out for brevity\n\n elif isinstance(node, AttributeExpression):\n return filters.attribute(node.name, self.field_mapping)\n\n elif isinstance(node, LiteralExpression):\n return node.value\n\n elif isinstance(node, ArithmeticExpressionNode):\n return filters.arithmetic(\n to_filter(node.lhs), to_filter(node.rhs), node.op\n )\n\n return node\n```\n\nAs mentionend, the `to_filter` method is the recursion.\n\n## Testing\n\nThe basic functionality can be tested using `pytest`.\n\n```bash\npython -m pytest\n```\n\nThere is a test project/app to test the Django integration. This is tested using the following\ncommand:\n\n```bash\npython manage.py test testapp\n```\n\n\n## Django integration\n\nFor Django there is a default bridging implementation, where all the filters are translated to the\nDjango ORM. In order to use this integration, we need two dictionaries, one mapping the available\nfields to the Django model fields, and one to map the fields that use `choices`. Consider the\nfollowing example models:\n\n```python\nfrom django.contrib.gis.db import models\n\n\noptional = dict(null=True, blank=True)\n\nclass Record(models.Model):\n identifier = models.CharField(max_length=256, unique=True, null=False)\n geometry = models.GeometryField()\n\n float_attribute = models.FloatField(**optional)\n int_attribute = models.IntegerField(**optional)\n str_attribute = models.CharField(max_length=256, **optional)\n datetime_attribute = models.DateTimeField(**optional)\n choice_attribute = models.PositiveSmallIntegerField(choices=[\n (1, 'ASCENDING'),\n (2, 'DESCENDING'),],\n **optional)\n\n\nclass RecordMeta(models.Model):\n record = models.ForeignKey(Record, on_delete=models.CASCADE, related_name='record_metas')\n\n float_meta_attribute = models.FloatField(**optional)\n int_meta_attribute = models.IntegerField(**optional)\n str_meta_attribute = models.CharField(max_length=256, **optional)\n datetime_meta_attribute = models.DateTimeField(**optional)\n choice_meta_attribute = models.PositiveSmallIntegerField(choices=[\n (1, 'X'),\n (2, 'Y'),\n (3, 'Z')],\n **optional)\n```\n\nNow we can specify the field mappings and mapping choices to be used when applying the filters:\n\n```python\nFIELD_MAPPING = {\n 'identifier': 'identifier',\n 'geometry': 'geometry',\n 'floatAttribute': 'float_attribute',\n 'intAttribute': 'int_attribute',\n 'strAttribute': 'str_attribute',\n 'datetimeAttribute': 'datetime_attribute',\n 'choiceAttribute': 'choice_attribute',\n\n # meta fields\n 'floatMetaAttribute': 'record_metas__float_meta_attribute',\n 'intMetaAttribute': 'record_metas__int_meta_attribute',\n 'strMetaAttribute': 'record_metas__str_meta_attribute',\n 'datetimeMetaAttribute': 'record_metas__datetime_meta_attribute',\n 'choiceMetaAttribute': 'record_metas__choice_meta_attribute',\n}\n\nMAPPING_CHOICES = {\n 'choiceAttribute': dict(Record._meta.get_field('choice_attribute').choices),\n 'choiceMetaAttribute': dict(RecordMeta._meta.get_field('choice_meta_attribute').choices),\n}\n```\n\nFinally we are able to connect the CQL AST to the Django database models. We also provide factory\nfunctions to parse the timestamps, durations, geometries and envelopes, so that they can be used\nwith the ORM layer:\n\n```python\nfrom pycql.integrations.django import to_filter, parse\n\ncql_expr = 'strMetaAttribute LIKE \"%parent%\" AND datetimeAttribute BEFORE 2000-01-01T00:00:01Z'\n\n# NOTE: we are using the django integration `parse` wrapper here\nast = parse(cql_expr)\nfilters = to_filter(ast, mapping, mapping_choices)\n\nqs = Record.objects.filter(**filters)\n```\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/geopython/pycql", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "pycql", "package_url": "https://pypi.org/project/pycql/", "platform": "", "project_url": "https://pypi.org/project/pycql/", "project_urls": { "Homepage": "https://github.com/geopython/pycql" }, "release_url": "https://pypi.org/project/pycql/0.0.7/", "requires_dist": [ "ply" ], "requires_python": "", "summary": "CQL parser for Python", "version": "0.0.7" }, "last_serial": 5855046, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "4d52fec6906b01b18d5fa9a07fa56578", "sha256": "f4481a79d58490b928e1d755a1f8f0bfb682d5c7b67a90c4b78dad5067d57ccb" }, "downloads": -1, "filename": "pycql-0.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "4d52fec6906b01b18d5fa9a07fa56578", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 25861, "upload_time": "2019-09-09T14:04:28", "url": "https://files.pythonhosted.org/packages/ae/93/4984f95672d41b8c1aabafbc2411f844c3f5abb66efc5a377a0985bf5efe/pycql-0.0.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "4454cf8ff20813949125d60d11e99b41", "sha256": "1862fafcb1b600abc89b305fcb9a01c6dae43f2f83577d0063a46f2772cfbb01" }, "downloads": -1, "filename": "pycql-0.0.1.tar.gz", "has_sig": false, "md5_digest": "4454cf8ff20813949125d60d11e99b41", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 19866, "upload_time": "2019-09-09T14:04:31", "url": "https://files.pythonhosted.org/packages/4b/c8/e4e6bebeaa7210d2c74dcd231d233ccc809065fcbc24b9a15b4535abc60b/pycql-0.0.1.tar.gz" } ], "0.0.2": [ { "comment_text": "", "digests": { "md5": "04435492e96a3220203cf4768b019f91", "sha256": "174934ea775cb3aa7720cffe2380aaa6f1663d884ed1bdbb394e372792b435d5" }, "downloads": -1, "filename": "pycql-0.0.2-py3-none-any.whl", "has_sig": false, "md5_digest": "04435492e96a3220203cf4768b019f91", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 25853, "upload_time": "2019-09-09T14:09:41", "url": "https://files.pythonhosted.org/packages/ae/e6/0a97e0069be0c3f6c5e6742e741ff4cc33e1f5d5f19eb337a2a9922849a8/pycql-0.0.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c2bd635c79475fa75af4ec5973851d5b", "sha256": "4e3fc824ffbd5b461ecb4d7b4114bd87e73e70d604d6d38251d105a44079aaf7" }, "downloads": -1, "filename": "pycql-0.0.2.tar.gz", "has_sig": false, "md5_digest": "c2bd635c79475fa75af4ec5973851d5b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 19818, "upload_time": "2019-09-09T14:09:43", "url": "https://files.pythonhosted.org/packages/3e/cc/972fda1e0f45fe11b328c6307cbeda69b26ab28e5cd0385bcf8e089b8422/pycql-0.0.2.tar.gz" } ], "0.0.3": [ { "comment_text": "", "digests": { "md5": "19c5810edaa6d55e59401ca21ae4d85c", "sha256": "32079dee4c747904c7c607ce5e4b60503dbc6e4dc408d1d6050dd137003dfa6d" }, "downloads": -1, "filename": "pycql-0.0.3-py3-none-any.whl", "has_sig": false, "md5_digest": "19c5810edaa6d55e59401ca21ae4d85c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 26835, "upload_time": "2019-09-10T12:06:58", "url": "https://files.pythonhosted.org/packages/11/4b/03f19549af4edd3a0496d3cb09ca8d5fcd590a20cea2e29d6584ed69a9c5/pycql-0.0.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "deded805777ad2969bd89371aa46969a", "sha256": "146b013d690ea86b1cdbd132e44b4a682c49e4bd87aa97d6f7a3f63cb7eab851" }, "downloads": -1, "filename": "pycql-0.0.3.tar.gz", "has_sig": false, "md5_digest": "deded805777ad2969bd89371aa46969a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21619, "upload_time": "2019-09-10T12:06:59", "url": "https://files.pythonhosted.org/packages/73/91/19258a40f77de627010715422633e548b1047a27eb746ee982806c955953/pycql-0.0.3.tar.gz" } ], "0.0.4": [ { "comment_text": "", "digests": { "md5": "2793ccab3030a27c387df78837637d9b", "sha256": "a2642ba4edc55b3059f04e7fc94f76659ada8d7765feb868841611539d706548" }, "downloads": -1, "filename": "pycql-0.0.4-py3-none-any.whl", "has_sig": false, "md5_digest": "2793ccab3030a27c387df78837637d9b", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 29133, "upload_time": "2019-09-11T16:02:50", "url": "https://files.pythonhosted.org/packages/f7/65/0c296bd0c801e985765527ea8a85c39dfcb2f1b130b259f4fcbff93d65b6/pycql-0.0.4-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "17be8c24a51b9aa8deb11f91d75b7a09", "sha256": "04348a403dbbec9b1ab9cee7a0e3af1f854ea51689629e616206cef15d28cf6b" }, "downloads": -1, "filename": "pycql-0.0.4.tar.gz", "has_sig": false, "md5_digest": "17be8c24a51b9aa8deb11f91d75b7a09", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22740, "upload_time": "2019-09-11T16:02:52", "url": "https://files.pythonhosted.org/packages/ed/a8/d1fb136734b630d18e0139384f4b75a5e8826012bb2c74c1a5854fa901b2/pycql-0.0.4.tar.gz" } ], "0.0.6": [ { "comment_text": "", "digests": { "md5": "e9d89588061357e5f8969b46bb30c314", "sha256": "e9aef626d0f41e851d9447996cf798bb2a46a49554af3b1bebeeab86c3caa802" }, "downloads": -1, "filename": "pycql-0.0.6-py3-none-any.whl", "has_sig": false, "md5_digest": "e9d89588061357e5f8969b46bb30c314", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 29194, "upload_time": "2019-09-19T08:55:16", "url": "https://files.pythonhosted.org/packages/c4/fa/c48c45e3994c4b490c616526b7404ed60b81d3c15d6b0ec9e4f24e9cd073/pycql-0.0.6-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c488625cafd91a9134ef02c862cf5330", "sha256": "c955fc148e1b7588d1b624a871d1464c9d110fe5597dcb76a54edbde9ba10b87" }, "downloads": -1, "filename": "pycql-0.0.6.tar.gz", "has_sig": false, "md5_digest": "c488625cafd91a9134ef02c862cf5330", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21175, "upload_time": "2019-09-19T08:55:18", "url": "https://files.pythonhosted.org/packages/d2/4a/b561a112f9be0fd943f2abc8cd75e4d0f415e52e125d4fcf92501ac046e3/pycql-0.0.6.tar.gz" } ], "0.0.7": [ { "comment_text": "", "digests": { "md5": "088a1d2fba00e64881628defe76dd079", "sha256": "bb70f49477c5ee2886afa3999e778415f629b94abc9a51d53a902b39ab6e856a" }, "downloads": -1, "filename": "pycql-0.0.7-py3-none-any.whl", "has_sig": false, "md5_digest": "088a1d2fba00e64881628defe76dd079", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 29196, "upload_time": "2019-09-19T09:14:46", "url": "https://files.pythonhosted.org/packages/22/ef/694b90f74f1286d4245883a56850e0eed6c3d27d6a70a7578605853e8d7c/pycql-0.0.7-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "525b879e3b850ccbdd7b7bcb73812d9a", "sha256": "e8ae98ee6731e7f9d93c816f0ca18f66b18cb6e0f6c885d2ba8cfec79f261689" }, "downloads": -1, "filename": "pycql-0.0.7.tar.gz", "has_sig": false, "md5_digest": "525b879e3b850ccbdd7b7bcb73812d9a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21176, "upload_time": "2019-09-19T09:14:48", "url": "https://files.pythonhosted.org/packages/24/93/0a41b850d5d48b5513996330c2b1de2f8620c0acd4abcedc908634d25970/pycql-0.0.7.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "088a1d2fba00e64881628defe76dd079", "sha256": "bb70f49477c5ee2886afa3999e778415f629b94abc9a51d53a902b39ab6e856a" }, "downloads": -1, "filename": "pycql-0.0.7-py3-none-any.whl", "has_sig": false, "md5_digest": "088a1d2fba00e64881628defe76dd079", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 29196, "upload_time": "2019-09-19T09:14:46", "url": "https://files.pythonhosted.org/packages/22/ef/694b90f74f1286d4245883a56850e0eed6c3d27d6a70a7578605853e8d7c/pycql-0.0.7-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "525b879e3b850ccbdd7b7bcb73812d9a", "sha256": "e8ae98ee6731e7f9d93c816f0ca18f66b18cb6e0f6c885d2ba8cfec79f261689" }, "downloads": -1, "filename": "pycql-0.0.7.tar.gz", "has_sig": false, "md5_digest": "525b879e3b850ccbdd7b7bcb73812d9a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21176, "upload_time": "2019-09-19T09:14:48", "url": "https://files.pythonhosted.org/packages/24/93/0a41b850d5d48b5513996330c2b1de2f8620c0acd4abcedc908634d25970/pycql-0.0.7.tar.gz" } ] }