{"id":41186455,"url":"https://github.com/scardine/doid","last_synced_at":"2026-01-22T20:17:27.716Z","repository":{"id":57423514,"uuid":"150377449","full_name":"scardine/doid","owner":"scardine","description":"Generic object container with filter/order DSL inspired by Django and SQLAlchemy","archived":false,"fork":false,"pushed_at":"2018-09-26T12:19:05.000Z","size":12,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-30T09:09:43.942Z","etag":null,"topics":["dsl","filtering","ordering"],"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/scardine.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":"2018-09-26T06:17:07.000Z","updated_at":"2018-09-26T12:19:07.000Z","dependencies_parsed_at":"2022-09-07T01:11:56.976Z","dependency_job_id":null,"html_url":"https://github.com/scardine/doid","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/scardine/doid","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scardine%2Fdoid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scardine%2Fdoid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scardine%2Fdoid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scardine%2Fdoid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scardine","download_url":"https://codeload.github.com/scardine/doid/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scardine%2Fdoid/sbom","scorecard":{"id":803464,"data":{"date":"2025-08-11","repo":{"name":"github.com/scardine/doid","commit":"f2927133f498422c00eea3079155c212e0a799fc"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"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":"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":"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":"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":"Code-Review","score":0,"reason":"Found 0/3 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":"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":"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":"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":"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":"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"}}]},"last_synced_at":"2025-08-23T11:08:22.568Z","repository_id":57423514,"created_at":"2025-08-23T11:08:22.569Z","updated_at":"2025-08-23T11:08:22.569Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28670366,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-22T19:36:09.361Z","status":"ssl_error","status_checked_at":"2026-01-22T19:36:05.567Z","response_time":144,"last_error":"SSL_read: 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":["dsl","filtering","ordering"],"created_at":"2026-01-22T20:17:27.647Z","updated_at":"2026-01-22T20:17:27.706Z","avatar_url":"https://github.com/scardine.png","language":"Python","readme":"\u003cimg src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/1/1e/Dioptiscyma.jpg/440px-Dioptiscyma.jpg\"\u003e\n\n\u003e **doid** (plural doids)\n\u003e\n\u003e 1. (zoology) Any member of the Doidae, a family of moths.\n\n# DOID - Django ORM inspired DSL\n\n\u003e “Talent borrows. Genius Steals!” - Oscar Wilde.\n\nDOID is a generic DSL for filtering and sorting inspired by projects like \nthe Django ORM and SQLAlchemy. It is a generic container with `filter` and `order_by`\nmethods much like the result set managers from Django. This should be useful\nif you are fond of Django's ORM idioms and would like to use the same patterns\non objects that are not Django Models like objects from API responses.\n\n\n## Filter Interface\n\nA filter object is a callable that receives an object and returns True or False. \nFilters can be combined using the bitwise operators: `\u0026`, `|` and `~`. For example:\n\n    filter4 = filter1 | (filter2 \u0026 filter3)\n    \nYou can turn any callable into a filter using a decorator:\n\n    from doid.filter import doid_filter\n    \n    @doid_filter\n    def milenials(value):\n        return value.born.year \u003e 2000\n        \nFilter should never mutate the value they receive.\n    \n## Sample data\n\nIt is easier to explain using examples. The containers are totally agnostic and \ntake any dataclasses-style object, so lets generate some fake data:\n\n    \u003e\u003e\u003e data =[\n        ['Adrian Mathews', 'Wilsonview', datetime.date(1944, 9, 5), 'PM'],\n        ['Amanda Kaufman', 'East Franklin', datetime.date(1972, 8, 6), 'AM'],\n        ['Benjamin Mcconnell', 'Wilsonview', datetime.date(1928, 8, 8), 'AM'],\n        ['Carolyn Wilcox', 'Lake Benjaminbury', datetime.date(1944, 4, 21), 'PM'],\n        ['Christina White', 'Angelamouth', datetime.date(1963, 10, 22), 'PM'],\n        ['David Berry', 'Lake Benjaminbury', datetime.date(1950, 2, 10), 'PM'],\n        ['Jacob Johnson', 'Wilsonview', datetime.date(1950, 5, 8), 'AM'],\n        ['Jasmine Sanchez', 'Port Janefort', datetime.date(2008, 5, 13), 'AM'],\n        ['John Robinson', 'Angelamouth', datetime.date(1945, 7, 16), 'PM'],\n        ['Kenneth Hernandez', 'Port Janefort', datetime.date(1978, 10, 23), 'PM'],\n        ['Monica Conley', 'East Franklin', datetime.date(1918, 9, 27), 'AM'],\n        ['Paula Melendez', 'East Franklin', datetime.date(2017, 5, 21), 'AM'],\n        ['Robin Harris', 'Angelamouth', datetime.date(1976, 2, 9), 'PM'],\n        ['Sheri Kerr', 'East Franklin', datetime.date(1904, 1, 3), 'AM'],\n        ['Shirley Gray', 'Lake Benjaminbury', datetime.date(1996, 8, 2), 'AM'],\n        ['Stacy Weaver', 'East Franklin', datetime.date(1931, 10, 31), 'PM'],\n        ['Tiffany Sullivan DVM', 'Wilsonview', datetime.date(1909, 1, 7), 'PM'],\n        ['Tracy Norman', 'Wilsonview', datetime.date(1932, 5, 2), 'PM'],\n        ['William Johnson', 'Angelamouth', datetime.date(1950, 3, 27), 'PM'],\n        ['Xavier Harris', 'East Franklin', datetime.date(1903, 10, 5), 'AM']\n    ]\n    \n    \u003e\u003e\u003e class GenericObject(object):\n            def __init__(self, **kwargs):\n                for k, v in kwargs.items():\n                    setattr(self, k, v)\n            def __repr__(self):\n                return \"\u003c\" + \", \".join(f\"{k}={v}\" for k, v in self.__dict__.items()) + \"\u003e\"\n                \n    \u003e\u003e\u003e from doid.container import ListContainer\n    \u003e\u003e\u003e results = ListContainer(\n        GenericObject(name=name, city=city, born=born, ampm=ampm) for\n        name, city, born, ampm in data\n    )\n    \n    \u003e\u003e\u003e results\n    [\u003cname=Adrian Mathews, city=Wilsonview, born=1944-09-05, ampm=PM\u003e,\n     \u003cname=Amanda Kaufman, city=East Franklin, born=1972-08-06, ampm=AM\u003e,\n     \u003cname=Benjamin Mcconnell, city=Wilsonview, born=1928-08-08, ampm=AM\u003e,\n     \u003cname=Carolyn Wilcox, city=Lake Benjaminbury, born=1944-04-21, ampm=PM\u003e,\n     \u003cname=Christina White, city=Angelamouth, born=1963-10-22, ampm=PM\u003e,\n     \u003cname=David Berry, city=Lake Benjaminbury, born=1950-02-10, ampm=PM\u003e,\n     \u003cname=Jacob Johnson, city=Wilsonview, born=1950-05-08, ampm=AM\u003e,\n     \u003cname=Jasmine Sanchez, city=Port Janefort, born=2008-05-13, ampm=AM\u003e,\n     \u003cname=John Robinson, city=Angelamouth, born=1945-07-16, ampm=PM\u003e,\n     \u003cname=Kenneth Hernandez, city=Port Janefort, born=1978-10-23, ampm=PM\u003e,\n     \u003cname=Monica Conley, city=East Franklin, born=1918-09-27, ampm=AM\u003e,\n     \u003cname=Paula Melendez, city=East Franklin, born=2017-05-21, ampm=AM\u003e,\n     \u003cname=Robin Harris, city=Angelamouth, born=1976-02-09, ampm=PM\u003e,\n     \u003cname=Sheri Kerr, city=East Franklin, born=1904-01-03, ampm=AM\u003e,\n     \u003cname=Shirley Gray, city=Lake Benjaminbury, born=1996-08-02, ampm=AM\u003e,\n     \u003cname=Stacy Weaver, city=East Franklin, born=1931-10-31, ampm=PM\u003e,\n     \u003cname=Tiffany Sullivan DVM, city=Wilsonview, born=1909-01-07, ampm=PM\u003e,\n     \u003cname=Tracy Norman, city=Wilsonview, born=1932-05-02, ampm=PM\u003e,\n     \u003cname=William Johnson, city=Angelamouth, born=1950-03-27, ampm=PM\u003e,\n     \u003cname=Xavier Harris, city=East Franklin, born=1903-10-05, ampm=AM\u003e]\n\n    \n## Attribute getter protocol\n\nThe filter protocol folows a attribute-getter protocol much like one used by Django.\nWe can filter by any attribute using `.filter(name=value)`:\n\n    \u003e\u003e\u003e results.filter(city=\"East Franklin\")\n    [\u003cname=Amanda Kaufman, city=East Franklin, born=1972-08-06, ampm=AM\u003e,\n     \u003cname=Monica Conley, city=East Franklin, born=1918-09-27, ampm=AM\u003e,\n     \u003cname=Paula Melendez, city=East Franklin, born=2017-05-21, ampm=AM\u003e,\n     \u003cname=Sheri Kerr, city=East Franklin, born=1904-01-03, ampm=AM\u003e,\n     \u003cname=Stacy Weaver, city=East Franklin, born=1931-10-31, ampm=PM\u003e,\n     \u003cname=Xavier Harris, city=East Franklin, born=1903-10-05, ampm=AM\u003e]\n\nWe can access nested attributes replacing the `.` by double underscores: \n\n    \u003e\u003e\u003e results.filter(born__month=5)\n    [\u003cname=Jacob Johnson, city=Wilsonview, born=1950-05-08, ampm=AM\u003e,\n     \u003cname=Jasmine Sanchez, city=Port Janefort, born=2008-05-13, ampm=AM\u003e,\n     \u003cname=Paula Melendez, city=East Franklin, born=2017-05-21, ampm=AM\u003e,\n     \u003cname=Tracy Norman, city=Wilsonview, born=1932-05-02, ampm=PM\u003e]\n\nSome operators ara available using the same names from the `operator` module, for example\nyou can append `__gt` to represent the `\u003e` operator:\n\n    \u003e\u003e\u003e from doid.filter import Q\n    \u003e\u003e\u003e milenials = Q(born__year__gt=1980)\n    \u003e\u003e\u003e results.filter(milenials)\n    [\u003cname=Jasmine Sanchez, city=Port Janefort, born=2008-05-13, ampm=AM\u003e,\n     \u003cname=Paula Melendez, city=East Franklin, born=2017-05-21, ampm=AM\u003e,\n     \u003cname=Shirley Gray, city=Lake Benjaminbury, born=1996-08-02, ampm=AM\u003e]\n\nWe can filter using regular expressions by appending `__match`:\n\n    \u003e\u003e\u003e results.filter(name__match='^S')\n    [\u003cname=Sheri Kerr, city=East Franklin, born=1904-01-03, ampm=AM\u003e,\n     \u003cname=Shirley Gray, city=Lake Benjaminbury, born=1996-08-02, ampm=AM\u003e,\n     \u003cname=Stacy Weaver, city=East Franklin, born=1931-10-31, ampm=PM\u003e]\n\nLike in the Django ORM, methods can be chained:\n\n    \u003e\u003e\u003e results.filter(name__match='^S').filter(milenials)\n    [\u003cname=Shirley Gray, city=Lake Benjaminbury, born=1996-08-02, ampm=AM\u003e]\n    \nThis is the same as:\n\n    \u003e\u003e\u003e results.filter(name__match='^S', milenials)\n    [\u003cname=Shirley Gray, city=Lake Benjaminbury, born=1996-08-02, ampm=AM\u003e]\n    \nAgain like in Django, we can express OR filters using Q objects:\n\n    \u003e\u003e\u003e results.filter(Q(name__match='^S') | milenials)\n    [\u003cname=Jasmine Sanchez, city=Port Janefort, born=2008-05-13, ampm=AM\u003e,\n     \u003cname=Paula Melendez, city=East Franklin, born=2017-05-21, ampm=AM\u003e,\n     \u003cname=Sheri Kerr, city=East Franklin, born=1904-01-03, ampm=AM\u003e,\n     \u003cname=Shirley Gray, city=Lake Benjaminbury, born=1996-08-02, ampm=AM\u003e,\n     \u003cname=Stacy Weaver, city=East Franklin, born=1931-10-31, ampm=PM\u003e]\n    \nThe `order_by` method accepts strings following the same protocol (keyword\narguments will be passed directly to the sort method):\n\n    \u003e\u003e\u003e results.order_by('born__year')[:5]\n    [\u003cname=Xavier Harris, city=East Franklin, born=1903-10-05, ampm=AM\u003e,\n     \u003cname=Sheri Kerr, city=East Franklin, born=1904-01-03, ampm=AM\u003e,\n     \u003cname=Tiffany Sullivan DVM, city=Wilsonview, born=1909-01-07, ampm=PM\u003e,\n     \u003cname=Monica Conley, city=East Franklin, born=1918-09-27, ampm=AM\u003e,\n     \u003cname=Benjamin Mcconnell, city=Wilsonview, born=1928-08-08, ampm=AM\u003e,\n\n    \u003e\u003e\u003e results.order_by('born__year', reverse=True)[:5]\n    [\u003cname=Paula Melendez, city=East Franklin, born=2017-05-21, ampm=AM\u003e,\n     \u003cname=Jasmine Sanchez, city=Port Janefort, born=2008-05-13, ampm=AM\u003e,\n     \u003cname=Shirley Gray, city=Lake Benjaminbury, born=1996-08-02, ampm=AM\u003e,\n     \u003cname=Kenneth Hernandez, city=Port Janefort, born=1978-10-23, ampm=PM\u003e,\n     \u003cname=Robin Harris, city=Angelamouth, born=1976-02-09, ampm=PM\u003e]\n       \n## Other ideas\n\nThis is pretty much a work in progress. Some ideas are:\n\n 1. Implement getters that works also for dicts and lists\n 1. Implement getters that fail gracefully it the attribute/key does not exist\n 1. Create other container types like ordered sets\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscardine%2Fdoid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscardine%2Fdoid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscardine%2Fdoid/lists"}