{ "info": { "author": "Artur Karazniewicz", "author_email": "karaznie+pip@protonmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "## ApiQL - a Simple API query language for RESTful services\n\nApiQL is a simple domain language for API consumers to express REST resource filtering criteria in a concise, powerful, URL friendly way.\n\n# Installing\n\nApiQL can be installed and updated with pip:\n\n\n``pip install apiql``\n\n### API query language\n\nApi query language provides set of predicates and expressions allowing API providers and consumers to build complex resource queries. While ApiQL syntax is in largely SQL inspired, it is designed to be technology agnostic. ApiQL is translated into abstract criteria tree and can be transformed into backend specific functionality. \n\n#### ApiQL query syntax\n\nApiQL query is set of basic predicates which may be then composed using conjunctions (`and`) or disjunctions (`or`) into expressions.\n\nFor example, let's assume we have REST API exposing movie information:\n\n`wget -q -O - 'http://awso.me/api/movies'`\n\n```javascript\n[{\n \"title\": \"Monty Python and the Holy Grail\",\n \"release_year\": 1975,\n \"original_title\": \"Monty Python and the Holy Grail\"\n \"created_datetime\": \"2019-05-01T09:17:06.527181+00:00\",\n \"external_id\": \"762\",\n \"genres\": [{\"id\" : 1, \"name\": \"Comedy\"}, {\"id\" : 2, \"name\": \"Adventure\"}, {\"id\" : 2, \"name\": \"Fantasy\"}],\n \"id\": 1,\n \"plot\": \"King Arthur, accompanied by his squire, recruits his Knights of the Round Table [...]\",\n \"rating\": 6.0, \n \"source\": \"tmdb\",\n \"ignored\": false\n},\n\n...\n\n]\n```\n\n#### ApiQL query basics\n\nIn its most basic form, ApiQL query is just single predicate:\n\n``wget -q -O - 'http://awso.me/api/movies?filter=title==\"Monty Python and the Holy Grail\"'``\n\nwhich will filter only \"Monty Python and the Holy Grails\" resources. (whole ApiQL query is contained within single URL query param; note that ApiQL uses `==` operator for equality, not ``=``).\n\n*Note: ApiQL was designed to be as much URL friendly as possible, however all ApiQL queries, should be URL-encoded. In this document we use ``wget`` as it URL-encodes all URLs by default; ``curl`` has little bit more arcane syntax in this case: ``curl -G --data-urlencode \"filter=[my ApiQL query]\" http://awso.me/api/movies``*\n\n##### ApiQL predicates\n\nIn reality API consumers require much more than simple ``==`` predicate. ApiQL supports following set of predicates:\n* `==`, \n* `!=`, \n* `>`, \n* `>=`, \n* `<`, \n* `<=`, \n* `like` - equivalent SQL LIKE operator, however You don't need explicitly add `%`, \n* `ilike`- case insensitive version of `like`, \n* `notlike` - equivalent SQL NOT LIKE operator,\n* `notilike` - case insensitive version on `notlike`, \n* `startswith` - equivalent to SQL STARTS WITH operator, \n* `istartswith` - case insensitive version of `startswith`, \n* `endswith` - equivalent to SQL ENDS WITH, \n* `iendswith` - case insensitive version of `iendswith`, \n* `contains` - alias to `like`, \n* `notcontains` - alias to `notlike`, \n* `icontains` - case insensitive version of `contains`,\n* `inotcontains` - case insensitive version of `notcontains`,\n* `in` - equivalent to SQL IN operator,\n* `notin` equivalent to SQL NOT IN operator. \n\nFor example, query:\n\n``wget -q -O - 'http://awso.me/api/movies?filter=title ilike \"Holy\"'``\n\nwill return all movies matching \"Holy\": \"Monty Python and the Holy Grails\", \"Holy Money\" and possibly bunch of other filcks matching \"Holy\".\n\nand query:\n\n``wget -q -O - 'http://awso.me/api/movies?filter=release_year>=1975'``\n\nwill return all movies released in 1975 or later.\n\n##### Query literals\n\nLiterals are the values, things that can be on the right hand side of predicate. So far we have seen strings (\"Holy\") and numeric literals (1975). ApiQL support bunch of other literals too:\n\n* Strings - all string literals are unicode and are following the same rules like JSON string literals. ApiQL strings are always double-quoted (for example, this is a string: \"This is a string\", this however: 'not a string' *is not*), and escaped (\"The movie: \\\\\"The Movie\\\\\"\").\n* Numbers - are basically integers and floats: `release_year != 2003` or `rating > 3.3` or even `rating > -1.6E-35`.\n* Boolean - `true` and `false` are translated into platform specific booleans. Example usage: `ingored != false`\n* Nil - special `null` literal is translated into platform specific literal, for example: `genres != null`\n* Datetime - literal representing datetime: `created_datetime >= datetime(\"2019-05-01T08:00:00.527181+00:00\")`. Out of the box ApiQL supports ISO-8601 datetime formats (however, this behavior can be customized). \n* Tuples - represents series of values in `in` and `notin` clauses: `release_year notin (1975, 2011)`. Tuples can contain coma separated list of other literals: `release_date in (flase, null, datetime(\"datetime(\"1975-01-01T00:00:00.000000+00:00\"))`\n\n#### Combining queries\n\nQueries can be combined into more complicated expressions by grouping atomic predicates (separated by `;`).\n\nFor example:\n\n``wget -q -O - 'http://awso.me/api/movies?filter=title ilike \"Holy\";release_year>1975;ignored!=null'``\n\npredicates in this query are interpreted as `conjunction` (`and`), returning all movies with `title` matching \"Holy\" *and* released after 1975 which are not marked as `ignored`.\n\n#### Logical expressions\n\nApiQL supports logical `conjunctions` (`and`) and `disjunctions` (`or`); both of them can nest predicates: `and(criteria(;criteria)*)`and`or(criteria(;criteria)*)`\n\nQuery below, is equivalent to the previous one:\n\n``wget -q -O - 'http://awso.me/api/movies?filter=and(title ilike \"Holy\";release_year > 1975;ignored != true)'``\n\nThis one, however:\n\n``wget -q -O -'http://awso.me/api/movies?filter=or(title ilike \"Holy\";release_year>1975;ignored!=true)'``\n\nwill return all movies with titles matching \"Holy\" *or* released after 1975 *or* not ignored.\n\nConjunctions and Disjunctions can be nested. Let's say we want to filter movies with rating greater than 7 or source is \"IMDB\", however we would like to filter only not-ignored resources:\n\n``wget -q -O - 'http://awso.me/api/movies?filter=and(or(rating>7;source=\"IMDB\");ignored!=flase)'``\n\n### Parsing ApiQL queries\n\nSo far, so good. Now, how ApiQL Queries can actually be interpreted by API data store? ApiQL queries are internally translated into python data structure (syntax tree) represented by`Crtieria` class. \n\n``Criteria`` along with `Predicate`, `Conjunction` and `Disjunction` fully represents parsed query tree.\n\n`Criteria` class aggregates list of `Criterion`. \n\n`Criterion` just abstract atomic criteria element; it's either:\n* simple `Predicate` an atomic logical expression (for example \n`Predicate('title', '==', 'Apocalypse Now')` for query `title==\"Apocalypse Now\"`)\n* `Conjunction` which again is just logical ``and`` operator with group of predicates `Conjunction([Predicate('title','==', 'Apocalypse Now'),Predicate('release_year','>', 1975)])` for query `and(title==\"Apocalypse Now\";release_year>1975)`\n* `Disjunction` - logical ``or`` operator\n\nParsing ApiQL query with python:\n\n```python\nimport apiql.parser as parser\nfrom apiql.criteria import Criteria, Conjunction, Disjunction, Predicate\n\n# ...\n\nparsed_criteria = parser.parse('and(title like \"Monty\";genres == null;ignored!=false;release_year<=1975)')\nsyntax_tree = Criteria(\n [Conjunction([\n Predicate('title', 'like', 'Monty'),\n Predicate('genres', '==', None),\n Predicate('ignored', '!=', False),\n Predicate('release_year', '<=', 1975)\n ])]\n)\n\n# parsed_criteria is equal to syntax_tree\n\nassertEqual(syntax_tree, parsed_criteria)\n\n``` \n### A Tour of queries\n\nNow we can actually use ApiQL to filter resources. Out of the box ApiQL provides SQLAlchemy ORM integration (it can be, however integrated with other backends). This section showcases all basic query examples. Complete list of query capabilities can be found in the test suite.\n\nAll examples will use this sample data model:\n\n```python\nBase = declarative_base()\n\nclass Genre(Base):\n __tablename__ = 'genre'\n\n id = Column(Integer, primary_key=True)\n name = Column(String)\n genre_id = Column(Integer)\n movie_id = Column(Integer, ForeignKey('movie.id'), nullable=True)\n\nclass Movie(Base):\n __tablename__ = 'movie'\n\n id = Column(Integer, primary_key=True)\n title = Column(String)\n original_title = Column(String)\n release_year = Column(Integer)\n source = Column(String)\n rating = Column(String)\n created_datetime = Column(DateTime, default=datetime.utcnow)\n\tgenres = relationship('Genre', cascade=\"all\", backref=\"movie\", lazy=True)\n\ndrama = Genre(name=\"Drama\", genre_id=1)\nscifi = Genre(name=\"Sci-Fi\", genre_id=2)\nwar = Genre(name=\"War\", genre_id=3)\nadventure = Genre(name=\"Adventure\", genre_id=4)\ncomedy = Genre(name=\"Comedy\", genre_id=5)\n\nmonty_python = Movie(title=\"Monty Python and the Holy Grail\", release_year=1975, source=\"IMDB\", rating=\"8\",\n genres=[comedy, adventure])\n\njurassic_park = Movie(title=\"Jurassic Park\", release_year=1993, source='IMDB', rating=\"9\",\n genres=[adventure, scifi])\n\napocalypse_now = Movie(title=\"Apocalypse Now\", release_year=1979, source=\"TMDB\", rating=\"9\",\n original_title=\"Apocalypse Now, The\", genres=[drama, war])\n\ngosford_park = Movie(title=\"Gosford Park\", release_year=2001, source='IMDB', rating=\"7\", genres=[drama])\n\nsession.add(monty_python)\nsession.add(jurassic_park)\nsession.add(apocalypse_now)\nsession.add(gosford_park)\n```\n\nA main entry point to SQLAlchemy integration is ``with_criteria`` extension method, which extends plain SQLAlchemy ``Query`` object with ApiQL capabilities. ``with_criteria`` is following basic SQLAlchemy conventions, so it can be freely used with native ``filter_by`` or ``filter`` functions. \n\nFollowing examples show ApiQL queries and their native SQLAlchemy representations.\n \n#### Simple conjunction criteria\n\n```python\nfrom apiql.backends.sqlalchemy.orm import with_criteria\n\nactual = session.query(Movie).with_criteria('and(rating==\"8\";release_year==1975)')\n\n# is equivalent to\n\nexpected = session.query(Movie).filter(\n and_(\n Movie.rating == 8,\n Movie.release_year == 1975\n )\n)\n```\n\n#### Simple disjunction criteria\n\n```python\nfrom apiql.backends.sqlalchemy.orm import with_criteria\n\nactual = session.query(Movie).with_criteria('or(rating==\"8\";release_year==1993;source==\"TMDB\")')\n\n# is equivalent to\n\nexpected = session.query(Movie).filter(\n or_(\n Movie.rating == 8,\n Movie.release_year == 1993,\n Movie.source == 'TMDB'\n )\n)\n\n```\n\n#### `<` and `>` predicates\n\n```python\n\nactual = session.query(Movie).with_criteria('and(release_year > 1975; release_year < 2001)')\n\n# is equivalent to\n\nexpected = session.query(Movie).filter(\n and_(\n Movie.release_year > 1975,\n Movie.release_year < 2001\n )\n)\n```\n\n#### `like` and `ilike` predicates\n\n```python\nactual = session.query(Movie).with_criteria('or(title like \"THE\"; original_title ilike \"THE\")')\n\n# is equivalent to\n\nexpected = session.query(Movie).filter(\n or_(\n Movie.title.like('%THE%'),\n Movie.original_title.ilike(\"%THE%\")\n )\n)\n```\n\n#### `notlike` predicate\n\n```python\nactual = session.query(Movie).with_criteria('and(title notlike \"the\"; release_year > 1990)')\n\n# is equivalent to\n\nexpected = session.query(Movie).filter(\n and_(\n Movie.title.notlike('%the%'),\n Movie.release_year > 1990\n )\n)\n```\n\n#### `in` and `notin` predicate\n\n```python\nactual = session.query(Movie).with_criteria('release_year in (1979, 2001))')\n\n# is equivalent to\n\nexpected = session.query(Movie).filter(\n Movie.release_year.in_((1979, 2001))\n)\n```\nand \n```python\nactual = session.query(Movie).with_criteria('release_year notin (1979, 2001))')\n\n# is equivalent to\n\nexpected = session.query(Movie).filter(\n Movie.release_year.notin_((1979, 2001))\n)\n```\n\n#### ``IS NULL`` and ``IS NOT NULL`` predicates\n\n```python\nactual = session.query(Movie).with_criteria('original_title == null)')\n\n# is equivalent to\n\nexpected = session.query(Movie).filter(\n Movie.original_title.is_(None)\n)\n```\nand\n```python\nactual = session.query(Movie).with_criteria('original_title != null)')\n\n# is equivalent to\n\nexpected = session.query(Movie).filter(\n Movie.original_title.isnot(None)\n)\n```\n\n#### `startswith` and `endswith` predicates\n\nFollowing queries are equivalents\n\n```python\nactual = session.query(Movie).with_criteria('title startswith \"The\")')\n\n# is equivalent to\n\nexpected = session.query(Movie).filter(\n Movie.title.startswith(\"The\")\n)\n```\nand\n```python\nactual = session.query(Movie).with_criteria('title endswith \"Park\")')\n\n# is equivalent to\n\nexpected = session.query(Movie).filter(\n Movie.title.endswith(\"Park\")\n)\n```\n\n#### `contains` predicate\n\n```python\nactual = session.query(Movie).with_criteria('or(original_title contains \"The\";title contains \"the\")')\n\n# is equivalent to\n\nexpected = session.query(Movie).filter(\n or_(\n Movie.original_title.contains('The'),\n Movie.title.contains('the')\n )\n)\n```\n\n#### `datetime` literals\n\nFollowing queries are equivalents\n```python\nnow = datetime.datetime.now().isoformat()\n\nactual = session.query(Movie).with_criteria('created_datetime=3.5", "summary": "A simple API Query Language", "version": "0.12.6" }, "last_serial": 5220494, "releases": { "0.12.4": [ { "comment_text": "", "digests": { "md5": "0ab9cf496d432621b8298bbe0b503134", "sha256": "5a330e4e5b7fb19949cf95f985fe774a7ea335e93f2716e7b8cbd66a88ac68b7" }, "downloads": -1, "filename": "apiql-0.12.4.tar.gz", "has_sig": false, "md5_digest": "0ab9cf496d432621b8298bbe0b503134", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 36937, "upload_time": "2019-05-02T06:36:07", "url": "https://files.pythonhosted.org/packages/39/f7/d17ef9a1e0f7463fd03463a0cdf46583a52671c7070a89fe3a16fa892977/apiql-0.12.4.tar.gz" } ], "0.12.5": [ { "comment_text": "", "digests": { "md5": "60e3b26f0d1adb1a029d0f7e5ac7fb47", "sha256": "29300763c7211be1f1a85491094170d12dbcdffc70899cf4a9e4e7276de69127" }, "downloads": -1, "filename": "apiql-0.12.5.tar.gz", "has_sig": false, "md5_digest": "60e3b26f0d1adb1a029d0f7e5ac7fb47", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 36500, "upload_time": "2019-05-03T06:07:41", "url": "https://files.pythonhosted.org/packages/c3/db/89a82cc94c17173d6872e1f28e1e1583f38662163c0d3c42a2c34c8f6e30/apiql-0.12.5.tar.gz" } ], "0.12.6": [ { "comment_text": "", "digests": { "md5": "23962a46abc4711d6a81c137586cc6bd", "sha256": "ee1b1ca29271aace6a49f0f9ecc07d7eb9347ab93ff68374066b1d53d93f13a5" }, "downloads": -1, "filename": "apiql-0.12.6.tar.gz", "has_sig": false, "md5_digest": "23962a46abc4711d6a81c137586cc6bd", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 36473, "upload_time": "2019-05-03T06:19:07", "url": "https://files.pythonhosted.org/packages/f0/e6/22b05eeecd0f0db7b77d9a74de75479c2ed520e77edd918934184f77d3d3/apiql-0.12.6.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "23962a46abc4711d6a81c137586cc6bd", "sha256": "ee1b1ca29271aace6a49f0f9ecc07d7eb9347ab93ff68374066b1d53d93f13a5" }, "downloads": -1, "filename": "apiql-0.12.6.tar.gz", "has_sig": false, "md5_digest": "23962a46abc4711d6a81c137586cc6bd", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 36473, "upload_time": "2019-05-03T06:19:07", "url": "https://files.pythonhosted.org/packages/f0/e6/22b05eeecd0f0db7b77d9a74de75479c2ed520e77edd918934184f77d3d3/apiql-0.12.6.tar.gz" } ] }