{"id":28640668,"url":"https://github.com/neurobin/python-ocd","last_synced_at":"2025-06-12T20:08:00.665Z","repository":{"id":57425315,"uuid":"253088390","full_name":"neurobin/python-ocd","owner":"neurobin","description":"Auto readonly, undead property creation and more","archived":false,"fork":false,"pushed_at":"2020-08-18T14:17:37.000Z","size":122,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-23T03:07:40.398Z","etag":null,"topics":["deprecate","deprecated","deprecation","ocd","readonly-property","undeletable-property"],"latest_commit_sha":null,"homepage":"https://docs.neuzunix.com/ocd/latest/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/neurobin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"patreon":"neurobin","liberapay":"neurobin","ko_fi":"neurobin","issuehunt":"neurobin","open_collective":"neurobin","otechie":"neurobin"}},"created_at":"2020-04-04T20:01:54.000Z","updated_at":"2024-08-09T12:40:09.000Z","dependencies_parsed_at":"2022-08-29T22:01:19.464Z","dependency_job_id":null,"html_url":"https://github.com/neurobin/python-ocd","commit_stats":null,"previous_names":["neurobin/python-easyvar"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/neurobin/python-ocd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurobin%2Fpython-ocd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurobin%2Fpython-ocd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurobin%2Fpython-ocd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurobin%2Fpython-ocd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neurobin","download_url":"https://codeload.github.com/neurobin/python-ocd/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurobin%2Fpython-ocd/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259522114,"owners_count":22870449,"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","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":["deprecate","deprecated","deprecation","ocd","readonly-property","undeletable-property"],"created_at":"2025-06-12T20:07:59.236Z","updated_at":"2025-06-12T20:08:00.659Z","avatar_url":"https://github.com/neurobin.png","language":"Python","funding_links":["https://patreon.com/neurobin","https://liberapay.com/neurobin","https://ko-fi.com/neurobin","https://issuehunt.io/r/neurobin","https://opencollective.com/neurobin","https://otechie.com/neurobin"],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/neurobin/python-ocd.svg?branch=release)](https://travis-ci.org/neurobin/python-ocd)\n\nDo you come from a C++ background? Are you very fond of the popular access modifiers such as `public`, `private`, `protected` etc? Do you worry that some of your python programs will be used by new pythonists and they will abuse your private variables?\n\nWell, veteran pythonists will tell you, that, this behavior is over-obsessive and programmers in the python world are all adult people (which is true). In python, you don't usually worry about public or private, instead, you think of internal variables and follow a convention: use a single leading underscore for internal variables (e.g `_varname`) and two leading underscores if you want name mangling inside a class (e.g `__varname`). This does not make them protected or private, but python programmers will know whether they are internal, from the leading underscore.\n\nNow, some people may still want to protect some of their variables from unknown changes, make them readonly, undeletable, etc. For example:\n\n```python\nclass Defaults():\n    STRONG = 2\n    WEAK = 1\n```\n\nLet's say, some new python programmer is using this piece of code and passing `Defaults.WEAK` in some methods. Suddenly, he decides that, he will use `STRONG` instead of `WEAK` and instead of going through all occurrences of `Defaults.WEAK` usage, he does the laziest thing to do: he monkey patches the code:\n\n```python\nDefaults.WEAK = Defaults.STRONG\n```\n\nWith this single line of code, his goal will be accomplished but it's going to be catastrophic if he is not careful (which he is obviously not). This guy may later go to your issue page and open an issue saying that some part of your code is not working as expected.\n\nTo mitigate this kind of scenario, your obsession might not be that bad of an idea. Now, introducing `ocd` aka \u003cmark\u003eObsessive Coder's Demeanor\u003c/mark\u003e (made different from Obsessive Compulsive Disorder on purpose :D). Using `ocd` you can make your variables readonly, undeletable or both. They can be protected from class or class instances or both.\n\n# Auto property creation\n\n## Readonly property\n\n```python\nfrom ocd.prop import Prop\nfrom ocd.mixins import PropMixin\n\nclass Defaults(PropMixin):\n    STRONG = Prop(2, readonly=True)\n    WEAK =  Prop(1, readonly=True)\n\n# use is the same as before: Defaults.STRONG and Defaults.WEAK\n```\n\nThis time, that monkey patch code will raise an exception:\n\n```python\nDefaults.WEAK = Defaults.STRONG # exception, readonly property value can not be changed.\n```\n\nThe class attributes have been made into readonly properties, but they are still deletable, which exposes the following vulnerability:\n\n```python\ndel Defaults.WEAK\nDefaults.WEAK = Defaults.STRONG # now it's OK\n```\n\nand we have the following solution:\n\n## Undead property\n\n```python\nclass Defaults(PropMixin):\n    STRONG = Prop(2, readonly=True, undead=True)\n    WEAK =  Prop(1, readonly=True, undead=True)\n```\n\nYou just need to say, it's an undead property. This time, the monkey patching will fail again:\n\n```python\ndel Defaults.WEAK # exception, undead property can not be deleted\nDefaults.WEAK = Defaults.STRONG\n```\n\n## More intuitive way to make readonly and undead properties\n\nYou may think that writing `Prop(2, readonly=True, undead=True)` and just `2` is a big difference and it is. So, we have a solution for this:\n\n```python\nfrom ocd import defaults\n\nclass Defaults(PropMixin):\n    VarConf = defaults.VarConfAllUnro\n\n    STRONG = 2\n    WEAK =  1\n```\n\nNow, all the attributes that do not start with an underscore('_') will be converted to readonly, undead properties. This is because of `VarConf = defaults.VarConfAllUnro`. `VarConf` is a configuration class that needs to define a method `get_conf`. The above is roughly equivalent to:\n\n```python\nclass Defaults(PropMixin):\n    class VarConf(defaults.VarConfNone):\n        def get_conf(self, name, value):\n            return Prop(readonly=True, undead=True)\n\n    STRONG = 2\n    WEAK =  1\n```\n\nAs you can see, the `get_conf` method has two parameters: name (property name) and value (value of the property), thus, you can decide which one will be what kind of property according to their names and values. You can match names/values with a pattern and make them readonly, match with another pattern and make them non-readonly, or match with another pattern to make them both readonly and undead, etc. You can return `None` for an attribute to not apply any property conversion on that specific attribute.\n\n## Notes\n\n* We do not allow variables starting with an underscores to be converted to property.\n* Variables with leading underscore can store `Prop` class objects without getting converted to property.\n\n# Other access obsessions\n\nWe have several classes to allow different level of obsessions over attribute access, for example:\n\n1. Should the attribute be changeable through class or class instance or both?\n2. Should the attribute be deletable through class or class instance or both?\n3. Should the attributes be allowed to be accessed as items (e.g `obj['name']` instead of `obj.name`)?\n\nYou can check out these classes at [https://docs.neurobin.org/ocd/latest/unro.html](https://docs.neurobin.org/ocd/latest/unro.html)\n\n# Other obsessions\n\n## Deprecate in future\n\nSo, you want to deprecate a function or method from version 2.0 and remove it in 3.0 and the current version is 1.0! No problem, you can obsess on your deprecation plan too:\n\n```python\nfrom ocd.deprecate import deprecate\n\n# If you do not specify the versions, it will be deprecated immediately\n@deprecate(by='method2', ver_cur=package.__version__, ver_dep='2.0', ver_eol='3.0')\ndef method1(self):\n    return self.method2()\n```\n\nWhen the version reaches 2.0, you will get a warning like this:\n\n```\nDeprecatedWarning: `\u003cfunction method1 at 0x7faf2c362c10\u003e` is deprecated by `method2` from version `2.0` and will be removed in version `3.0`. Current version: `2.0`.\n```\n\nand when the version reaches 3.0, you will get a warning like this:\n\n```\nUnsupportedWarning: `\u003cfunction method1 at 0x7faf2c362c10\u003e` was deprecated by `method2` from version `2.0` and planned to be removed in version `3.0`. Current version: `3.0`.\n```\n\nThe unsupported warning is not that helpful, but you can raise this warning into error in your test suite and force yourself or your team to remove this deprecated method in the planned version. For that, you can use the `raiseUnsupportedWarning` decorator:\n\n```python\nfrom ocd.deprecate import raiseUnsupportedWarning\n\n@raiseUnsupportedWarning\ndef test_method1(self):\n    # your test code\n    pass\n```\n\nYou can find the detailed documentation at [https://docs.neurobin.org/ocd/latest/](https://docs.neurobin.org/ocd/latest/).\n\n# Install\n\n```bash\npip install ocd\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneurobin%2Fpython-ocd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneurobin%2Fpython-ocd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneurobin%2Fpython-ocd/lists"}