# RQLAlchemy
[![Build Status](https://github.com/pjwerneck/rqlalchemy/actions/workflows/pytest.yml/badge.svg?branch=develop)](https://github.com/pjwerneck/rqlalchemy/actions/workflows/pytest.yml)
## Resource Query Language extension for SQLAlchemy
**Overview**
Resource Query Language (RQL) is a query language designed for use in URIs, with object-style data structures.
`rqlalchemy` is an RQL extension for SQLAlchemy, making it easy to expose SQLAlchemy tables or models as an HTTP API endpoint and perform complex queries using only query string parameters.
**Installing**
```bash
pip install rqlalchemy
```
**Usage**
Support RQL queries in your application by using the `select()` construct provided by RQLAlchemy. After creating the selectable, use the `rql()` method to apply the RQL query string, and then use the `execute()` method with the session to retrieve the results.
For example, in a Flask HTTP API with a users collection endpoint querying the `User` model:
```python
from urllib.parse import unquote
from flask import request
from rqlalchemy import select
@app.route('/users')
def get_users_collection():
qs = unquote(request.query_string.decode(request.charset))
users = select(User).rql(qs).execute(session)
return render_response(users)
```
The `.execute()` method handles the session and adjusts the results accordingly, returning scalars, lists of dicts, or a single scalar result when appropriate. There's no need to use `session.execute()` or `session.scalars()` directly unless you want to handle the results yourself.
**Pagination**
RQLAlchemy offers limit/offset pagination with the `rql_paginate()` method, returning the requested page, RQL expressions for previous and next pages if available, and the total number of items.
```python
from urllib.parse import unquote
from flask import request
from rqlalchemy import select
@app.route('/users')
def get_users_collection():
qs = unquote(request.query_string.decode(request.charset))
res = select(User).rql(qs).rql_paginate(session)
response = {
"data": res.page,
"total": res.total,
}
if res.previous_page:
response["previous"] = '/users?' + res.previous_page
if res.next_page:
response["next"] = '/users?' + res.next_page
return render_response(response)
```
Pagination requires a limit, as a `RQLSelect._rql_default_limit` value, a query string `limit(x)`, or the `limit` parameter to the `rql()` method. Calling `rql_paginate()` without a limit will raise `RQLQueryError`.
**Reference Table**
| RQL | SQLAlchemy equivalent | Observation |
|-------------------------|----------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|
| QUERYING | | |
| select(a,b,c,...) | select(Model.a, Model.b, Model.c,...) | |
| values(a) | [o.a for o in query.from_self(a)] | |
| limit(count,start?) | .limit(count).offset(start) | |
| sort(attr1) | .order_by(attr) | |
| sort(-attr1) | .order_by(attr.desc()) | |
| distinct() | .distinct() | |
| first() | .limit(1) | |
| one() | [query.one()] | |
| FILTERING | | |
| eq(attr,value) | .where(Model.attr == value) | |
| ne(attr,value) | .where(Model.attr != value) | |
| lt(attr,value) | .where(Model.attr < value) | |
| le(attr,value) | .where(Model.attr <= value) | |
| gt(attr,value) | .where(Model.attr > value) | |
| ge(attr,value) | .where(Model.attr >= value) | |
| in(attr,value) | .where(Model.attr.in_(value) | |
| out(attr,value) | .where(not_(Model.attr.in_(value))) | |
| contains(attr,value) | .where(Model.contains(value)) | Produces a LIKE expression when querying against a string, or an IN expression when querying against an iterable relationship |
| excludes(attr,value) | .where(not_(Model.contains(value))) | See above. |
| and(expr1,expr2,...) | .where(and_(expr1, expr2, ...)) | |
| or(expr1,expr2,...) | .where(or_(expr1, expr2, ...)) | |
| AGGREGATING | | All aggregation functions return scalar results. |
| aggregate(a,b\(c\),...) | select(Model.a, func.b(Model.c)).group_by(Model.a) | |
| sum(attr) | select(func.sum(Model.attr)) | |
| mean(attr) | select(func.avg(Model.attr)) | |
| max(attr) | select(func.max(Model.attr)) | |
| min(attr) | select(func.min(Model.attr)) | |
| count() | select(func.count()) | |
Raw data
{
"_id": null,
"home_page": "https://github.com/pjwerneck/rqlalchemy",
"name": "rqlalchemy",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.8,<4.0",
"maintainer_email": "",
"keywords": "sqlachemy,sql,rql,querying,httpapi",
"author": "Pedro Werneck",
"author_email": "pjwerneck@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/59/c5/ac566b53f835898c3219e1f1bd610ddbd66e444ec24231dd3c1676c9388d/rqlalchemy-0.5.0.tar.gz",
"platform": null,
"description": "# RQLAlchemy\n\n[![Build Status](https://github.com/pjwerneck/rqlalchemy/actions/workflows/pytest.yml/badge.svg?branch=develop)](https://github.com/pjwerneck/rqlalchemy/actions/workflows/pytest.yml)\n\n## Resource Query Language extension for SQLAlchemy\n\n**Overview**\n\nResource Query Language (RQL) is a query language designed for use in URIs, with object-style data structures.\n\n`rqlalchemy` is an RQL extension for SQLAlchemy, making it easy to expose SQLAlchemy tables or models as an HTTP API endpoint and perform complex queries using only query string parameters.\n\n**Installing**\n\n```bash\npip install rqlalchemy\n```\n\n**Usage**\n\nSupport RQL queries in your application by using the `select()` construct provided by RQLAlchemy. After creating the selectable, use the `rql()` method to apply the RQL query string, and then use the `execute()` method with the session to retrieve the results.\n\nFor example, in a Flask HTTP API with a users collection endpoint querying the `User` model:\n\n```python\nfrom urllib.parse import unquote\nfrom flask import request\n\nfrom rqlalchemy import select\n\n@app.route('/users')\ndef get_users_collection():\n qs = unquote(request.query_string.decode(request.charset))\n users = select(User).rql(qs).execute(session)\n\n return render_response(users)\n```\n\nThe `.execute()` method handles the session and adjusts the results accordingly, returning scalars, lists of dicts, or a single scalar result when appropriate. There's no need to use `session.execute()` or `session.scalars()` directly unless you want to handle the results yourself.\n\n**Pagination**\n\nRQLAlchemy offers limit/offset pagination with the `rql_paginate()` method, returning the requested page, RQL expressions for previous and next pages if available, and the total number of items.\n\n```python\nfrom urllib.parse import unquote\nfrom flask import request\n\nfrom rqlalchemy import select\n\n@app.route('/users')\ndef get_users_collection():\n qs = unquote(request.query_string.decode(request.charset))\n res = select(User).rql(qs).rql_paginate(session)\n\n response = {\n \"data\": res.page,\n \"total\": res.total,\n }\n\n if res.previous_page:\n response[\"previous\"] = '/users?' + res.previous_page\n\n if res.next_page:\n response[\"next\"] = '/users?' + res.next_page\n\n return render_response(response)\n```\n\nPagination requires a limit, as a `RQLSelect._rql_default_limit` value, a query string `limit(x)`, or the `limit` parameter to the `rql()` method. Calling `rql_paginate()` without a limit will raise `RQLQueryError`.\n\n**Reference Table**\n\n| RQL | SQLAlchemy equivalent | Observation |\n|-------------------------|----------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|\n| QUERYING | | |\n| select(a,b,c,...) | select(Model.a, Model.b, Model.c,...) | |\n| values(a) | [o.a for o in query.from_self(a)] | |\n| limit(count,start?) | .limit(count).offset(start) | |\n| sort(attr1) | .order_by(attr) | |\n| sort(-attr1) | .order_by(attr.desc()) | |\n| distinct() | .distinct() | |\n| first() | .limit(1) | |\n| one() | [query.one()] | |\n| FILTERING | | |\n| eq(attr,value) | .where(Model.attr == value) | |\n| ne(attr,value) | .where(Model.attr != value) | |\n| lt(attr,value) | .where(Model.attr < value) | |\n| le(attr,value) | .where(Model.attr <= value) | |\n| gt(attr,value) | .where(Model.attr > value) | |\n| ge(attr,value) | .where(Model.attr >= value) | |\n| in(attr,value) | .where(Model.attr.in_(value) | |\n| out(attr,value) | .where(not_(Model.attr.in_(value))) | |\n| contains(attr,value) | .where(Model.contains(value)) | Produces a LIKE expression when querying against a string, or an IN expression when querying against an iterable relationship |\n| excludes(attr,value) | .where(not_(Model.contains(value))) | See above. |\n| and(expr1,expr2,...) | .where(and_(expr1, expr2, ...)) | |\n| or(expr1,expr2,...) | .where(or_(expr1, expr2, ...)) | |\n| AGGREGATING | | All aggregation functions return scalar results. |\n| aggregate(a,b\\(c\\),...) | select(Model.a, func.b(Model.c)).group_by(Model.a) | |\n| sum(attr) | select(func.sum(Model.attr)) | |\n| mean(attr) | select(func.avg(Model.attr)) | |\n| max(attr) | select(func.max(Model.attr)) | |\n| min(attr) | select(func.min(Model.attr)) | |\n| count() | select(func.count()) | |\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "\"Resource Query Language for SQLAlchemy\"",
"version": "0.5.0",
"project_urls": {
"Homepage": "https://github.com/pjwerneck/rqlalchemy",
"Repository": "https://github.com/pjwerneck/rqlalchemy"
},
"split_keywords": [
"sqlachemy",
"sql",
"rql",
"querying",
"httpapi"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "e935d158e7c66431db200b5cf635996982aba331262e863877b5b05102c6da35",
"md5": "12ecd1bf094e6cd15c567e10f8cfb5d6",
"sha256": "d9d215eb02ca7fc6c2c911235ac2f5111d6d406728e18be923bc685835decb59"
},
"downloads": -1,
"filename": "rqlalchemy-0.5.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "12ecd1bf094e6cd15c567e10f8cfb5d6",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8,<4.0",
"size": 7328,
"upload_time": "2023-12-09T18:31:02",
"upload_time_iso_8601": "2023-12-09T18:31:02.368258Z",
"url": "https://files.pythonhosted.org/packages/e9/35/d158e7c66431db200b5cf635996982aba331262e863877b5b05102c6da35/rqlalchemy-0.5.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "59c5ac566b53f835898c3219e1f1bd610ddbd66e444ec24231dd3c1676c9388d",
"md5": "5e1ab1807cd8011c2a85a868bc5e39c2",
"sha256": "9870eda54b585eb6abf0d994949a349457bd64541838584d2eb245194caa423d"
},
"downloads": -1,
"filename": "rqlalchemy-0.5.0.tar.gz",
"has_sig": false,
"md5_digest": "5e1ab1807cd8011c2a85a868bc5e39c2",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8,<4.0",
"size": 7654,
"upload_time": "2023-12-09T18:31:04",
"upload_time_iso_8601": "2023-12-09T18:31:04.492260Z",
"url": "https://files.pythonhosted.org/packages/59/c5/ac566b53f835898c3219e1f1bd610ddbd66e444ec24231dd3c1676c9388d/rqlalchemy-0.5.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-12-09 18:31:04",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "pjwerneck",
"github_project": "rqlalchemy",
"travis_ci": true,
"coveralls": false,
"github_actions": true,
"lcname": "rqlalchemy"
}