{"id":31062724,"url":"https://github.com/erewok/servant-py","last_synced_at":"2025-09-15T12:47:29.246Z","repository":{"id":54223984,"uuid":"81767961","full_name":"erewok/servant-py","owner":"erewok","description":"Servant client generators for the Python language","archived":false,"fork":false,"pushed_at":"2021-03-02T16:06:34.000Z","size":47,"stargazers_count":17,"open_issues_count":6,"forks_count":7,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-13T08:33:49.040Z","etag":null,"topics":["haskell","haskell-library","python","servant","servant-client"],"latest_commit_sha":null,"homepage":null,"language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/erewok.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}},"created_at":"2017-02-13T00:13:58.000Z","updated_at":"2025-05-11T19:27:32.000Z","dependencies_parsed_at":"2022-08-13T09:31:14.236Z","dependency_job_id":null,"html_url":"https://github.com/erewok/servant-py","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/erewok/servant-py","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erewok%2Fservant-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erewok%2Fservant-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erewok%2Fservant-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erewok%2Fservant-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/erewok","download_url":"https://codeload.github.com/erewok/servant-py/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erewok%2Fservant-py/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275260484,"owners_count":25433379,"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-09-15T02:00:09.272Z","response_time":75,"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":["haskell","haskell-library","python","servant","servant-client"],"created_at":"2025-09-15T12:47:27.494Z","updated_at":"2025-09-15T12:47:29.233Z","avatar_url":"https://github.com/erewok.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# servant-py\n\n[![Build Status](https://dev.azure.com/eraker62/servant-py/_apis/build/status/erewok.servant-py?branchName=master)](https://dev.azure.com/eraker62/servant-py/_build/latest?definitionId=1?branchName=master)\n\nThis library lets you derive automatically Python functions that let you query each endpoint of a *servant* webservice.\n\nCurrently, the only supported method for generating requests is via the `requests` library, which is the recommended way to generate HTTP requests in the Python world (even among Python core devs).\n\n## Inspiration\n\nThis library is largely inspired by [servant-js](https://github.com/haskell-servant/servant-js) and by the fantastic work of the Servant team in general. Any good ideas you find in here are from their work (any mistakes are almost entirely mine, however).\n\n## Example\n\nThere are two different styles of function-return supported here: `DangerMode` and `RawResponse`.\n\nThe latter returns the raw response from issuing the request and the former calls `raise_for_status` and then attempts to return `resp.json()`. You can switch which style you'd like to use by creating a proper `CommonGeneratorOptions` object.\n\nThe default options just chucks it all to the wind and goes for `DangerMode` (because, seriously, we're using Haskell to generate Python here...).\n\nFollowing is an example of using the Servant DSL to describe endpoints and then using `servant-py` to create Python clients for those endpoints.\n\n#### Servant DSL API Description\n\n``` haskell\n{-# LANGUAGE DataKinds                  #-}\n{-# LANGUAGE DeriveGeneric              #-}\n{-# LANGUAGE GeneralizedNewtypeDeriving #-}\n{-# LANGUAGE TypeOperators              #-}\n\nmodule Main where\n\nimport           Data.Aeson\nimport qualified Data.ByteString.Char8 as B\nimport           Data.Proxy\nimport qualified Data.Text             as T\nimport           GHC.Generics\nimport           Servant\nimport           System.FilePath\n\nimport           Servant.PY\n\n-- * A simple Counter data type\nnewtype Counter = Counter { value :: Int }\n  deriving (Generic, Show, Num)\ninstance ToJSON Counter\n\ndata LoginForm = LoginForm\n { username :: !T.Text\n , password :: !T.Text\n , otherMissing :: Maybe T.Text\n } deriving (Eq, Show, Generic)\ninstance ToJSON LoginForm\n\n-- * Our API type\ntype TestApi = \"counter-req-header\" :\u003e Post '[JSON] Counter\n          :\u003c|\u003e \"counter-queryparam\"\n            :\u003e QueryParam \"sortby\" T.Text\n            :\u003e Header \"Some-Header\" T.Text :\u003e Get '[JSON] Counter\n          :\u003c|\u003e \"login-queryflag\" :\u003e QueryFlag \"published\" :\u003e Get '[JSON] LoginForm\n          :\u003c|\u003e \"login-params-authors-with-reqBody\"\n            :\u003e QueryParams \"authors\" T.Text\n            :\u003e ReqBody '[JSON] LoginForm :\u003e Post '[JSON] LoginForm\n          :\u003c|\u003e \"login-with-path-var-and-header\"\n            :\u003e Capture \"id\" Int\n            :\u003e Capture \"Name\" T.Text\n            :\u003e Capture \"hungrig\" Bool\n            :\u003e ReqBody '[JSON] LoginForm\n            :\u003e Post '[JSON] (Headers '[Header \"test-head\" B.ByteString] LoginForm)\n\ntestApi :: Proxy TestApi\ntestApi = Proxy\n\n-- where our static files reside\nresult :: FilePath\nresult = \"examples\"\n\nmain :: IO ()\nmain = writePythonForAPI testApi requests (result \u003c/\u003e \"api.py\")\n```\n\n#### Generated Python Code\n\nIf you build the above and run it, you will get some output that looks like the following:\n\n```python\nfrom urllib import parse\n\nimport requests\n\ndef post_counterreqheader():\n    \"\"\"\n    POST \"counter-req-header\"\n\n    \"\"\"\n    url = \"http://localhost:8000/counter-req-header\"\n\n    resp = requests.post(url)\n    resp.raise_for_status()\n    return resp.json()\n\n\ndef get_counterqueryparam(sortby, headerSomeHeader):\n    \"\"\"\n    GET \"counter-queryparam\"\n\n    \"\"\"\n    url = \"http://localhost:8000/counter-queryparam\"\n\n    headers = {\"Some-Header\": headerSomeHeader}\n    params = {\"sortby\": sortby}\n    resp = requests.get(url,\n                        headers=headers,\n                        params=params)\n\n    resp.raise_for_status()\n    return resp.json()\n\n\ndef get_loginqueryflag(published):\n    \"\"\"\n    GET \"login-queryflag\"\n\n    \"\"\"\n    url = \"http://localhost:8000/login-queryflag\"\n\n    params = {\"published\": published}\n    resp = requests.get(url,\n                        params=params)\n\n    resp.raise_for_status()\n    return resp.json()\n\n\ndef post_loginparamsauthorswithreqBody(authors, data):\n    \"\"\"\n    POST \"login-params-authors-with-reqBody\"\n\n    \"\"\"\n    url = \"http://localhost:8000/login-params-authors-with-reqBody\"\n\n    params = {\"authors\": authors}\n    resp = requests.post(url,\n                         params=params,\n                         json=data)\n\n    resp.raise_for_status()\n    return resp.json()\n\n\ndef post_loginwithpathvarandheader_by_id_by_Name_by_hungrig(id, Name, hungrig, data):\n    \"\"\"\n    POST \"login-with-path-var-and-header/{id}/{Name}/{hungrig}\"\n    Args:\n        id\n        Name\n        hungrig\n    \"\"\"\n    url = \"http://localhost:8000/login-with-path-var-and-header/{id}/{Name}/{hungrig}\".format(\n        id=parse.quote(id),\n        Name=parse.quote(Name),\n        hungrig=parse.quote(hungrig))\n\n    resp = requests.post(url,\n                         json=data)\n\n    resp.raise_for_status()\n    return resp.json()\n```\n\nIf you would like to compile and run this example yourself, you can do that like so:\n\n```\n$ stack build --flag servant-py:example\n$ stack exec servant-py-exe\n$ cat examples/api.py\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferewok%2Fservant-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ferewok%2Fservant-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferewok%2Fservant-py/lists"}