{"id":13578632,"url":"https://github.com/ingolemo/python-lenses","last_synced_at":"2026-02-18T23:32:41.514Z","repository":{"id":44758341,"uuid":"46625565","full_name":"ingolemo/python-lenses","owner":"ingolemo","description":"A python lens library for manipulating deeply nested immutable structures","archived":false,"fork":false,"pushed_at":"2023-11-15T16:29:31.000Z","size":569,"stargazers_count":327,"open_issues_count":4,"forks_count":18,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-10-21T19:55:57.606Z","etag":null,"topics":["functional-optics","functional-programming","immutable","immutable-datastructures","lens","lenses","optics","prism","python","traversal"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ingolemo.png","metadata":{"files":{"readme":"readme.rst","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}},"created_at":"2015-11-21T17:25:49.000Z","updated_at":"2025-10-03T10:11:04.000Z","dependencies_parsed_at":"2022-09-03T13:21:44.041Z","dependency_job_id":"b8336d4d-9abd-4db4-a9ed-4d8947831197","html_url":"https://github.com/ingolemo/python-lenses","commit_stats":{"total_commits":478,"total_committers":11,"mean_commits":43.45454545454545,"dds":0.05020920502092052,"last_synced_commit":"f5eb8a86d47cbf177365e7090674f765e52227b4"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/ingolemo/python-lenses","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ingolemo%2Fpython-lenses","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ingolemo%2Fpython-lenses/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ingolemo%2Fpython-lenses/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ingolemo%2Fpython-lenses/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ingolemo","download_url":"https://codeload.github.com/ingolemo/python-lenses/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ingolemo%2Fpython-lenses/sbom","scorecard":{"id":488649,"data":{"date":"2025-08-11","repo":{"name":"github.com/ingolemo/python-lenses","commit":"f5eb8a86d47cbf177365e7090674f765e52227b4"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 1/29 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":"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":"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":"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":"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":"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":"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":"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: GNU General Public License v3.0: 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":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 2 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-19T18:29:01.678Z","repository_id":44758341,"created_at":"2025-08-19T18:29:01.678Z","updated_at":"2025-08-19T18:29:01.678Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29598247,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T22:25:43.180Z","status":"ssl_error","status_checked_at":"2026-02-18T22:25:42.766Z","response_time":162,"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-optics","functional-programming","immutable","immutable-datastructures","lens","lenses","optics","prism","python","traversal"],"created_at":"2024-08-01T15:01:32.407Z","updated_at":"2026-02-18T23:32:41.474Z","avatar_url":"https://github.com/ingolemo.png","language":"Python","readme":"\nLenses\n======\n\nLenses is a python library that helps you to manipulate large\ndata-structures without mutating them. It is inspired by the lenses in\nHaskell, although it's much less principled and the api is more suitable\nfor python.\n\n\nInstallation\n------------\n\nYou can install the latest version from pypi using pip like so::\n\n    pip install lenses\n\nYou can uninstall similarly::\n\n    pip uninstall lenses\n\n\nDocumentation\n-------------\n\nThe lenses library makes liberal use of docstrings, which you can access\nas normal with the ``pydoc`` shell command, the ``help`` function in\nthe repl, or by reading the source yourself.\n\nMost users will only need the docs from ``lenses.UnboundLens``. If you\nwant to add hooks to allow parts of the library to work with custom\nobjects then you should check out the ``lenses.hooks`` module. Most of\nthe fancy lens code is in the ``lenses.optics`` module for those who\nare curious how everything works.\n\nSome examples are given in the `examples`_ folder and the `documentation`_\nis available on ReadTheDocs.\n\n.. _examples: examples\n.. _documentation: https://python-lenses.readthedocs.io/en/latest/\n\n\nExample\n-------\n\n.. code:: pycon\n\n    \u003e\u003e\u003e from pprint import pprint\n    \u003e\u003e\u003e from lenses import lens\n    \u003e\u003e\u003e\n    \u003e\u003e\u003e data = [{'name': 'Jane', 'scores': ['a', 'a', 'b', 'a']},\n    ...         {'name': 'Richard', 'scores': ['c', None, 'd', 'c']},\n    ...         {'name': 'Zoe', 'scores': ['f', 'f', None, 'f']}]\n    ... \n    \u003e\u003e\u003e format_scores = lens.Each()['scores'].Each().Instance(str).call_upper()\n    \u003e\u003e\u003e cheat = lens[2]['scores'].Each().set('a')\n    \u003e\u003e\u003e\n    \u003e\u003e\u003e corrected = format_scores(data)\n    \u003e\u003e\u003e pprint(corrected)\n    [{'name': 'Jane', 'scores': ['A', 'A', 'B', 'A']},\n     {'name': 'Richard', 'scores': ['C', None, 'D', 'C']},\n     {'name': 'Zoe', 'scores': ['F', 'F', None, 'F']}]\n    \u003e\u003e\u003e\n    \u003e\u003e\u003e cheated = format_scores(cheat(data))\n    \u003e\u003e\u003e pprint(cheated)\n    [{'name': 'Jane', 'scores': ['A', 'A', 'B', 'A']},\n     {'name': 'Richard', 'scores': ['C', None, 'D', 'C']},\n     {'name': 'Zoe', 'scores': ['A', 'A', 'A', 'A']}]\n\n\nThe definition of ``format_scores`` means \"for each item in the data take\nthe value with the key of ``'scores'`` and then for each item in that list\nthat is an instance of ``str``, call its ``upper`` method on it\". That one\nline is the equivalent of this code:\n\n.. code:: python\n\n    def format_scores(data):\n        results = []\n        for entry in data:\n            result = {}\n            for key, value in entry.items():\n                if key == 'scores':\n                    new_value = []\n                    for letter in value:\n                        if isinstance(letter, str):\n                            new_value.append(letter.upper())\n                        else:\n                            new_value.append(letter)\n                    result[key] = new_value\n                else:\n                    result[key] = value\n            results.append(result)\n        return results\n\nNow, this code can be simplified using comprehensions. But comprehensions\nonly work with lists, dictionaries, and sets, whereas the lenses library\ncan work with arbitrary python objects.\n\nHere's an example that shows off the full power of this library:\n\n.. code:: pycon\n\n    \u003e\u003e\u003e from lenses import lens\n    \u003e\u003e\u003e state = ((\"foo\", \"bar\"), \"!\", 2, ())\n    \u003e\u003e\u003e lens.Recur(str).Each().Filter(lambda c: c \u003c= 'm').Parts().call_mut_reverse()(state)\n    (('!oo', 'abr'), 'f', 2, ())\n\nThis is an example from the `Putting Lenses to Work`__ talk about the\nhaskell lenses library by John Wiegley. We extract all the strings inside\nof ``state``, extract the characters, filter out any characters that\ncome after ``'m'`` in the alphabet, treat these characters as if they\nwere a list, reverse that list, before finally placing these characters\nback into the state in their new positions.\n\n.. _putting_lenses_to_work: https://www.youtube.com/watch?v=QZy4Yml3LTY\u0026t=2250\n\n__ putting_lenses_to_work_\n\nThis example is obviously very contrived, but I can't even begin to\nimagine how you would do this in python code without lenses.\n\n\nLicense\n-------\n\npython-lenses is free software: you can redistribute it and/or modify it\nunder the terms of the GNU General Public License as published by the\nFree Software Foundation, either version 3 of the License, or (at your\noption) any later version.\n\nThis program is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\nPublic License for more details.\n\nYou should have received a copy of the GNU General Public License along\nwith this program. If not, see http://www.gnu.org/licenses/.\n","funding_links":[],"categories":["Python","Awesome Functional Python","\u003ca name=\"Python\"\u003e\u003c/a\u003ePython"],"sub_categories":["Libraries"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fingolemo%2Fpython-lenses","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fingolemo%2Fpython-lenses","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fingolemo%2Fpython-lenses/lists"}