{"id":30735706,"url":"https://github.com/ramazanpolat/prodict","last_synced_at":"2025-09-03T20:13:38.801Z","repository":{"id":28641572,"uuid":"118947873","full_name":"ramazanpolat/prodict","owner":"ramazanpolat","description":"Prodict, what Python dict meant to be.","archived":false,"fork":false,"pushed_at":"2024-11-18T14:28:14.000Z","size":154,"stargazers_count":152,"open_issues_count":21,"forks_count":15,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-08-16T02:33:24.342Z","etag":null,"topics":["conversion","dictionary","dynamic-props","hints","ide","json","python","python3","typehinting"],"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/ramazanpolat.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-01-25T17:56:04.000Z","updated_at":"2025-07-04T00:48:19.000Z","dependencies_parsed_at":"2024-06-18T17:08:56.059Z","dependency_job_id":"d3db25f1-5c78-49cd-ac02-e9f18b172783","html_url":"https://github.com/ramazanpolat/prodict","commit_stats":{"total_commits":96,"total_committers":4,"mean_commits":24.0,"dds":0.03125,"last_synced_commit":"e73e8528652a5ef1bdbcf2ce288e121a82f052cb"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/ramazanpolat/prodict","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramazanpolat%2Fprodict","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramazanpolat%2Fprodict/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramazanpolat%2Fprodict/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramazanpolat%2Fprodict/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ramazanpolat","download_url":"https://codeload.github.com/ramazanpolat/prodict/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramazanpolat%2Fprodict/sbom","scorecard":{"id":760633,"data":{"date":"2025-08-11","repo":{"name":"github.com/ramazanpolat/prodict","commit":"5e5f3bccbfaae63c309ac045da912c02f34f60ec"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.3,"checks":[{"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":"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":"Code-Review","score":2,"reason":"Found 4/18 approved changesets -- score normalized to 2","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":"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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/pythonapp.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":"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/pythonapp.yml:9: update your workflow using https://app.stepsecurity.io/secureworkflow/ramazanpolat/prodict/pythonapp.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pythonapp.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/ramazanpolat/prodict/pythonapp.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pythonapp.yml:40: update your workflow using https://app.stepsecurity.io/secureworkflow/ramazanpolat/prodict/pythonapp.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pythonapp.yml:42: update your workflow using https://app.stepsecurity.io/secureworkflow/ramazanpolat/prodict/pythonapp.yml/master?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/pythonapp.yml:16","Warn: pipCommand not pinned by hash: .github/workflows/pythonapp.yml:17","Warn: pipCommand not pinned by hash: .github/workflows/pythonapp.yml:18","Warn: pipCommand not pinned by hash: .github/workflows/pythonapp.yml:21","Warn: pipCommand not pinned by hash: .github/workflows/pythonapp.yml:28","Warn: pipCommand not pinned by hash: .github/workflows/pythonapp.yml:32","Warn: pipCommand not pinned by hash: .github/workflows/pythonapp.yml:47","Warn: pipCommand not pinned by hash: .github/workflows/pythonapp.yml:48","Warn: pipCommand not pinned by hash: .github/workflows/pythonapp.yml:49","Warn: pipCommand not pinned by hash: .github/workflows/pythonapp.yml:52","Warn: pipCommand not pinned by hash: .github/workflows/pythonapp.yml:59","Warn: pipCommand not pinned by hash: .github/workflows/pythonapp.yml:63","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of  12 pipCommand 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":"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":"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":"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":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact 0.8.6 not signed: https://api.github.com/repos/ramazanpolat/prodict/releases/26165379","Warn: release artifact 0.8.2 not signed: https://api.github.com/repos/ramazanpolat/prodict/releases/18312448","Warn: release artifact 0.8.6 does not have provenance: https://api.github.com/repos/ramazanpolat/prodict/releases/26165379","Warn: release artifact 0.8.2 does not have provenance: https://api.github.com/repos/ramazanpolat/prodict/releases/18312448"],"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 20 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-22T23:22:06.668Z","repository_id":28641572,"created_at":"2025-08-22T23:22:06.668Z","updated_at":"2025-08-22T23:22:06.668Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273502354,"owners_count":25117173,"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-03T02:00:09.631Z","response_time":76,"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":["conversion","dictionary","dynamic-props","hints","ide","json","python","python3","typehinting"],"created_at":"2025-09-03T20:13:24.226Z","updated_at":"2025-09-03T20:13:38.792Z","avatar_url":"https://github.com/ramazanpolat.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Prodict\nProdict = Dictionary with IDE friendly(auto code completion) and dot-accessible attributes **and more**.\n\n# What it does\nEver wanted to use a `dict` like a class and access keys as attributes? **Prodict** does exactly this. \n\nAlthough there are number of modules doing this, **Prodict** does a little bit **more**.\n\nYou can provide type hints and get auto code completion!\n\nWith type hints, you also get nested object instantiations, which will blow your mind.\n\nYou will never want to use `dict` again.\n\n# Why?\n* Because accessing `dict` keys like `d['key']` is error prone and ugly.\n\n* Because it becomes uglier if it is nested, like `d['key1]['key2']['key3']`. Compare `d['key1]['key2']['key3']` to `d.key1.key2.key3`, which one looks better?\n\n* Because since web technologies mostly talk with JSON, it should be much more easy to use JSON data(see sample use case below).\n\n* Because auto code completion makes developers' life easier.\n\n* Because serializing a Python class to `dict` and deserializing from `dict` in one line is awesome!\n \n\n# Features\n\n  1) A class with dynamic properties, without defining it beforehand.\n\n```python\nj = Prodict()\nj.hi = 'there'\n```\n\n  2) Pass named arguments and all arguments will become properties.\n\n```python\np = Prodict(lang='Python', pros='Rocks!')\nprint(p.lang)  # Python\nprint(p.pros)  # Rocks!\nprint(p)  # {'lang': 'Python', 'pros': 'Rocks!'}\n```\n\n  3) Instantiate from a `dict`, get `dict` keys as properties\n```python\np = Prodict.from_dict({'lang': 'Python', 'pros': 'Rocks!'})\nprint(p.lang)   # Python\np.another_property = 'this is dynamically added'\n```\n\n  4) Pass a `dict` as argument, get a nested `Prodict`!\n```python\np = Prodict(package='Prodict', makes='Python', rock={'even': 'more!'})\nprint(p)  #  {'package': 'Prodict', 'makes': 'Python', 'rock': {'even': 'more!'}}\nprint(p.rock.even)  #  'more!'\nprint(type(p.rock))  # \u003cclass 'prodict.Prodict'\u003e\n```\n\n  5) Extend `Prodict` and use type annotations for auto type conversion and auto code completion\n```python\nclass User(Prodict):\n    user_id: int\n    name: str\n\nuser = User(user_id=\"1\", name=\"Ramazan\")\ntype(user.user_id) # \u003cclass 'int'\u003e\n# IDE will be able to auto complete 'user_id' and 'name' properties(see example 1 below)\n```\nWhy type conversion? Because it will be useful if the incoming data doesn't have the desired type.\n\n```python\nclass User(Prodict):\n    user_id: int\n    name: str\n    literal: Any\n    \nresponse = requests.get(\"https://some.restservice.com/user/1\").json()\nuser: User = User.from_dict(response)\ntype(user.user_id) # \u003cclass 'int'\u003e\n```\n\n**Notes on automatic type conversion**:\n* In the above example code, `user.user_id` will be an `int`, even if rest service responded with `str`.\n* Same goes for all built-in types(int, str, float, bool, list, tuple), except `dict`. Because by default, all `dict` types will be converted to `Prodict`.\n* If you don't want any type conversion but still want to have auto code completion, use `Any` as type annotation, like the `literal` attribute defined in `User` class.\n* If the annotated type of an attribute is sub-class of a `Prodict`, the provided `dict` will be instantiated as the instance of sub-class. Even if it is `List` of the sub-class(see sample usa case below).\n\n\n\n# Sample use case\n\nSuppose that you are getting this JSON response from `https://some.restservice.com/user/1`:\n\n```javascript\n{\n  user_id: 1,\n  user_name: \"rambo\",\n  posts: [\n    {\n      title:\"Hello World\",\n      text:\"This is my first blog post...\",\n      date:\"2018-01-02 03:04:05\",\n      comments: [\n          {\n            user_id:2,\n            comment:\"Good to see you blogging\",\n            date:\"2018-01-02 03:04:06\"\n          },\n          {\n            user_id:3,\n            comment:\"Good for you\",\n            date:\"2018-01-02 03:04:07\"\n          }\n        ]\n    },\n    {\n      title:\"Leave the old behind\",\n      text:\"Stop using Python 2.x...\",\n      date:\"2018-02-03 04:05:06\",\n      comments: [\n          {\n            user_id:4,\n            comment:\"Python 2 is dead, long live Python\",\n            date:\"2018-02-03 04:05:07\"\n          },\n          {\n            user_id:5,\n            comment:\"You are god damn right :wears Heissenberg glasses:\",\n            date:\"2018-02-03 04:05:08\"\n          }\n        ]\n    }\n  ]\n}\n```\nDespite the fact that JSON being schemaless, most REST services will respond with a certain structure.\nIn the above example, the structure is something like this:\n```\nUser\n |--\u003e user_id\n |--\u003e user_name\n |--\u003e posts [post]\n       |--\u003e title\n       |--\u003e text\n       |--\u003e date\n       |--\u003e comments [comment]\n             |--\u003e user_id\n             |--\u003e comment\n             |--\u003e date\n```\n\nAnd you want to convert this to appropriate Python classes.\n\nWithout `Prodict`:\n\n```python\nclass Comment:\n\tdef __init__(self, user_id, comment, date):\n    \tself.user_id = user_id\n    \tself.comment = comment\n        self.date = date\n        \nclass Post:\n    def __init__(self, title, text, date):\n    \tself.title = title\n        self.text = text\n        self.date = date\n        self.comments = []\n        \nclass User:\n    def __init__(self, user_id, user_name):\n        self.user_id = user_id\n        self.user_name = user_name\n        self.posts = []\n\nuser_json = requests.get(\"https://some.restservice.com/user/1\").json()\nposts = [Post(post['title'], post['text'], post['date']) for post in user_json['posts']]\nfor post in posts:\n    post.comments = [[comment for comment in post['comments']] for post in user_json['posts']]\n\nuser = User(user_json['user_id'], user_json['user_name'])\nuser.posts = posts\n\nfor post in user.posts:\n    print(post.title)\n\n```\n\nWith **Prodict** you just need to define the classes and let the prodict do the rest like this:\n\n```python\nclass Comment(Prodict):\n    user_id: int\n    comment: str\n    date: str\n\nclass Post(Prodict):\n    title: str\n    text: str\n    date: str\n    comments: List[Comment]\n        \nclass User(Prodict):\n    user_id: int\n    user_name: str\n    posts: List[Post]\n\nuser_json = requests.get(\"https://some.restservice.com/user/1\").json()\nuser:User = User.from_dict(user_json)\n# Don't forget to annotate the `user` with `User` type in order to get auto code completion.\n```\n\nSee the difference?\nPlus you can add new attributes to `User`, `Post` and `Comment` objects dynamically and access them as dot-accessible attributes.\n\n\n# Examples\n\n**Example 0**: Use it like regular `dict`, because **it is** a dict.\n```python\n\nfrom prodict import Prodict\n\nd = dict(lang='Python', pros='Rocks!')\np = Prodict(lang='Python', pros='Rocks!')\n\nprint(d)  # {'lang': 'Python', 'pros': 'Rocks!'}\nprint(p)  # {'lang': 'Python', 'pros': 'Rocks!'}\nprint(d == p)  # True\n\np2 = Prodict.from_dict({'Hello': 'world'})\n\nprint(p2)  # {'Hello': 'world'}\nprint(issubclass(Prodict, dict))  # True\nprint(isinstance(p, dict))  # True\nprint(set(dir(dict)).issubset(dir(Prodict)))  # True\n```\n\n**Example 1**: Accessing keys as attributes and auto completion.\n```python\nfrom prodict import Prodict\nclass Country(Prodict):\n    name: str\n    population: int\n\nturkey = Country()\nturkey.name = 'Turkey'\nturkey.population = 79814871\n```\n\n![auto code complete](/auto-complete1.png?raw=true \"Auto complete in action!\")\n\n**Example 2**: Auto type conversion\n```python\ngermany = Country(name='Germany', population='82175700', flag_colors=['black', 'red', 'yellow'])\n\nprint(germany.population)  # 82175700\nprint(type(germany.population))  # \u003cclass 'int'\u003e \u003c-- The type is `int` !\n# If you don't want type conversion and still want to have auto code completion, use `Any` as type.\nprint(germany.flag_colors)  # ['black', 'red', 'yellow']\nprint(type(germany.population))  # \u003cclass 'int'\u003e\n```\n\n**Example 3**: Nested class instantiation\n```python\nclass Ram(Prodict):\n    capacity: int\n    unit: str\n    type: str\n    clock: int\n\nclass Computer(Prodict):\n    name: str\n    cpu_cores: int\n    rams: List[Ram]\n\n    def total_ram(self):\n        return sum([ram.capacity for ram in self.rams])\n\ncomp1 = Computer.from_dict(\n    {\n        'name': 'My Computer',\n        'cpu_cores': 4,\n        'rams': [\n            {'capacity': 4,\n             'unit': 'GB',\n             'type': 'DDR3',\n             'clock': 2400}\n        ]\n    })\nprint(comp1.rams)  #  [{'capacity': 4, 'unit': 'GB', 'type': 'DDR3', 'clock': 2400}]\n\ncomp1.rams.append(Ram(capacity=8, type='DDR3'))\ncomp1.rams.append(Ram.from_dict({'capacity': 12, 'type': 'DDR3', 'clock': 2400}))\n\nprint(comp1.rams)\n# [\n#   {'capacity': 4, 'unit': 'GB', 'type': 'DDR3', 'clock': 2400}, \n#   {'capacity': 8, 'type': 'DDR3'}, \n#   {'capacity': 12, 'type': 'DDR3', 'clock': 2400}\n# ]\nprint(type(comp1.rams))  # \u003cclass 'list'\u003e\nprint(type(comp1.rams[0]))  # \u003cclass 'Ram'\u003e \u003c-- Mind the type !\n```\n\n**Example 4**: Provide default values\n\nYou can use `init` method to provide default values. Keep in mind that `init` is NOT `__init__` but `init` method will be called in `__init__` method.\n\nAdditionally, you can use `init` method instead of `__init__` without referring to `super`.\n\n```python\nclass MyDataWithDefaults(Prodict):\n    an_int: int\n    a_str: str\n    \n    def init(self):\n        self.an_int = 42\n        self.a_str = 'string'\n        \n\ndata = MyDataWithDefaults(dynamic=43)\nprint(data)\n# {'an_int':42, 'a_str':'string', 'dynamic':43}\n\n``` \n\n# Class attributes vs Instance attributes\n\nProdict only works for instance attributes.\nEven if you try to set an inherited class attribute, a new instance attribute is created and set.\n\nConsider this example:\n```python\nfrom prodict import Prodict\n\nclass MyClass(Prodict):\n    class_attr: int = 42  # class_attr is a class attribute, not instance attribute\n\nmy_class = MyClass()\nprint(f\"my_class.class_attr: {my_class.class_attr}\")  # 42\n# There is no 'class_attr' defined as instance attribute, so class attribute will be returned (42).\nprint(f\"MyClass.class_attr: {MyClass.class_attr}\") # 42\n# This is a class attribute, it will be returned as is.\n\n# Now an instance attribute 'class_attr' is created and set to 77\nmy_class.class_attr = 77\nprint(f\"my_class.class_attr: {my_class.class_attr}\")  # 42\n# For this matter, avoid setting class_attribute with dot notation, use class name instead\n\nMyClass.class_attr = 88\nprint(f\"MyClass.class_attr: {my_class.class_attr}\")  # 88\n\n# So where did 77 go? It is in instance attribute of the class and since it's name is colliding with\n# the class attribute, you can't get it by dot notation. You can use .get tho.\nprint(f\"my_class.get('class_attr'): {my_class.get('class_attr')}\")  # 77\n```\n\n\n# Installation\nIf your default Python is 3.7:\n\n`pip install prodict`\n\nIf you have more than one Python versions installed:\n\n`python3.7 -m pip install prodict`\n\n\n# Limitations\n- You cannot use `dict` method names as attribute names because of ambiguity.\n- You cannot use `Prodict` method names as attribute names(I will change `Prodict` method names with dunder names to reduce the limitation).\n- You must use valid variable names as `Prodict` attribute names(obviously). For example, while '1' cannot be an attribute for `Prodict`, it is perfectly valid for a `dict` to have '1' as a key. You can still use prodict.set_attribute('1',123) tho.\n- Requires Python 3.7+\n\n# Thanks\nI would like to thank to [JetBrains](https://www.jetbrains.com/) for creating [PyCharm](https://www.jetbrains.com/pycharm/), the IDE that made my life better.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Framazanpolat%2Fprodict","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Framazanpolat%2Fprodict","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Framazanpolat%2Fprodict/lists"}