{"id":13625840,"url":"https://github.com/keithasaurus/koda","last_synced_at":"2026-02-19T08:34:31.120Z","repository":{"id":39985310,"uuid":"356151477","full_name":"keithasaurus/koda","owner":"keithasaurus","description":"Type-safe functional tools for Python.","archived":false,"fork":false,"pushed_at":"2024-08-12T03:56:52.000Z","size":295,"stargazers_count":97,"open_issues_count":0,"forks_count":6,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-11-22T19:09:01.593Z","etag":null,"topics":["functional-programming","python","tagged-unions","type-safety"],"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/keithasaurus.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,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-04-09T05:49:06.000Z","updated_at":"2025-07-15T01:17:50.000Z","dependencies_parsed_at":"2023-12-23T20:25:52.018Z","dependency_job_id":"5ebed1ec-bdc9-4472-bcfa-7010942d1f22","html_url":"https://github.com/keithasaurus/koda","commit_stats":{"total_commits":45,"total_committers":4,"mean_commits":11.25,"dds":0.5111111111111111,"last_synced_commit":"162088742d7dd13ba27205666b75a9e421bf5d1e"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/keithasaurus/koda","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keithasaurus%2Fkoda","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keithasaurus%2Fkoda/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keithasaurus%2Fkoda/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keithasaurus%2Fkoda/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/keithasaurus","download_url":"https://codeload.github.com/keithasaurus/koda/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keithasaurus%2Fkoda/sbom","scorecard":{"id":554046,"data":{"date":"2025-08-11","repo":{"name":"github.com/keithasaurus/koda","commit":"0ea55b2784805cf75f89b591dfb8c759b4778947"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Code-Review","score":0,"reason":"Found 1/26 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":"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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/push.yml:1","Info: no jobLevel write permissions found"],"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":"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":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","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":"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":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/push.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/keithasaurus/koda/push.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/push.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/keithasaurus/koda/push.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/push.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/keithasaurus/koda/push.yml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned"],"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":"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":"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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE: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":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"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":"Vulnerabilities","score":6,"reason":"4 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2024-48 / GHSA-fj7x-q9j7-g6q6","Warn: Project is vulnerable to: PYSEC-2025-49 / GHSA-5rjg-fvgr-3xxf","Warn: Project is vulnerable to: GHSA-cx63-2mw6-8hw5","Warn: Project is vulnerable to: PYSEC-2024-187 / GHSA-rqc4-2hc7-8c8v"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 21 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-20T11:50:41.630Z","repository_id":39985310,"created_at":"2025-08-20T11:50:41.630Z","updated_at":"2025-08-20T11:50:41.630Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29608558,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T06:47:36.664Z","status":"ssl_error","status_checked_at":"2026-02-19T06:45:47.551Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["functional-programming","python","tagged-unions","type-safety"],"created_at":"2024-08-01T21:02:03.554Z","updated_at":"2026-02-19T08:34:31.081Z","avatar_url":"https://github.com/keithasaurus.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# Koda\n\nKoda is a collection of practical type-safe tools for Python.\n\nAt its core are a number of datatypes that are common in functional programming.\n\n## Maybe\n\n`Maybe` is similar to Python's `Optional` type. It has two variants: `Nothing` and `Just`, and they work in similar ways\nto what you may have seen in other languages.\n\n```python3\nfrom koda import Maybe, Just, nothing\n\na: Maybe[int] = Just(5)\nb: Maybe[int] = nothing\n```\n\nTo know if a `Maybe` is a `Just` or a `Nothing`, you'll need to inspect it.\n\n```python3\nfrom koda import Just, Maybe\n\nmaybe_str: Maybe[str] = function_returning_maybe_str()\n\n# python 3.10 +\nmatch maybe_str:\n    case Just(val):\n        print(val)\n    case Nothing:\n        print(\"No value!\")\n\n# python 3.9 and earlier\nif isinstance(maybe_str, Just):\n    print(maybe_str.val)\nelse:\n    print(\"No value!\")\n```\n\n`Maybe` has methods for conveniently stringing logic together.\n\n#### Maybe.map\n\n```python3\nfrom koda import Just, nothing\n\ndef add_10(x: int) -\u003e int:\n    return x + 10\n\n\nJust(5).map(add_10)  # Just(15)\nnothing.map(add_10)  # nothing \nJust(5).map(add_10).map(lambda x: f\"abc{x}\")  # Just(\"abc15\")\n```\n\n#### Maybe.flat_map\n\n```python3\nfrom koda import Maybe, Just, nothing\n\n\ndef safe_divide(dividend: int, divisor: int) -\u003e Maybe[float]:\n    if divisor != 0:\n        return Just(dividend / divisor)\n    else:\n        return nothing\n\nJust(5).flat_map(lambda x: safe_divide(10, x))  # Just(2)\nJust(0).flat_map(lambda x: safe_divide(10, x))  # nothing\nnothing.flat_map(lambda x: safe_divide(10, x))  # nothing\n```\n\n## Result\n\n`Result` provides a means of representing whether a computation succeeded or failed. To represent success, we can use `OK`;\nfor failures we can use `Err`. Compared to `Maybe`, `Result` is perhaps most useful in that the \"failure\" case also returns data,\nwhereas `Nothing` contains no data.\n\n```python3\nfrom koda import Ok, Err, Result \n\n\ndef safe_divide_result(dividend: int, divisor: int) -\u003e Result[float, str]:\n    if divisor != 0:\n        return Ok(dividend / divisor)\n    else:\n        return Err(\"cannot divide by zero!\")\n\n\nOk(5).flat_map(lambda x: safe_divide_result(10, x))  # Ok(2)\nOk(0).flat_map(lambda x: safe_divide_result(10, x))  # Err(\"cannot divide by zero!\") \nErr(\"some other error\").map(lambda x: safe_divide_result(10, x))  # Err(\"some other error\")\n```\n\n`Result` can be convenient with `try`/`except` logic.\n```python3\nfrom koda import Result, Ok, Err\n\ndef divide_by(dividend: int, divisor: int) -\u003e Result[float, ZeroDivisionError]:\n    try:\n        return Ok(dividend / divisor)\n    except ZeroDivisionError as exc:\n        return Err(exc)\n\n\ndivided: Result[float, ZeroDivisionError] = divide_by(10, 0)  # Err(ZeroDivisionError(\"division by zero\"))\n```\n\nAnother way to perform the same computation would be to use `safe_try`:\n```python3\nfrom koda import Result, safe_try\n\n\n# not safe on its own!\ndef divide(dividend: int, divisor: int) -\u003e float:\n    return dividend / divisor\n\n# safe if used with `safe_try`\ndivided_ok: Result[float, Exception] = safe_try(divide, 10, 2)  # Ok(5)\ndivided_err: Result[float, Exception] = safe_try(divide, 10, 0)  # Err(ZeroDivisionError(\"division by zero\"))\n```\n\n### Conversion between `Result`s, `Maybe`s, and `Optional`s\n\n### Result and Maybe\n\nConvert a `Result` to a `Maybe` type.\n\n```python3\nfrom koda import Just, nothing, Ok, Err\n\nassert Ok(5).to_maybe == Just(5)\nassert Err(\"any error\").to_maybe == nothing \n```\n\nConvert a `Maybe` to a `Result` type.\n\n```python3\nfrom koda import Just, nothing, Ok, Err\n\nassert nothing.to_result(\"value if nothing\") == Err(\"value if nothing\")\nassert Just(5).to_result(\"value if nothing\") == Ok(5)\n```\n\n### `Maybe` and `Optional`\n\nConvert an `Optional` value to a `Maybe`.\n\n```python3\nfrom koda import to_maybe, Just, nothing\n\nassert to_maybe(5) == Just(5)\nassert to_maybe(\"abc\") == Just(\"abc\")\nassert to_maybe(False) == Just(False)\n\nassert to_maybe(None) == nothing\n```\n\nConvert a `Maybe` to an `Optional`.\n```python3\nfrom koda import Just, nothing\n\nassert Just(5).to_optional == 5\nassert nothing.to_optional is None\n\n# note that `Maybe[None]` will always return None, \n# so `Maybe.get_or_else` would be preferable in this case\nassert Just(None) is None\n```\n\n### `Result` and `Optional`\n\nConvert an `Optional` value to a `Result`.\n\n```python3\nfrom koda import to_result, Ok, Err \n\nassert to_result(5, \"fallback\") == Ok(5)\nassert to_result(\"abc\", \"fallback\") == Ok(\"abc\")\nassert to_result(False, \"fallback\") == Ok(False)\n\nassert to_result(None, \"fallback\") == Err(\"fallback\")\n\n```\n\nConvert a `Result` to an `Optional`.\n```python3\nfrom koda import Ok, Err\n\nassert Ok(5).to_optional == 5\nassert Err(\"some error\").to_optional is None\n\n# note that `Result[None, Any]` will always return None, \n# so `Result.get_or_else` would be preferable in this case\nassert Ok(None) is None\n```\n\n## More\n\nThere are many other functions and datatypes included. Some examples:\n\n### compose\nCombine functions by sequencing.\n\n```python3\nfrom koda import compose\nfrom typing import Callable\n\ndef int_to_str(val: int) -\u003e str:\n    return str(val)\n\ndef prepend_str_abc(val: str) -\u003e str:\n    return f\"abc{val}\"    \n\ncombined_func: Callable[[int], str] = compose(int_to_str, prepend_str_abc)\nassert combined_func(10) == \"abc10\"\n```\n\n### mapping_get\nTry to get a value from a `Mapping` object, and return an unambiguous result.\n\n```python3\nfrom koda import mapping_get, Just, Maybe, nothing\n\nexample_dict: dict[str, Maybe[int]] = {\"a\": Just(1), \"b\": nothing}\n\nassert mapping_get(example_dict, \"a\") == Just(Just(1))\nassert mapping_get(example_dict, \"b\") == Just(nothing)\nassert mapping_get(example_dict, \"c\") == nothing\n```\n\nAs a comparison, note that `dict.get` can return ambiguous results:\n```python\nfrom typing import Optional\n\nexample_dict: dict[str, Optional[int]] = {\"a\": 1, \"b\": None}\n\nassert example_dict.get(\"b\") is None\nassert example_dict.get(\"c\") is None\n```\nWe can't tell from the resulting value whether the `None` was the \nvalue for a key, or whether the key was not present in the `dict`\n\n### load_once\nCreate a lazy function, which will only call the passed-in function\nthe first time it is called. After it is called, the value is cached.\nThe cached value is returned on each successive call.\n```python3\nfrom random import random\nfrom koda import load_once\n\ncall_random_once = load_once(random)  # has not called random yet\n\nretrieved_val: float = call_random_once()\nassert retrieved_val == call_random_once()\n```\n\n## Intent\n\nKoda is intended to focus on a small set of practical data types and utility functions for Python. It will not \ngrow to encompass every possible functional or typesafe concept. Similarly, the intent of this library is to avoid \nrequiring extra plugins (beyond a type-checker like mypy or pyright) or specific typchecker settings. As such,\nit is unlikely that things like Higher Kinded Types emulation or extended type inference will be implemented in this \nlibrary.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeithasaurus%2Fkoda","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkeithasaurus%2Fkoda","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeithasaurus%2Fkoda/lists"}