{"id":15297393,"url":"https://github.com/fnproject/fdk-python","last_synced_at":"2025-04-07T17:10:30.934Z","repository":{"id":37693006,"uuid":"105650950","full_name":"fnproject/fdk-python","owner":"fnproject","description":"Python Function Development Kit ","archived":false,"fork":false,"pushed_at":"2025-03-14T11:29:49.000Z","size":514,"stargazers_count":47,"open_issues_count":13,"forks_count":25,"subscribers_count":33,"default_branch":"master","last_synced_at":"2025-03-31T15:18:26.212Z","etag":null,"topics":["faas-platform","fn","functions-as-a-service","python","python3"],"latest_commit_sha":null,"homepage":"http://fnproject.io/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fnproject.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-10-03T12:48:10.000Z","updated_at":"2025-03-14T11:29:50.000Z","dependencies_parsed_at":"2025-01-07T20:10:55.983Z","dependency_job_id":"35c40d28-e34b-48f3-9137-26581515c0e4","html_url":"https://github.com/fnproject/fdk-python","commit_stats":{"total_commits":171,"total_committers":20,"mean_commits":8.55,"dds":0.543859649122807,"last_synced_commit":"49ab4323ff332a3b6af3f5f9ecab4534e722edf1"},"previous_names":[],"tags_count":93,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fnproject%2Ffdk-python","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fnproject%2Ffdk-python/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fnproject%2Ffdk-python/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fnproject%2Ffdk-python/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fnproject","download_url":"https://codeload.github.com/fnproject/fdk-python/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247694876,"owners_count":20980733,"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","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":["faas-platform","fn","functions-as-a-service","python","python3"],"created_at":"2024-09-30T19:17:04.088Z","updated_at":"2025-04-07T17:10:30.916Z","avatar_url":"https://github.com/fnproject.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Function development kit for Python\nThe python FDK lets you write functions in python 3.8/3.9/3.11\n\n## Simplest possible function \n \n```python\nimport io\nimport logging\n\nfrom fdk import response\n\ndef handler(ctx, data: io.BytesIO = None):\n    logging.getLogger().info(\"Got incoming request\")\n    return response.Response(ctx, response_data=\"hello world\")\n```\n\n\n## Handling HTTP metadata in HTTP Functions \nFunctions can implement HTTP services when fronted by an HTTP Gateway\n\nWhen your function is behind an HTTP gateway you can access the inbound HTTP Request via :\n\n  - `ctx.HttpHeaders()` : a map of string -\u003e value | list of values , unlike `ctx.Headers()` this only includes headers \n        passed by the HTTP gateway (with no functions metadata).\n  - `ctx.RequestURL()` : the incoming request URL passed by the gateway \n  - `ctx.Method()` : the HTTP method of the incoming request \n   \nYou can set outbound HTTP headers and the HTTP status of the request using `ctx.SetResponseHeaders` or the `Response`    \n  - e.g. `ctx.SetResponseHeaders({\"Location\",\"http://example.com/\",\"My-Header2\": [\"v1\",\"v2\"]}, 302)` \n  - or by passing these to the Response object : \n```python\nreturn new Response(\n    ctx,\n    headers={\"Location\",\"http://example.com/\",\"My-Header2\": [\"v1\",\"v2\"]},\n    response_data=\"Page moved\",\n    status_code=302)\n```\n\ne.g. to redirect users to a different page : \n```python\nimport io\nimport logging\n\nfrom fdk import response\n\ndef handler(ctx, data: io.BytesIO = None):\n    logging.getLogger().info(\"Got incoming request for URL %s with headers %s\", ctx.RequestURL(), ctx.HTTPHeaders())\n    ctx.SetResponseHeaders({\"Location\": \"http://www.example.com\"}, 302)\n    return response.Response(ctx, response_data=\"Page moved from %s\")\n```\n\n\n## Handling JSON in  Functions\n\nA main loop is supplied that can repeatedly call a user function with a series of requests.\nIn order to utilise this, you can write your `func.py` as follows:\n\n```python\nimport json\nimport io\n\nfrom fdk import response\n\ndef handler(ctx, data: io.BytesIO=None):\n    name = \"World\"\n    try:\n        body = json.loads(data.getvalue())\n        name = body.get(\"name\")\n    except (Exception, ValueError) as ex:\n        print(str(ex))\n        pass\n\n    return response.Response(\n        ctx, response_data=json.dumps(\n            {\"message\": \"Hello {0}\".format(name)}),\n        headers={\"Content-Type\": \"application/json\"}\n    )\n\n```\n\n## Writing binary data from functions \nIn order to write a binary response to your function pass a `bytes` object to the response_data\n\n```python\nimport io \nfrom PIL import Image, ImageDraw \nfrom fdk import response \n \n \ndef handler(ctx, data: io.BytesIO=None): \n    img = Image.new('RGB', (100, 30), color='red') \n    d = ImageDraw.Draw(img) \n    d.text((10, 10), \"hello world\", fill=(255, 255, 0)) \n    # write png image to memory  \n    output =  io.BytesIO() \n    img.save(output, format=\"PNG\") \n    # get the bytes of the image  \n    imgbytes = output.getvalue() \n \n    return response.Response( \n        ctx, response_data=imgbytes, \n        headers={\"Content-Type\": \"image/png\"} \n    )\n```\n\n\n\n## Unit testing your functions\n\nStarting v0.0.33 FDK-Python provides a testing framework that allows performing unit tests of your function's code.\nThe unit test framework is the [pytest](https://pytest.org/). Coding style remain the same, so, write your tests as you've got used to.\nHere's the example of the test suite:\n```python\nimport json\nimport io\nimport pytest\n\nfrom fdk import fixtures\nfrom fdk import response\n\n\ndef handler(ctx, data: io.BytesIO=None):\n    name = \"World\"\n    try:\n        body = json.loads(data.getvalue())\n        name = body.get(\"name\")\n    except (Exception, ValueError) as ex:\n        print(str(ex))\n        pass\n\n    return response.Response(\n        ctx, response_data=json.dumps(\n            {\"message\": \"Hello {0}\".format(name)}),\n        headers={\"Content-Type\": \"application/json\"}\n    )\n\n\n@pytest.mark.asyncio\nasync def test_parse_request_without_data():\n    call = await fixtures.setup_fn_call(handler)\n\n    content, status, headers = await call\n\n    assert 200 == status\n    assert {\"message\": \"Hello World\"} == json.loads(content)\n\n```\n\nAs you may see all assertions being performed with native assertion command.\n\nIn order to run tests, use the following command:\n```bash\npytest -v -s --tb=long func.py\n```\n\n```bash\n========================================================================================= test session starts ==========================================================================================\nplatform darwin -- Python 3.7.1, pytest-4.0.1, py-1.7.0, pluggy-0.8.0 -- /python/bin/python3\ncachedir: .pytest_cache\nrootdir: /Users/denismakogon/go/src/github.com/fnproject/test, inifile:\nplugins: cov-2.4.0, asyncio-0.9.0, aiohttp-0.3.0\ncollected 1 item                                                                                                                                                                                       \n\nfunc.py::test_parse_request_without_data 2018-12-10 15:42:30,029 - asyncio - DEBUG - Using selector: KqueueSelector\n2018-12-10 15:42:30,029 - asyncio - DEBUG - Using selector: KqueueSelector\n'NoneType' object has no attribute 'getvalue'\n{'Fn-Http-Status': '200', 'Content-Type': 'application/json'}\nPASSED\n\n======================================================================================= 1 passed in 0.02 seconds =======================================================================================\n```\n\nTo add coverage first install one more package:\n```bash\npip install pytest-cov\n```\nthen run tests with coverage flag:\n```bash\npytest -v -s --tb=long --cov=func func.py\n```\n\n```bash\npytest -v -s --tb=long --cov=func func.py\n========================================================================================= test session starts ==========================================================================================\nplatform darwin -- Python 3.7.1, pytest-4.0.1, py-1.7.0, pluggy-0.8.0 -- /python/bin/python3\ncachedir: .pytest_cache\nrootdir: /Users/denismakogon/go/src/github.com/fnproject/test, inifile:\nplugins: cov-2.4.0, asyncio-0.9.0, aiohttp-0.3.0\ncollected 1 item                                                                                                                                                                                       \n\nfunc.py::test_parse_request_without_data 2018-12-10 15:43:10,339 - asyncio - DEBUG - Using selector: KqueueSelector\n2018-12-10 15:43:10,339 - asyncio - DEBUG - Using selector: KqueueSelector\n'NoneType' object has no attribute 'getvalue'\n{'Fn-Http-Status': '200', 'Content-Type': 'application/json'}\nPASSED\n\n---------- coverage: platform darwin, python 3.7.1-final-0 -----------\nName      Stmts   Miss  Cover\n-----------------------------\nfunc.py      19      1    95%\n\n\n======================================================================================= 1 passed in 0.06 seconds =======================================================================================\n```\n\n## FDK tooling\n\n## Installing tools\n\nCreate a virtualenv:\n```bash\npython3 -m venv .venv\n```\nActivate virtualenv:\n```bash\nsource .venv/bin/activate\n```\nAll you have to do is:\n```bash\npip install fdk\n```\nNow you have a new tools added!\n\n## Tools\n\nWith a new FDK release a new set of tooling introduced:\n\n - `fdk` - CLI tool, an entry point to a function, that's the way you start your function in real life\n - `fdk-tcp-debug` - CLI tool, an entry point to a function local debugging\n\n## CLI tool: `fdk`\n\nThis is an entry point to a function, this tool you'd be using while working with a function that is deployed at Fn server.\n\n### Usage\n\n`fdk` is a Python CLI script that has the following signature:\n\n```bash\nfdk \u003cpath-to-a-function-module\u003e [module-entrypoint]\n```\n\nwhere:\n    - `fdk` is a CLI script\n    - `\u003cpath-to-a-function-module\u003e` is a path to your function's code, for instance, `/function/func.py`\n    - `[module-entrypoint]` is an entry point to a module, basically you need to point to a method that has the following signature:\n    `def \u003cfunction_name\u003e(ctx, data: io.BytesIO=None)`, as you many notice this is a ordinary signature of Python's function you've used to while working with an FDK,\n\nThe parameter `[module-entrypoint]` has a default value: `handler`. It means that if a developer will point an `fdk` CLI to a module `func.py`:\n\n```\nfdk func.py\n```\n\nthe CLI will look for `handler` Python function.\nIn order to override `[module-entrypoint]` you need to specify your custom entry point.\n\n### Testing locally\n\nTo run a function locally (outside Docker) you need to set `FN_FORMAT` and `FN_LISTENER`, like so:\n\n```bash\nenv FDK_DEBUG=1 FN_FORMAT=http-stream FN_LISTENER=unix://tmp/func.sock fdk \u003cpath-to-a-function-module\u003e [module-entrypoint]\n```\n\nYou can then test with curl:\n\n```bash\ncurl -v --unix-socket /tmp/func.sock -H \"Fn-Call-Id: 0000000000000000\" -H \"Fn-Deadline: 2030-01-01T00:00:00.000Z\" -XPOST http://function/call -d '{\"name\":\"Tubbs\"}'\n```\n\n## CLI tool: `fdk-tcp-debug`\n\nThe reason why this tool exists is to give a chance to developers to debug their function on their machines.\nThere's no difference between this tool and `fdk` CLI tool, except one thing: `fdk` works on top of the unix socket,\nwhen this tool works on top of TCP socket, so, the difference is a transport, nothing else.\n\n#### Usage\n\n`fdk-tcp-debug` is a Python CLI script that has the following signature:\n\n```bash\nfdk-tcp-debug \u003cport\u003e \u003cpath-to-a-function-module\u003e [module-entrypoint]\n```\n\nThe behaviour of this CLI is the same, but it will start an FDK on top of the TCP socket.\nThe only one difference is that this CLI excepts one more parameter: `port` that is required by TCP socket configuration.\n\nNow you can test your functions not only with the unit tests but also see how it works within the FDK before actually deploying them to Fn server.\n\n\n## Developing and testing an FDK\n\nIf you decided to develop an FDK please do the following:\n\n - open an issue with the detailed description of your problem\n - checkout a new branch with the following signature: `git checkout -b issue-\u003cnumber\u003e`\n\nIn order to test an FDK changes do the following:\n\n - `python3 -m venv .venv \u0026\u0026 source .venv/bin/activate`\n - `pip install tox`\n - `tox`\n \n### Testing with `fdk-tcp-debug`\n\nTest an FDK change with sample function using `fdk-tcp-debug`:\n\n```bash\npip install -e .\nFDK_DEBUG=1 fdk-tcp-debug 5001 samples/echo/func.py handler\n```\n\nThen just do:\n\n```bash\ncurl -v -X POST localhost:5001 -d '{\"name\":\"denis\"}'\n```\n\n### Testing within a function\n\nFirst of all create a test function:\n```bash\nfn init --runtime python3.8 test-function\n```\n\nCreate a Dockerfile in a function's folder:\n```dockerfile\nFROM fnproject/python:3.8-dev as build-stage\n\nADD . /function\nWORKDIR /function\n\nRUN pip3 install --target /python/  --no-cache --no-cache-dir fdk-test-py3-none-any.whl \n\nRUN rm -fr ~/.cache/pip /tmp* requirements.txt func.yaml Dockerfile .venv\n\nFROM fnproject/python:3.8\n\nCOPY --from=build-stage /function /function\nCOPY --from=build-stage /python /python\nENV PYTHONPATH=/python\n\nENTRYPOINT [\"/python/bin/fdk\", \"/function/func.py\", \"handler\"]\n```\n\nBuild an FDK wheel:\n```bash\npip install wheel\nPBR_VERSION=test python setup.py bdist_wheel\n```\n\nMove an FDK wheel (located at `dist/fdk-test-py3-none-any.whl`) into a function's folder.\n\nDo the deploy:\n```bash\nfn --versbose deploy --app testapp --local --no-bump\nfn config fn testapp test-function FDK_DEBUG 1\n```\n\nAnd the last step - invoke it and see how it goes:\n```bash\nfn invoke testapp test-function\n```\n\n## Speeding up an FDK\n\nFDK is based on the asyncio event loop. Default event loop is not quite fast, but works on all operating systems (including Windows),\nIn order to make an FDK to process IO operation at least 4 times faster you need to add another dependency to your function:\n\n```text\nuvloop\n```\n\n[UVLoop](https://github.com/MagicStack/uvloop) is a CPython wrapper on top of cross-platform [libuv](https://github.com/libuv/libuv).\nUnfortunately, uvloop doesn't support Windows for some reason, so, in order to let developers test their code on Windows\nFDK doesn't install uvloop by default, but still has some checks to see whether it is installed or not.\n\n\n## Migration path\n\nAs if you are the one who used Python FDK before and would like to update - please read this section carefully.\nA new FDK is here which means there suppose to be a way to upgrade your code from an old-style FDK to a new-style FDK.\n\n### No `__main__` definition\n\nAs you noticed - an entry point a function changed, i.e., func.py no longer considered as the main module (`__main__`) which means that the following section:\n\n```python\nif __name__ == \"__main__\":\n    fdk.handle(handler)\n```\n\nhas no effect any longer. Please note that FDK will fail-fast with an appropriate message if old-style FDK format used.\n\n### `data` type has changed\n\nWith a new FDK, `data` parameter is changing from `str` to `io.BytesIO`.\nThe simplest way to migrate is to wrap your data processing code with 1 line of code:\n```python\ndata = data.read()\n```\n\nIf you've been using json lib to turn an incoming data into a dictionary you need to replace: `json.loads` with `json.load`\n\n```python\ntry:\n    dct = json.load(data)\nexcept ValueError as ex:\n    # do here whatever is reasonable\n```\n\n### Dockerfile\nIf you've been using CLI to build function without modifying runtime in `func.yaml` to `docker` \ninstead of `python` then the only thing you need is to update the CLI to the latest version and \npin your Python runtime version to `python`, `python3.7`, `python3.8`, or `python3.9`, or `python3.11` .\n\nIf you've been using custom multi-stage Dockerfile (derived from what Fn CLI generates) \nthe only thing that is necessary to change is an `ENTRYPOINT` from:\n\n```text\nENTRYPOINT[\"python\", \"func.py\"]\n```\n\nto:\n\n```text\nENTRYPOINT[\"/python/bin/fdk\", \"func.py\", \"handler\"]\n```\n\nIf you've been using your own Dockerfile that wasn't derived from the Dockerfile \nthat CLI is generating, then you need to search in your `$PATH` where CLI fdk was installed \n(on Linux, it will be installed to `/usr/local/bin/fdk`). At most of the times, if you've been using:\n\n```text\npip install --target \u003clocation\u003e ...\n```\n\nthen you need to search fdk CLI at `\u003clocation\u003e/bin/fdk`, this is what Fn CLI does by calling the following command:\n\n```text\npip install --target /python ...\n```\n\n## Notes\n\nA new FDK will abort a function execution if old-style function definition is used.\nMake sure you check you migrated your code wisely.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffnproject%2Ffdk-python","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffnproject%2Ffdk-python","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffnproject%2Ffdk-python/lists"}