{ "info": { "author": "David Tulga", "author_email": "davidtulga@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3" ], "description": "# Rest-o-matic\nAutomatic JSON-based API generator, including a SQL Query Compositor and WSGI Endpoint Router\n\n[![CircleCI](https://circleci.com/gh/dtulga/restomatic/tree/master.svg?style=svg)](https://circleci.com/gh/dtulga/restomatic/tree/master)\n[![codecov](https://codecov.io/gh/dtulga/restomatic/branch/master/graph/badge.svg)](https://codecov.io/gh/dtulga/restomatic)\n\n## Warning: This software is in alpha, and API or function signatures may change before full release.\n\n## Usage\nThis package includes three primary components:\n\n### Rest-o-matic Endpoints / API Description\nThis system generates JSON endpoints automatically for tables utilizing the included\nSQL Query Compositor and WSGI Endpoint router\n\nCreate a new set of endpoints with the command:\n```\nfrom restomatic.json_sql_compositor import SQLiteDB\nfrom restomatic.wsgi_endpoint_router import EndpointRouter\nfrom restomatic.endpoint import register_restomatic_endpoint\n\ntable_mappers = {\n 'table_name': ['column1', 'column2', ...],\n ...\n}\n\ndb = SQLiteDB(database_filename, table_mappers)\n\nrouter = EndpointRouter()\n\nregister_restomatic_endpoint(router, db, 'table_name', ['GET', 'PUT', 'POST', 'PATCH', 'DELETE'])\n...\n```\nAny set of methods is valid to allow. Methods not provided will return 405 Method Not Allowed errors\nif disallow_other_methods is set to True.\n\n### GET (returns 200 if found, 404 if no match)\n```\nGET one: /table/1\n```\nReturns: The requested row as a JSON object (dictionary)\n\n### POST (returns 201 for create, 200 for search, on success)\nThis endpoint creates a new row (or multiple new rows) \nAlso supports a get-like search (but without the limits on uri size/format)\n```\nPOST one: /table\n body: {...}\nor POST many: /table\n body: [{...}, {...}]\nor POST-based search: /table/search (returns 200 if found, 404 if no matches)\n body: {'where': [...search criteria...]}\n Optionally, can also include in addition to the where clause:\n 'limit': 1, 'offset': 2, 'order_by': ['column_1', {'column': 'column_2', 'direction': 'ASC'}]\n```\nThe IDs of the created instances are returned as well.\n\n### PUT (returns 200 on success)\nThis endpoint can create or update the given rows\n```\nPUT one: /table\n body: {...} (if ID specified, update, otherwise create)\nor PUT many: /table\n body: [{...}, {...}] (if ID specified, update, otherwise create)\n```\n\n### PATCH (returns 200 on success)\nThis endpoint updates the given row or based on the given where condition\n```\nPATCH one: /table/1\n body: {...}\nor PATCH many: /table/where\n body: {'where': [...search criteria...], 'set': {...}}\n```\n\n### DELETE (returns 200 on success)\nThis endpoint deletes the given row or based on the given where condition \n```\nDELETE one: /table/1\nor DELETE many: /table/where\n body: {'where': [...search criteria...]}\n```\n\n### Search Criteria Format\nThe search criteria is a list which contains two or three elements,\nthe column, an operator to compare, and a value (unless the operator does not need a value.)\nIn addition, search criteria can be combined with 'and' or 'or' by adding all\ndesired comparison statements (two or three element lists) into a list with a dictionary with\neither 'and' or 'or' as the key. (See example below)\n\nSearch Criteria Examples:\n```\n['id', 'isnotnull']\n['id', 'eq', 1]\n['value', 'lt', 1.3]\n['id', 'gte', 2]\n['description', 'like', '%ABC%']\n['value', 'isnull']\n['description', 'in', ['test 1', 'test 5']]\n['description', 'not_in', ['test 1', 'test 2', 'test 3']]\n```\n\nOperators should be one of:\n```\n'eq', '=', '=='\n'lt', '<'\n'gt', '>'\n'lte', '<='\n'gte', '>='\n'in'\n'notin', 'not_in'\n'like'\n'isnull', 'is_null'\n'isnotnull', 'is_not_null'\n```\n(Operators in each row are equivalent)\n\nExample Search:\n```\nPOST /test/search\n{'where': ['id', 'lte', 3]}\n```\n\nWith an AND statement:\n```\nPOST /test/search\n{\n 'where': {\n 'and': [\n ['id', 'lte', 3],\n ['id', '>', 1]\n ]\n }\n}\n```\n\n### SQL Query Compositor / SQLite DB Interface\nThis provides a convenient and secure way of interacting with a local SQLite DB,\nallowing one to construct SQL queries through python functions, rather than by\nstring manipulation. It also supports parameter binding, to prevent the most common\nkind of SQL injection attacks.\n\nTo utilize the query compositor, first create a database reference:\n\n```\ndb = SQLiteDB('test.db', table_mappers)\n```\n\nThis will create the database file (but not the tables) if it does not exist.\nYou can also provide ':memory:' for an in-memory-only sqlite database.\n\nThen one can perform select, update, insert, and delete statements as such:\n\n```\ndb.select_all('test').where({'and': [['id', 'gte', 2], ['id', 'lt', 3]]}).one() == (2, 'test 2', 1.5)\ndb.select_all('test').where(['id', 'isnotnull']).all_mapped() == [{'id': 1, 'description': 'test 1', 'value': 0.5}]\ndb.select_all('test').count().scalar() == 2\ndb.select_all('test').where(['id', 'gte', 3]).one_or_none_mapped() is None\ndb.insert('test', ('description', 'value')).values(('test 2', 1.5))\ndb.insert_mapped('test', ({'description': 'test 3', 'value': 3.0}, {'description': 'test 4', 'value': 4.4}))\ndb.delete('test').where(('id', 'eq', 4)).run()\ndb.update_mapped('test', {'value': 2.0}).where(('id', 'eq', 1)).run()\ndb.commit() # Required for persisting any transactional changes.\n```\n\nNote that generally the _mapped() forms will return a JSON-like dict, the plain forms will return tuples in\neither the table order (for select_all) or the columns provided order (if columns are specified). In addition,\nscalar() will return a the first value only, such as for count queries. In addition, all() functions return\nlists of results, while one() returns only one (and will raise an error if not found), and one_or_none()\nreturns either one result or None if not found.\n\nAlso note that the format used by the filter functions (such as where and order_by) is the same as the API\nformat described above and utilized by the restomatic endpoints.\n\nSee the test file for a full treatment on all possible usages and return values/formats.\n\n### WSGI Endpoint Router\n\nThis provides a lightweight and convenient way of routing multiple endpoints based on request method\n(GET, PUT, POST, PATCH, DELETE) and the requested uri (such as /index or /table). It also supports\nboth exact-match and prefix-match for uris, and auto-parses/returns formats of plain text, JSON, and\nform-input, html-output.\n\nIt can be instantiated with:\n```\nfrom restomatic.wsgi_endpoint_router import EndpointRouter\n\nrouter = EndpointRouter(default_in_format='plain', default_out_format='plain')\n```\nOptionally specifying the server default formats.\n\nEndpoints are then registered as:\n```\nrouter.register_endpoint(endpt_index, exact='/', method='GET')\nrouter.register_endpoint(endpt_get_json, prefix='/get_json', method='GET', out_format='json')\nrouter.register_endpoint(endpt_patch_json, exact='/patch_json', method='PATCH', in_format='json', out_format='json')\n```\nWith each endpoint definition specifying first the function to handle the endpoint call, and\nallowed to specify either an exact or prefix match, an HTTP method to handle,\nand in and out format to automatically handle.\n\nEndpoint functions have this signature:\n```\ndef endpoint_index(request):\n ...create data...\n return data\n```\nThe return value can be either of:\n```\nresponse_data\nresponse_data, status_code\nresponse_data, status_code, additional_headers\n```\nWhere additional headers can either be in the format:\n```\n{'X-Example': 'Header-Value'}\n```\nor\n```\n[('Content-Type', 'text/plain')]\n```\nIf the content-type header is provided, it can be used to override the default based on the endpoint definition.\n\nSee the test file for a full treatment on all possible usages and return values/formats.\n\n### Advanced Usage (Pre-/post-processing, etc.)\n\nIn addition, you can add pre- and post- processors, to perform validation of data inputs, and also for custom type handling.\n\nThese can be added at database connection time, for example:\n\n```\ndb = SQLiteDB('file.db', table_mappers, preprocessors={\n 'table_name': {\n 'description': description_validator,\n 'value': value_preprocessor,\n },\n}, postprocessors={\n 'table_name': {\n 'value': value_postprocessor,\n }\n})\n```\n\nPreprocessors can raise exceptions to indicate invalid data, which will then abort the current SQL query or request.\n\nPostprocessors can format the data in other ways (different than the internal data format), and along with matching\npreprocessors can be used to effectively create custom data types.\n\nThe function signature of both pre- and post-processors should be:\n\n```\ndef value_processor(value, **context):\n ... code here ...\n return value\n```\n\nNote that the function name and first variable name (for the value) are both not important and can be named whatever\nis best for your code. The context variable is to capture current and future context information about where this\npre or postprocessor is being run from. Currently, the context is set only for preprocessors, and has the 'db' set to\nthe db instance it is running from, and the 'mode' variable set to the current mode, either 'INSERT INTO', 'UPDATE',\nor 'WHERE' (for searching/getting).\n\nNote that both processor types should return the new processed value to either be inserted into the database (pre-)\nor returned to the user (post-), which of course can be identical to the inputted value in the case of validators\nor conditional processors.\n\n### Foreign Key Support\n\nSqlite by default does not enforce foreign keys, to enable support, simply set the flag at database connection time:\n\n```\ndb = SQLiteDB('example.db', table_mappers, enable_foreign_key_constraints=True)\n```\n\nThis will then throw a sqlite3.IntegrityError if a foreign key constraint is not satisfied.\n\n## Install\n\nRequires Python 3.6+ with no other external dependencies.\nHowever, a WSGI server will be required, such as uWSGI.\n\nTo use uWSGI, you can install it as such:\n\n```\npip3 install uwsgi\n```\n\nYou may need dependent packages (depending upon the system) such as python3-dev and build-essential\nto install uWSGI, for full instructions see: https://uwsgi-docs.readthedocs.io/en/latest/WSGIquickstart.html\n\nIn addition, to run the unit tests, pytest is required, and pytest-cov recommended.\n\n## Planned Features\n\n* Greater database support, including PostgreSQL / MySQL\n* Support for more types, such as enums and booleans\n* Automatic table creation, plus check constraints (for ranges/positive/etc.)\n* Ability to use decorators, authorization, and logging for better security and customization\n* Support for JOINs and possibly foreign key relationship loading\n* Support for Flask (option to be used instead of the provided WSGI router)\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/dtulga/restomatic", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "restomatic", "package_url": "https://pypi.org/project/restomatic/", "platform": "", "project_url": "https://pypi.org/project/restomatic/", "project_urls": { "Homepage": "https://github.com/dtulga/restomatic" }, "release_url": "https://pypi.org/project/restomatic/0.3.1/", "requires_dist": null, "requires_python": "", "summary": "Automatic JSON-based API generator, including a SQL Query Compositor and WSGI Endpoint Router", "version": "0.3.1" }, "last_serial": 5369602, "releases": { "0.2.1": [ { "comment_text": "", "digests": { "md5": "67b34489b4982457a5a033b8c63a3525", "sha256": "cad7e9c9a373dbacbcbd1f4b0c229043be813aea9b2dc4b464bfb96a7a9b22fb" }, "downloads": -1, "filename": "restomatic-0.2.1-py3-none-any.whl", "has_sig": false, "md5_digest": "67b34489b4982457a5a033b8c63a3525", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 19021, "upload_time": "2019-05-11T17:16:09", "url": "https://files.pythonhosted.org/packages/7e/f6/fd6e4c9499c0a7b5c7e968381c56133ab6c517096a2b308469195d463827/restomatic-0.2.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d52bbd56c1914e42779d31fdd89ced45", "sha256": "ee3095a0a729df723e17fd1ba9d47fa0732456a5013af9216ab2969e20af951b" }, "downloads": -1, "filename": "restomatic-0.2.1.tar.gz", "has_sig": false, "md5_digest": "d52bbd56c1914e42779d31fdd89ced45", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20143, "upload_time": "2019-05-11T17:16:11", "url": "https://files.pythonhosted.org/packages/e9/ab/cd6b9c6a10fc27a2b29c75acda6feb9ee6f6c487f5e0dde492e4e5720c8a/restomatic-0.2.1.tar.gz" } ], "0.3.1": [ { "comment_text": "", "digests": { "md5": "73eb3be604f05d70278d657f363ca921", "sha256": "2c4bebc1efe0e08f42925ed8c4d64f990c4a04f5ff4a865393589f3a6792f32f" }, "downloads": -1, "filename": "restomatic-0.3.1-py3-none-any.whl", "has_sig": false, "md5_digest": "73eb3be604f05d70278d657f363ca921", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 19585, "upload_time": "2019-06-06T23:59:05", "url": "https://files.pythonhosted.org/packages/32/ed/92a09ae05af2e122f63786b305462a3f55a7ecc34a60f775df69c90c3dc8/restomatic-0.3.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "32a792d297bf26a0c648c0d2309ecb3c", "sha256": "d3055071172bee1dac91bb3eac6514854e3ae7b51d8794d3ae1c4a01959d3e4b" }, "downloads": -1, "filename": "restomatic-0.3.1.tar.gz", "has_sig": false, "md5_digest": "32a792d297bf26a0c648c0d2309ecb3c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21159, "upload_time": "2019-06-06T23:59:08", "url": "https://files.pythonhosted.org/packages/a9/4c/d130ae3f7c929a17f4320c9f9d6260a1074f06e666afae9b5c058d881f8c/restomatic-0.3.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "73eb3be604f05d70278d657f363ca921", "sha256": "2c4bebc1efe0e08f42925ed8c4d64f990c4a04f5ff4a865393589f3a6792f32f" }, "downloads": -1, "filename": "restomatic-0.3.1-py3-none-any.whl", "has_sig": false, "md5_digest": "73eb3be604f05d70278d657f363ca921", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 19585, "upload_time": "2019-06-06T23:59:05", "url": "https://files.pythonhosted.org/packages/32/ed/92a09ae05af2e122f63786b305462a3f55a7ecc34a60f775df69c90c3dc8/restomatic-0.3.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "32a792d297bf26a0c648c0d2309ecb3c", "sha256": "d3055071172bee1dac91bb3eac6514854e3ae7b51d8794d3ae1c4a01959d3e4b" }, "downloads": -1, "filename": "restomatic-0.3.1.tar.gz", "has_sig": false, "md5_digest": "32a792d297bf26a0c648c0d2309ecb3c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21159, "upload_time": "2019-06-06T23:59:08", "url": "https://files.pythonhosted.org/packages/a9/4c/d130ae3f7c929a17f4320c9f9d6260a1074f06e666afae9b5c058d881f8c/restomatic-0.3.1.tar.gz" } ] }