{"id":18828398,"url":"https://github.com/dealfonso/pyapi","last_synced_at":"2026-05-17T01:33:35.671Z","repository":{"id":206411890,"uuid":"716568315","full_name":"dealfonso/pyapi","owner":"dealfonso","description":"A template to create python applications that offer a REST API","archived":false,"fork":false,"pushed_at":"2023-11-14T22:59:25.000Z","size":26,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-29T14:22:08.420Z","etag":null,"topics":["api","api-rest","fastapi","python","python3","uvicorn"],"latest_commit_sha":null,"homepage":"","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/dealfonso.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-11-09T12:11:46.000Z","updated_at":"2023-11-15T08:41:06.000Z","dependencies_parsed_at":"2023-11-14T23:45:39.747Z","dependency_job_id":null,"html_url":"https://github.com/dealfonso/pyapi","commit_stats":null,"previous_names":["dealfonso/pyapi-rest","dealfonso/pyapi"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dealfonso/pyapi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dealfonso%2Fpyapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dealfonso%2Fpyapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dealfonso%2Fpyapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dealfonso%2Fpyapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dealfonso","download_url":"https://codeload.github.com/dealfonso/pyapi/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dealfonso%2Fpyapi/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267527835,"owners_count":24102019,"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-07-28T02:00:09.689Z","response_time":68,"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","api-rest","fastapi","python","python3","uvicorn"],"created_at":"2024-11-08T01:24:55.210Z","updated_at":"2026-05-17T01:33:30.623Z","avatar_url":"https://github.com/dealfonso.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PyAPI REST\n\nThis is a module that provides utilities to implement a REST API in Python. The utilities range from basic DB management to configuration management, including logging and easing the implementation of the REST calls.\n\n## Features\n\n- Includes an object to implement a DB that is backed in a json file\n    - Needs to implement methods `_unserialize` (converts the content of the JSON file to internal structures) and `_serialize` (converts the internal structures to a serializable json object).\n    - It implements _autosave_ features\n- Implements configuration management\n    - Easily load `yaml` files and put the values in a module or an object\n- Implements logging\n    - Log to a file and/or to the console\n- Help to implement REST calls\n    - Add the ability to use API keys\n    - Help to implement authentication\n    - It is based on [FastAPI](https://fastapi.tiangolo.com/).\n\n## Installation\n\n## Basic example\n\nHere we include a full working example that covers a lot of the features of the module.\n\n```python\nparser = argparse.ArgumentParser(allow_abbrev=False)\n\nparser.add_argument(\"-p\", \"--port\", help=\"port in which to listen\", default=None, type=int, dest=\"port\")\nparser.add_argument(\"-v\", \"--verbose\", help=\"verbose\", action=\"store_true\", dest=\"verbose\")\nparser.add_argument(\"-i\", \"--ip\", help=\"ip address in which to listen\", default=None, type=str, dest=\"ip\")\nparser.add_argument(\"-c\", \"--config\", help=\"configuration file\", default=None, type=str, dest=\"config_file\")\nparser.add_argument(\"-q\", \"--quiet\", help=\"suppress console output\", action=\"store_true\", dest=\"quiet\")\nparser.add_argument(\"-l\", \"--log-file\", help=\"the log file\", default=None, type=str, dest=\"log_file\")\n\nargs = parser.parse_args()\n\n# Create a config object\nconfig = pyapi.config.Config(\n    LOG_LEVEL=\"INFO\",\n    LOG_FILE=None,\n    QUIET=False,\n    PORT=8000,\n    IP_ADDRESS=\"0.0.0.0\"\n)\n\n# If a config file is supplied, use it as the first option\nif args.config_file is not None:\n    config.load(args.config_file, False)\n\n# Override commandline options\nif args.verbose:\n    config.LOG_LEVEL = \"DEBUG\"\n\nif args.log_file is not None:\n    config.LOG_FILE = args.log_file\n\nif args.quiet:\n    config.QUIET = True\n\nif args.port is not None:\n    config.PORT = args.port\n\n# Now we are going to configure the log system\npyapi.debug.set_log_file(config.LOG_FILE)\npyapi.debug.set_log_level(config.LOG_LEVEL)\npyapi.debug.set_quiet(config.QUIET)\n\np_debug(f\"Now we are checking the IP address\")\nif args.ip is not None:\n    import ipaddress\n    try:\n        ipaddress.ip_address(args.ip)\n        config.IP_ADDRESS = args.ip\n    except:\n        p_fatal(\"Invalid IP address: %s\" % args.ip)\n\np_debug(f\"And now we start the server\")\n\n# Create the API\napi = pyapi.fastapi.FastAPIX()\n\n# Add the root endpoint (these are FastAPI things)\n@api.get(\"/\")\ndef home():\n    return { \"status\": \"OK\", \"message\": \"Hello world!\" }\n\n# Start the server according to the configuration\npyapi.uvicorn.run(api, host = config.IP_ADDRESS, port = config.PORT, log_level = config.LOG_LEVEL)\n```\n\n## Logging (`pyapi.debug`)\n\nThe module `pyapi.debug` provides different functions to log messages to the console and/or to a file. In particular, it provides the following functions: `p_debug`, `p_info`, `p_warning`, `p_error` and `p_fatal`.\n\n### Configuring the log system\n\nThe log system can be configured by means of the following functions:\n\n- `set_log_file`: Sets the log file. If `None` is passed, the log file is disabled.\n- `set_log_level`: Sets the log level. The log level can be one of the following: `DEBUG`, `INFO`, `WARNING`, `ERROR` or `FATAL`.\n- `set_quiet`: Sets the quiet mode. If `True` is passed, the console output is disabled.\n\n## REST API (`pyapi.fastapi`)\n\nThis module exports a class named `FastAPIX` that extends the class `FastAPI` from the [FastAPI](https://fastapi.tiangolo.com/) module. The main difference is that it adds the ability to use API keys and to implement authentication, and some other facilities.\n\nThe signature of the constructor is the following:\n\n```python\ndef __init__(self\n    # Function to be called when the server starts\n    on_start: Callable[[FastAPI], Any] = None, \n    # Function to be called when the server stops\n    on_stop: Callable[[FastAPI], Any] = None,\n    # List of API keys that are valid for this API\n    api_keys : list[str] = None, \n    # Function to be called when a user is authenticated \"def auth(username: str, password: str) -\u003e str | bool\"\n    auth_user : Callable[[str, str], Union[str,bool]] = None,\n    # The rest of the parameters are passed to the FastAPI constructor\n    *args, **kwargs)\n```\n\n- `on_start`: A function that is called when the server starts. It receives the `FastAPI` object as parameter.\n- `on_stop`: A function that is called when the server stops. It receives the `FastAPI` object as parameter.\n- `api_keys`: A list of API keys that are valid for this API (they have to be passed in the `X-API-KEY` header or as `api-key` query var). If `None` is passed, no API keys are defined.\n- `auth_user`: A function that is called when a user is authenticated. It receives the username and password as parameters and it has to return `True` if the user is authenticated or `False` otherwise.\n\n### Other features\n\nThe class `FastAPIX` also provides the following features:\n- Attach sub-APIs to the main API at a given path, using function `add_api(self, api: FastAPI, *, base_path = \"/\")`.\n- Add a function that helps to obtain the username of the authenticated user that is using a route\n    \u003e In your routes you can add the parameter `username: Annotated[ str, Depends(api.get_username) ]` and it will contain the username of the authenticated user, or None (if no credentials were provided).\n\n### Decorating the calls\n\nThe class `FastAPIX` provides the same route decorators that are provided by the `FastAPI` class, but adding some extra functionality. In particular, it offers the following decorators: `get`, `post`, `put`, `delete`, `patch`, `options`, `head` and `trace`, and they all accept the same parameters as the decorators of the `FastAPI` class, but also the next ones:\n\n- `require_keys`: if `True` is passed, a valid API key is required to call the endpoint. The API key can be passed in the `X-API-KEY` header or as `api-key` query var, and it has to be one of the API keys defined in the constructor of the class.\n- `require_auth`: if `True` is passed, the user has to be authenticated to call the endpoint. The authentication is done by calling the function passed in the constructor of the class. The function receives the username and password as parameters and it has to return `True` if the user is authenticated or `False` otherwise.\n- `allow_anonymous`: if `True` is passed, the user does not need to be authenticated to call the endpoint. This parameter completes the `require_auth` parameter, so if both are `True`, the user does not need to be authenticated but the request has to include a username and a password.\n\nAs an example, the following code defines an endpoint that requires a valid API key and that the user is authenticated:\n\n```python\n@api.get(\"/test\", require_keys=True, require_auth=True)\ndef test():\n    return { \"status\": \"OK\", \"message\": \"Hello world!\" }\n```\n\n## `JsonDB` class\n\nThis is a class that does nothing but to provide a DB that is backed in a json file.\n\nThe underlying idea is to abstract the DB from the application, so that the application does not need to know how the DB is implemented. So in this class you should implement your mechanisms to retrieve the data structures in the application so that if you want to upgrade the DB to a different DB runtime, it would help you to do it.\n\ne.g.\n\n```python\nclass MyDB(JsonDB):\n    tokens = {}\n\n    def _serialize(self, data):\n        return self.tokens\n\n    def _unserialize(self, data):\n        self.tokens = data\n        return true\n\n    def getToken(self, tokenId):\n        if tokenId in self.tokens:\n            return self.tokens[tokenId]\n        return None\n```\n\nIn this way, the application can use the DB by means of using the method `getToken` without knowing how the DB is implemented.\n\n## `uvicorn` helper\n\nThis module exports a function named `run` that can be used to start a server. It is a wrapper around the `uvicorn.run` function, but it adds some extra functionality. In particular, it enables to mute the console output and to log to a file by integrating the logging system of `uvicorn` with the one of `pyapi.debug`.\n\nThe signature of the function is the following:\n\n```python\ndef run(api, *, host: str = \"0.0.0.0\", port: int = 8000, log_level: str = \"info\", log_api_calls = False, log_uvicorn = True):\n```\n\n## Configuration management (`pyapi.config`)\n\nThe module `pyapi.config` provides different mechanisms to manage configuration variables:\n- The `Config` class: A class that can be used to define configuration variables and load them from a configuration file.\n- The `load` function: A function that can be used to load a configuration file and put the values in a module or an object.\n\n### The `Config` class\n\nThe `Config` class can be used to define configuration variables and load them from a configuration file. There are different ways to use it:\n\n#### Subclassing `Config` class\n\nYou can subclass the `Config` class and define your own variables:\n\n```python\nimport pyapi.config\n\nclass MyConfig(pyapi.config.Config):\n    IP_ADDRESS = \"0.0.0.0\"\n    PORT = 8000\n```\n\nThen you can instantiate your class and use the variables defined there (well... we did not anything yet), but now you can load a configuration file and the values will be put in the variables:\n\n```yaml\nIP_ADDRESS: 127.0.0.1\n```\n\n```python\nconfig = MyConfig()\nconfig.load(\"myconfig.yaml\")\nprint(config.IP_ADDRESS)\n```\n\n#### Using the `Config` class directly\n\nIf you do not want to subclass the `Config` class, you can use it directly:\n\n```python\nimport pyapi.config\n\nconfig = pyapi.config.Config(\n    IP_ADDRESS=\"0.0.0.0\",\n    PORT=8000\n)\n```\n\nThis example is equivalent to the previous one, but now the variables are defined in the constructor of the class, along with the default values. And now you can load a configuration file and the values will be put in the variables:\n\n```yaml\nIP_ADDRESS: 127.0.0.1\n```\n\n```python\nconfig.load(\"myconfig.yaml\")\nprint(config.IP_ADDRESS)\n```\n\n### The `load` function\n\nAppart from the `Config` class, module `pyapi.config` exports the function `load`, which can be used to load a configuration file and put the values in a module or an object.\n\nThe function has the following signature:\n\n```python\ndef load(filenames: Union[str, list[str]], layered_configuration = False, *, configurable_variables: list[str] | None = None, current_config: Any = None, subkey: str = None) -\u003e list[str]:\n```\n\nThis function loads the configuration files passed as parameter and puts the values in the module or object passed as parameter `current_config`. And the thing is that `current_config` can be any type of object and so you can use it to load the configuration in a module or in an object.\n\n#### Loading configuration in an object\n\nIf you want to load the configuration in an object, you can do it by passing the object as the parameter `current_config`:\n\n\u003e I find that, as we have the `Config` class, it is better to use it instead of using an arbitraty object.\n\n```python\nimport pyapi.config\n\nclass MyConfig:\n    IP_ADDRESS = \"0.0.0.0\"\n    PORT = 8000\n```\n\nAnd now you can load a configuration file and the values will be put in the variables:\n\n```yaml\nIP_ADDRESS: 127.0.0.1\n```\n\n```python\nconfig = MyConfig()\npyapi.config.load(\"myconfig.yaml\", current_config=config)\nprint(config.IP_ADDRESS)\n```\n\n\u003e The key difference is that the object is not a subclass of `Config` class, but it is a normal object.\n\n#### Loading configuration in a module\n\nI really like to have a module with the configuration variables, so that I can import it in any part of the code and use the variables defined there. And the `load` function can be used to load the configuration into that module.\n\nIf we have the following module named `myconfig.py`:\n\n```python\nimport pyapi.config\n\nIP_ADDRESS = \"0.0.0.0\"\nPORT = 8000\n\ndef load(filename: str) -\u003e bool:\n    return pyapi.config.load(filename, current_config=sys.modules[__name__])\n```\n\nNow we can load a configuration file and the values will be put in the variables:\n\n```yaml\nip_address: 127.0.0.1\n```\n\n```python\nimport myconfig\n\nmyconfig.load(\"myconfig.yaml\")\nprint(myconfig.IP_ADDRESS)\n```\n\nAnd now you can import the module in any part of your code and use the variables defined there, in the same way.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdealfonso%2Fpyapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdealfonso%2Fpyapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdealfonso%2Fpyapi/lists"}