{"id":13501471,"url":"https://github.com/alexmojaki/instant_api","last_synced_at":"2025-10-27T08:07:06.898Z","repository":{"id":62571167,"uuid":"261021504","full_name":"alexmojaki/instant_api","owner":"alexmojaki","description":"Instantly create an HTTP API with automatic type conversions, JSON RPC, and a Swagger UI. Just add methods!","archived":false,"fork":false,"pushed_at":"2020-07-02T11:55:53.000Z","size":250,"stargazers_count":133,"open_issues_count":1,"forks_count":6,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-10-17T06:30:37.227Z","etag":null,"topics":["api","flask","http-server","json-rpc","marshmallow","python","swagger-ui"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexmojaki.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-03T21:10:40.000Z","updated_at":"2025-08-30T04:30:24.000Z","dependencies_parsed_at":"2022-11-03T17:15:46.172Z","dependency_job_id":null,"html_url":"https://github.com/alexmojaki/instant_api","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/alexmojaki/instant_api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexmojaki%2Finstant_api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexmojaki%2Finstant_api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexmojaki%2Finstant_api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexmojaki%2Finstant_api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexmojaki","download_url":"https://codeload.github.com/alexmojaki/instant_api/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexmojaki%2Finstant_api/sbom","scorecard":{"id":182207,"data":{"date":"2025-08-11","repo":{"name":"github.com/alexmojaki/instant_api","commit":"b2a81bb108c8f501aa07187e6ce8119a5c5b4fa4"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":0,"reason":"Found 0/17 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 18 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-16T19:04:14.773Z","repository_id":62571167,"created_at":"2025-08-16T19:04:14.773Z","updated_at":"2025-08-16T19:04:14.773Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281235231,"owners_count":26466158,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-27T02:00:05.855Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","flask","http-server","json-rpc","marshmallow","python","swagger-ui"],"created_at":"2024-07-31T22:01:38.694Z","updated_at":"2025-10-27T08:07:06.857Z","avatar_url":"https://github.com/alexmojaki.png","language":"Python","funding_links":[],"categories":["Python","HarmonyOS"],"sub_categories":["Windows Manager"],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"images/tin.png\" width=\"256px\" height=\"256px\" alt=\"logo\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://travis-ci.org/alexmojaki/instant_api\"\u003e\u003cimg src=\"https://travis-ci.org/alexmojaki/instant_api.svg?branch=master\" alt=\"Build Status\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://coveralls.io/github/alexmojaki/instant_api?branch=master\"\u003e\u003cimg src=\"https://coveralls.io/repos/github/alexmojaki/instant_api/badge.svg?branch=master\" alt=\"Coverage Status\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://pypi.python.org/pypi/instant_api\"\u003e\u003cimg src=\"https://img.shields.io/pypi/pyversions/instant_api.svg\" alt=\"Supports Python versions 3.7+\"\u003e\u003c/a\u003e\n    \n# instant_api\n\nInstantly create an HTTP API with automatic type conversions, JSON RPC, and a Swagger UI. All the boring stuff is done for you, so you can focus on the interesting logic while having an awesome API. Just add methods!\n\n  * [Installation](#installation)\n  * [Basic usage](#basic-usage)\n     * [Talking to the API with instant_client](#talking-to-the-api-with-instant_client)\n  * [Using method paths instead of JSON-RPC](#using-method-paths-instead-of-json-rpc)\n     * [HTTP status codes](#http-status-codes)\n  * [Global API configuration](#global-api-configuration)\n  * [Handling errors](#handling-errors)\n  * [Attaching methods](#attaching-methods)\n     * [Customising method paths in the Swagger UI](#customising-method-paths-in-the-swagger-ui)\n        * [Setting attributes directly](#setting-attributes-directly)\n        * [Setting summary and description via the docstring](#setting-summary-and-description-via-the-docstring)\n  * [Customising global request and method handling](#customising-global-request-and-method-handling)\n  * [Authentication](#authentication)\n  * [Dependencies](#dependencies)\n  * [Why use this library?](#why-use-this-library)\n\n## Installation\n\n    pip install instant-api\n\nOr to also install the corresponding Python client:\n\n    pip install 'instant-api[client]'\n\n## Basic usage\n\nJust write some Python functions or methods and decorate them. Parameters and the return value need type annotations so that they can be converted to and from JSON for you. You can use dataclasses for complex values.\n\n```python\nfrom dataclasses import dataclass\nfrom flask import Flask\nfrom instant_api import InstantAPI\n\napp = Flask(__name__)\n\n@dataclass\nclass Point:\n    x: int\n    y: int\n\n@InstantAPI(app)\nclass Methods:\n    def translate(self, p: Point, dx: int, dy: int) -\u003e Point:\n        \"\"\"Move a point by dx and dy.\"\"\"\n        return Point(p.x + dx, p.y + dy)\n\n    def scale(self, p: Point, factor: int) -\u003e Point:\n        \"\"\"Scale a point away from the origin by factor.\"\"\"\n        return Point(p.x * factor, p.y * factor)\n\nif __name__ == '__main__':\n    app.run()\n```\n\nVisit http://127.0.0.1:5000/apidocs/ for a complete Swagger GUI to try out the API interactively:\n\n![Swagger overview](images/swagger_overview.png)\n\n### Talking to the API with `instant_client`\n\nIf you need a Python client, I highly recommend the companion library [instant_client](https://github.com/alexmojaki/instant_client). It handles data conversion on the client side and works well with developer tools. Basic usage looks like:\n\n```python\nfrom server import Methods, Point  # the classes we defined above\nfrom instant_client import InstantClient\n\n# The type hint is a lie, but your linter/IDE doesn't know that!\nmethods: Methods = InstantClient(\"http://127.0.0.1:5000/api/\", Methods()).methods\n\nassert methods.scale(Point(1, 2), factor=3) == Point(3, 6)\n```\n\nThat looks a lot like it just called `Methods.scale()` directly, which is the point (no pun intended), but under the hood it did in fact send an HTTP request to the server.\n\n## Using method paths instead of JSON-RPC\n\nThe API is automatically available in two flavours, and clients can choose which way they prefer to communicate:\n\n1. The central JSON-RPC endpoint, which follows the JSON-RPC protocol spec exactly, and is easiest to use with standard client libraries.\n2. Method paths, which make it slightly easier for humans to write requests manually (especially in the Swagger GUI) and use the features of HTTP more.\n\nTo make a request to a method path, include the method name at the end of the URL, and just send the parameters object in the JSON body. Here's what such a call looks like:\n\n```python\nimport requests\n\nresponse = requests.post(\n    'http://127.0.0.1:5000/api/scale',\n    json={\n        'p': {'x': 1, 'y': 2}, \n        'factor': 3,\n    },\n)\n\nassert response.json()['result'] == {'x': 3, 'y': 6}\n```\n\nThe response will be a complete JSON-RPC response as if you had made a full JSON-RPC request. In particular it will either have a `result` or an `error` key.\n\n### HTTP status codes\n\nThe central JSON-RPC endpoint will always (unless a request is not authenticated, [see below](#authentication)) return the code HTTP status code 200 (OK), even if there's an error, as standard clients expect that.\n\nSince the method paths are not quite JSON-RPC, they may return a different code in case of errors. In particular an invalid request will lead to a 400 and an unhandled error inside a method will cause a 500.\n\nIf you [raise an `InstantError`](#handling-errors) inside a method, you can give it an `http_code`, e.g. `raise InstantError(..., http_code=404)`. This will become the HTTP status code *only if the method was called by the method path, not the JSON-RPC endpoint*.\n\n## Global API configuration\n\nThe `InstantAPI` class requires a Flask app and has the following optional keyword-only parameters:\n\n- `path` is a string (default `'/api/'`) which is the endpoint that will be added to the app for the JSON RPC. There will also be a path for each method based on the function name, e.g. `/api/scale` and `/api/translate` - see [Using method paths instead of JSON-RPC](#using-method-paths-instead-of-json-rpc). Specify a different string to change all of these paths.\n- `swagger_kwargs` is a dictionary (default empty) of keyword arguments to pass to the `flasgger.Swagger` constructor that is called with the app. For example, you can customise the Swagger UI by [passing a dictionary to `config`](https://github.com/flasgger/flasgger#customize-default-configurations):\n\n```python\napi = InstantAPI(app, swagger_kwargs={\"config\": {\"specs_route\": \"/my_apidocs/\", ...}})\n```\n\n## Handling errors\n\nWhen the server encounters an error, the response will contain an `error` key (instead of a `result`) with an object containing `code`, `data`, and `message`. For example, if a method is given invalid parameters, the details of the error (either a `TypeError` or a marshmallow `ValidationError`) will be included in the response. The error code will be `-32602`. The response JSON looks like this:\n\n```json\n{\n  \"error\": {\n    \"code\": -32602,\n    \"data\": {\n      \"p\": {\n        \"y\": [\n          \"Not a valid integer.\"\n        ]\n      }\n    },\n    \"message\": \"marshmallow.exceptions.ValidationError: {'p': {'y': ['Not a valid integer.']}}\"\n  },\n  \"id\": 0,\n  \"jsonrpc\": \"2.0\"\n}\n```\n\nYou can find more details, including the standard error codes for some typical errors, in the [JSON-RPC protocol spec](https://www.jsonrpc.org/specification#error_object).\n\nTo return your own custom error information, raise an `InstantError` in your method, e.g:\n\n```python\nfrom instant_api import InstantAPI, InstantError\n\n@InstantAPI(app)\nclass Methods:\n    def find_thing(self, thing_id: int) -\u003e Thing:\n        ...\n        raise InstantError(\n            code=123,\n            message=\"Thing not found anywhere at all\",\n            data=[\"not here\", \"or here\"],\n        )\n```\n\nThe response will then be:\n\n```json\n{\n  \"error\": {\n    \"code\": 123,\n    \"data\": [\n      \"not here\",\n      \"or here\"\n    ],\n    \"message\": \"Thing not found anywhere at all\"\n  },\n  \"id\": 0,\n  \"jsonrpc\": \"2.0\"\n}\n```\n\nThe HTTP status code depends on which flavour of the API you use - see [this section](#http-status-codes).\n\n## Attaching methods\n\nInstances of `InstantAPI` can be called with functions, classes, or arbitrary objects to add methods to the API. For functions and classes, the instance can be used as a decorator to call it.\n\nDecorating a single function adds it as an API method, as you'd expect. The function itself should not be a method of a class, since there is no way to provide the first argument `self`.\n\nCalling `InstantAPI` with an object will search through all its attributes and add to the API all functions (including bound methods) whose name doesn't start with an underscore (`_`).\n\nDecorating a class will construct an instance of the class without arguments and then call the resulting object as described above. This means it will add bound methods, so the `self` argument is ignored.\n\nSo given `api = InstantAPI(app)`, all of these are equivalent:\n\n```python\n@api\ndef foo(bar: Bar) -\u003e Spam:\n    ...\n\napi(foo)\n\n@api\nclass Methods:\n    def foo(self, bar: Bar) -\u003e Spam:\n        ...\n\napi(Methods)\n\napi(Methods())\n```\n\nIf a function is missing a type annotation for any of its parameters or for the return value, an exception will be raised. If you don't want a method to be added to the API, prefix its name with an underscore, e.g. `def _foo(...)`.\n\n### Customising method paths in the Swagger UI\n\n#### Setting attributes directly\n\nFor each method, a [`flasgger.SwaggerView`](https://github.com/flasgger/flasgger#using-marshmallow-schemas) will be created.\nYou can customise the view by passing a dictionary of class attributes\nin the argument `swagger_view_attrs` of the decorator.\nFor example:\n\n```python\n@api(swagger_view_attrs={\"tags\": [\"Stuff\"]})\ndef foo(...)\n```\n\nThis will put `foo` in the `Stuff` section of the Swagger UI.\n\nNote that the below is invalid syntax [before Python 3.9](https://www.python.org/dev/peps/pep-0614/):\n\n```python\n@InstantAPI(app)(swagger_view_attrs={\"tags\": [\"Stuff\"]})\ndef foo(...)\n```\n\n#### Setting summary and description via the docstring\n\nIf a method has a docstring, its first line will be the `summary`\nin the OpenAPI spec of the method path, visible in the overview in the Swagger UI.\nThe remaining lines will become the `description`,\nvisible when the path is expanded in the UI.\n\n## Customising global request and method handling\n\nTo directly control how requests are handled, create a subclass of `InstantAPI` and override one of these methods:\n\n- `handle_request(self, method)` is the entrypoint which converts a raw flask request to a response. If `method` is None, the request was made to the generic JSON-RPC path. Otherwise `method` is a string with the method name at the end of the request path.\n- `call_method(self, func, *args, **kwargs)` calls the API method `func` with the given arguments. The arguments here are not yet deserialized according to the function type annotations.\n\nUnless you're doing something very weird, remember to call the parent method with `super()` somewhere.\n\n## Authentication\n\nTo require authentication for requests:\n\n1. Create a subclass of `InstantAPI`.\n2. Override the method `def is_authenticated(self):`.\n3. Return a boolean: `True` if a user should have access (based on the global Flask `request` object), `False` if they should be denied.\n4. Use an instance of your subclass to decorate methods.\n\nUnauthenticated requests will receive a 403 response with a non-JSON body.\n\n## Dependencies\n\n- [**`datafunctions`**](https://github.com/alexmojaki/datafunctions) (which in turn uses [`marshmallow`](https://marshmallow.readthedocs.io/)) is used by both `instant_api` and `instant_client` to transparently handle conversion between JSON and Python classes on both ends.\n- [**Flasgger**](https://github.com/flasgger/flasgger) provides the Swagger UI.\n- [**`json-rpc`**](https://github.com/pavlov99/json-rpc) handles the protocol.\n\nBecause other libraries do so much of the work, `instant_api` itself is a very small library, essentially contained in [one little file](https://github.com/alexmojaki/instant_api/blob/master/instant_api/instant_api.py). You can probably read the source code pretty easily and adapt it to your needs.\n\n## Why use this library?\n\nThis library takes obvious inspiration from [FastAPI](https://github.com/tiangolo/fastapi). So why did I write this, and why might you want to use it?\n\n- It's really great with [`instant_client`](https://github.com/alexmojaki/instant_client), which lets you feel like you're calling methods locally (and your IDE helps you as if you are) even though they're executed remotely.\n- It's easier to set up, as you don't have to specify paths or HTTP methods. If you group everything into a class, you just have to decorate the whole thing once. It's almost the minimum amount of boilerplate possible.\n- JSON-RPC is pretty cool.\n    - It's a popular, standard protocol that has client libraries written in many languages.\n    - It lets you do bulk requests: send an array of requests, get an array of responses back.\n    - It supports notifications for when you don't care about the result.\n- It's great when you want to work with Flask (e.g. to use other Flask libraries), or more generally if you want a WSGI application without having to embed it inside FastAPI.\n\n  When my use case for this popped up, I considered FastAPI, but being able to use Flask (specifically Plotly Dash) was a hard requirement. The API was only a small part of a larger project, so I didn't want FastAPI to be 'in charge'.\n\n  I tried looking through the source code of FastAPI to extract the bits I needed, like generating the Swagger spec from type annotations, but the code is very complicated and this wasn't worth it. So I wrote my own version where the dependencies do the hard work like that in a nice modular manner. What's left is a small, readable library that largely just wires other stuff together. This way if someone else is in the same situation as me where they have slightly different needs, it's now feasible for them to adapt the source code.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexmojaki%2Finstant_api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexmojaki%2Finstant_api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexmojaki%2Finstant_api/lists"}