{"id":48573487,"url":"https://github.com/polydojo/dotsi","last_synced_at":"2026-04-08T15:35:05.534Z","repository":{"id":46886502,"uuid":"307431835","full_name":"polydojo/dotsi","owner":"polydojo","description":"Dot-accessible, update-aware Python dicts (\u0026 lists). Works recursively, like a charm.","archived":false,"fork":false,"pushed_at":"2020-11-22T16:48:43.000Z","size":19,"stargazers_count":33,"open_issues_count":4,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-25T16:43:38.280Z","etag":null,"topics":["dict","dictionaries","dot-notation","nested","pypi","python","python-library","recursive","types"],"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/polydojo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-26T16:14:55.000Z","updated_at":"2025-07-14T09:06:42.000Z","dependencies_parsed_at":"2022-09-26T18:31:54.139Z","dependency_job_id":null,"html_url":"https://github.com/polydojo/dotsi","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/polydojo/dotsi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polydojo%2Fdotsi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polydojo%2Fdotsi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polydojo%2Fdotsi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polydojo%2Fdotsi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/polydojo","download_url":"https://codeload.github.com/polydojo/dotsi/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polydojo%2Fdotsi/sbom","scorecard":{"id":740385,"data":{"date":"2025-08-11","repo":{"name":"github.com/polydojo/dotsi","commit":"7aa332a086200d1aeb0e02545223535ff4a28ebb"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"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":"Code-Review","score":0,"reason":"Found 0/8 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":"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":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"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":-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":"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":"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":"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":"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":"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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"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"}}]},"last_synced_at":"2025-08-22T17:15:29.120Z","repository_id":46886502,"created_at":"2025-08-22T17:15:29.120Z","updated_at":"2025-08-22T17:15:29.120Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31562691,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T14:31:17.711Z","status":"ssl_error","status_checked_at":"2026-04-08T14:31:17.202Z","response_time":54,"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":["dict","dictionaries","dot-notation","nested","pypi","python","python-library","recursive","types"],"created_at":"2026-04-08T15:35:04.841Z","updated_at":"2026-04-08T15:35:05.519Z","avatar_url":"https://github.com/polydojo.png","language":"Python","readme":"Dotsi\n=====\n\nDot-accessible, update-aware Python dicts (\u0026 lists). Works recursively, like a charm.\n\nDotsi defines two classes, `dotsi.Dict` and `dotsi.List`, which *work together* to bring JavaScript-like dot-notation to Python dicts (and lists therein).\n\nInstallation\n--------------\n```\npip install dotsi\n```\nAlternately, download `dotsi.py` it into your project directory.\n\nUsage\n--------\n\nLet's dive right in:\n\n```py\n\u003e\u003e\u003e import dotsi\n\u003e\u003e\u003e \n\u003e\u003e\u003e d = dotsi.Dict({\"foo\": {\"bar\": \"baz\"}})     # Basic\n\u003e\u003e\u003e d.foo.bar\n'baz'\n\u003e\u003e\u003e d.users = [{\"id\": 0, \"name\": \"Alice\"}]   # List\n\u003e\u003e\u003e d.users[0].name\n'Alice'\n\u003e\u003e\u003e d.users.append({\"id\": 1, \"name\": \"Becca\"}); # Append\n\u003e\u003e\u003e d.users[1].name\n'Becca'\n\u003e\u003e\u003e d.users += [{\"id\": 2, \"name\": \"Cathy\"}];    # `+=`\n\u003e\u003e\u003e d.users[2].name\n'Cathy'\n\u003e\u003e\u003e d.update({\"tasks\": [{\"id\": \"a\", \"text\": \"Task A\"}]});\n\u003e\u003e\u003e d.tasks[0].text\n'Task A'\n\u003e\u003e\u003e d.tasks[0].tags = [\"red\", \"white\", \"blue\"];\n\u003e\u003e\u003e d.tasks[0].tags[2];\n'blue'\n\u003e\u003e\u003e d.tasks[0].pop(\"tags\")                      # `.pop()`\n['red', 'white', 'blue']\n\u003e\u003e\u003e \n\u003e\u003e\u003e import pprint\n\u003e\u003e\u003e pprint.pprint(d)\n{'foo': {'bar': 'baz'},\n 'tasks': [{'id': 'a', 'text': 'Task A'}],\n 'users': [{'id': 0, 'name': 'Alice'},\n           {'id': 1, 'name': 'Becca'},\n           {'id': 2, 'name': 'Cathy'}]}\n\u003e\u003e\u003e \n\u003e\u003e\u003e type(d.users)       # dotsi.Dict (AKA dotsi.DotsiDict)\n\u003cclass 'dotsi.DotsiList'\u003e\n\u003e\u003e\u003e type(d.users[0])    # dotsi.List (AKA dotsi.DotsiList)\n\u003cclass 'dotsi.DotsiDict'\u003e \n\u003e\u003e\u003e \n```\n\nIn the above example, while we explicitly initialized `d` as an `dotsi.Dict`:\n- `d.users` automatically became a `dotsi.List`.\n- `d.users[0]` automatically became a `dotsi.Dict`.\n\nDotsi vs Others\n-------------------\n\n#### Addict:\n\nAt Polydojo, we've been using [Addict](https://github.com/mewwts/addict) for quite some time. It's a great library! But it doesn't play well with list-nested (inner) dicts.\n\n```py\n\u003e\u003e\u003e import addict\n\u003e\u003e\u003e \n\u003e\u003e\u003e d = addict.Dict({\"foo\": {\"bar\": \"baz\"}})\n\u003e\u003e\u003e d.foo\n{'bar': 'baz'}\n\u003e\u003e\u003e d.users = [{\"id\": 0, \"name\": \"Alice\"}]\n\u003e\u003e\u003e d.users[0].name\nTraceback (most recent call last):\n  File \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\nAttributeError: 'dict' object has no attribute 'name'\n\u003e\u003e\u003e \n```\n\n#### EasyDict:\n\n[EasyDict](https://github.com/makinacorpus/easydict) is another great library. It works recursively, but doesn't fully support list-nested dict updates.\n\n```py\n\u003e\u003e\u003e import easydict\n\u003e\u003e\u003e \n\u003e\u003e\u003e d = easydict.EasyDict({\"foo\": {\"bar\": \"baz\"}})\n\u003e\u003e\u003e d.foo\n{'bar': 'baz'}\n\u003e\u003e\u003e d.users = [{\"id\": 0, \"name\": \"Alice\"}]\n\u003e\u003e\u003e d.users[0].name\n'Alice'\n\u003e\u003e\u003e d.users.append({\"id\": 1, \"name\": \"Becca\"});\n\u003e\u003e\u003e d.users[1].name\nTraceback (most recent call last):\n  File \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\nAttributeError: 'dict' object has no attribute 'name'\n\u003e\u003e\u003e \n```\n\nShortcuts\n------------\nClasses:\n- `dotsi.Dict` is a short alias for `dotsi.DotsiDict`.\n- `dotsi.List` is a short alias for `dotsi.DotsiList`.\n\nFunctions:\n- `dotsi.dotsify()` calls `dotsi.Dict`/`dotsi.List`, as appropriate.\n- `dotsi.fy()` is a short alias for `dotsi.dotsify()`.\n- `dotsi.mapdotsify()` is like the built-in `map()`, but returns a `dotsi.List`.\n- `dotsi.mapfy` is a short alias for `dotsi.mapdotsify()`. (More on this below.)\n\nIn most cases, all you need is:\n- `dotsi.fy(thing)`, where `thing` is a `dict` or `list`.\n\nDict-Like Objects\n----------------------\nWhile `dotsi.fy()` converts objects of type `dict` to `dotsi.Dict`, it ***doesn't*** touch other dict-like objects, such as those of type `collections.OrderedDict` or `http.cookies.SimpleCookie`.\n\nTo convert a non-`dict`, but dict-like object to `dotsi.Dict`, use `dotsi.Dict(.)` directly, or use `dotsi.fy(dict(.))`.\n\n```py\n\u003e\u003e\u003e import dotsi\n\u003e\u003e\u003e from collections import OrderedDict\n\u003e\u003e\u003e \n\u003e\u003e\u003e d = OrderedDict({\"foo\": {\"bar\": \"baz\"}})\n\u003e\u003e\u003e d\nOrderedDict([('foo', {'bar': 'baz'})])\n\u003e\u003e\u003e type(d)\n\u003cclass 'collections.OrderedDict'\u003e\n\u003e\u003e\u003e\n\u003e\u003e\u003e x = dotsi.fy(d)\n\u003e\u003e\u003e x\nOrderedDict([('foo', {'bar': 'baz'})])\n\u003e\u003e\u003e type(x)\n\u003cclass 'collections.OrderedDict'\u003e\n\u003e\u003e\u003e \n\u003e\u003e\u003e y = dotsi.Dict(d)\n\u003e\u003e\u003e y\n{'foo': {'bar': 'baz'}}\n\u003e\u003e\u003e type(y)\n\u003cclass 'dotsi.DotsiDict'\u003e\n\u003e\u003e\u003e \n\u003e\u003e\u003e z = dotsi.fy(dict(d))\n\u003e\u003e\u003e z\n{'foo': {'bar': 'baz'}}\n\u003e\u003e\u003e type(z)\n\u003cclass 'dotsi.DotsiDict'\u003e\n```\n\nSubclasses of `dict`, such as `http.cookie.SimpleCookie`, often implement custom behavior, which would be lost on conversion to `dotsi.Dict`. Thus, automatic conversion shouldn't be implemented.\n\nQuick Plug\n--------------\nDotsi is built and maintained by the folks at [Polydojo, Inc.](https://www.polydojo.com/), led by [Sumukh Barve](https://www.sumukhbarve.com/). If your team is looking for a simple project management tool, please check out our latest product: [**BoardBell.com**](https://www.boardbell.com/).\n\nList-Like Objects\n--------------------\n\nLike with dicts, `dotsi.fy(.)` only converts objects of type `list` to `dotsi.List`, but doesn't touch other list-like objects or tuples. To convert a non-`list`, but list-like object to `dotsi.List`, directly call `dotsi.List(.)` or use `dotsi.fy(list(.))`\n\n#### Identity Function\n\nFor non-`dict` and non-`list` objects, `dotsi.fy(.)` is equivalent to the identity function.\n\nKindly note that from Python3+, the built-in `map()` produces a non-`list` iterable. Thus, calling `dotsi.fy(map(.))` is equivalent to just `map(.)`. Instead, please use `dotsi.List(map(.))`.\n\n\n#### Mapping Helper\n\nAs mapping is a pretty-common use case, we've included `dotsi.mapfy(.)`, which is essentially equivalent to `dotsi.List(map(.))`. But additionally, with `dotsi.mapfy(.)`, for mapping onto a *single* sequence, you may pass arguments in either order.\n\nThat is, the following lines are equivalent:\n- `x = dotsi.mapfy(lambda n: {\"n\": n}, [0, 1, 2])`\n- `x = dotsi.mapfy([0, 1, 2], lambda n: {\"n\": n})`\n\nIn either case, `x[0].n == 0` will be `True`.\n\nWhen mapping onto *multiple* sequences, `dotsi.mapfy(.)` expects the same order of arguments as `map(.)`.\n\nOverridden Methods\n--------------------------\nExcluding magic-methods like `.__init__(.)` etc., methods overridden by Dotsi are listed below.\n\n#### `dotsi.Dict` overrides:\n- `.update(.)`\n- `.setdefault(.)`\n- `.copy(.)`\n\n#### `dotsi.List` overrides:\n- `insert(.)`\n- `append(.)`\n- `extend(.)`\n- `copy(.)`\n\nSignatures for all overridden methods should be equivalent (if not exactly identical) to their non-overridden counterparts.\n\nUnderscore-Like Helpers\n------------------------------\n\nThe following helper functions are inspired by their counterparts in [Underscore.js](https://underscorejs.org/).\n\n- `dotsi.extend(tgt, *srcs)`\n- `dotsi.defaults(tgt, *srcs)`\n\nNote that these helpers *don't* `dotsi.fy()` the result. While `tgt` is updated (and returned), it's type remains unchanged.\n\n\nLicensing\n------------\nCopyright (c) 2020 Polydojo, Inc.\n\n**Software Licensing:**  \nThe software is released \"AS IS\" under the **MIT license**, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Kindly see [LICENSE.txt](https://github.com/polydojo/dotsi/blob/master/LICENSE.txt) for more details.\n\n**No Trademark Rights:**  \nThe above software licensing terms **do not** grant any right in the trademarks, service marks, brand names or logos of Polydojo, Inc.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpolydojo%2Fdotsi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpolydojo%2Fdotsi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpolydojo%2Fdotsi/lists"}