{"id":21512953,"url":"https://github.com/pfython/cleverdict","last_synced_at":"2025-04-10T01:10:29.918Z","repository":{"id":42086962,"uuid":"273642362","full_name":"PFython/cleverdict","owner":"PFython","description":"A JSON-friendly data structure which allows both object attributes and dictionary keys and values to be used simultaneously and interchangeably.","archived":false,"fork":false,"pushed_at":"2024-03-20T16:23:27.000Z","size":4526,"stargazers_count":102,"open_issues_count":14,"forks_count":9,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-10T01:10:29.243Z","etag":null,"topics":["alias","attributes","auto-save","data","dictionary","keyword","object","orm"],"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/PFython.png","metadata":{"files":{"readme":"README.md","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,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-06-20T05:07:58.000Z","updated_at":"2024-10-10T22:56:03.000Z","dependencies_parsed_at":"2024-06-19T09:55:25.813Z","dependency_job_id":"2c2e010c-8f24-4408-8e72-d97850f63783","html_url":"https://github.com/PFython/cleverdict","commit_stats":{"total_commits":195,"total_committers":7,"mean_commits":"27.857142857142858","dds":"0.16923076923076918","last_synced_commit":"c06fe3870b994b145a74989eca18c2f1c1e9bcf5"},"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PFython%2Fcleverdict","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PFython%2Fcleverdict/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PFython%2Fcleverdict/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PFython%2Fcleverdict/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PFython","download_url":"https://codeload.github.com/PFython/cleverdict/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248137886,"owners_count":21053775,"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":["alias","attributes","auto-save","data","dictionary","keyword","object","orm"],"created_at":"2024-11-23T22:48:49.066Z","updated_at":"2025-04-10T01:10:29.899Z","avatar_url":"https://github.com/PFython.png","language":"Python","readme":"# `CleverDict`\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://pypi.python.org/pypi/cleverdict\"\u003e\u003cimg alt=\"PyPI\" src=\"https://img.shields.io/pypi/v/cleverdict.svg\"\u003e\u003c/a\u003e\n\t\u003ca href=\"https://pypi.python.org/pypi/cleverdict\"\u003e\u003cimg alt=\"PyPI - Python Version\" src=\"https://img.shields.io/pypi/pyversions/cleverdict.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://pepy.tech/project/cleverdict\"\u003e\u003cimg alt=\"Downloads\" src=\"https://pepy.tech/badge/cleverdict\"\u003e\u003c/a\u003e\n    \u003ca href=\"#Contribution\" title=\"Contributions are welcome\"\u003e\u003cimg src=\"https://img.shields.io/badge/contributions-welcome-green.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/pfython/cleverdict/releases\" title=\"CleverDict\"\u003e\u003cimg src=\"https://img.shields.io/github/release-date/pfython/cleverdict?color=green\u0026label=updated\"\u003e\u003c/a\u003e\n    \u003cimg alt=\"PyPI - License\" src=\"https://img.shields.io/pypi/l/cleverdict\"\u003e\n    \u003cimg alt=\"PyPI - Status\" src=\"https://img.shields.io/pypi/status/cleverdict\"\u003e\n    \u003cimg alt=\"GitHub Repo stars\" src=\"https://img.shields.io/github/stars/pfython/cleverdict\"\u003e\n    \u003ca href=\"https://twitter.com/@appawsom\" title=\"Follow us on Twitter\"\u003e\u003cimg src=\"https://img.shields.io/twitter/follow/appawsom.svg?style=social\u0026label=Follow\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n![cleverdict cartoon](https://raw.githubusercontent.com/PFython/cleverdict/master/resources/cleverdict%20cartoon.png)\n\n## \u003eCONTENTS\n\n1. [OVERVIEW](#1-overview)\n2. [INSTALLATION](#2-installation)\n3. [IMPORTING INTO CLEVERDICT](#3-importing-to-cleverdict)\n4. [EXPORTING FROM CLEVERDICT](#4-exporting-from-cleverdict)\n5. [ATTRIBUTE NAMES AND ALIASES](#5-attribute-names-and-aliases)\n6. [DEEPER DIVE INTO ATTRIBUTE NAMES](#6-deeper-dive-into-attribute-names)\n7. [SETTING AN ATTRIBUTE WITHOUT CREATING A DICTIONARY ITEM](#7-setting-an-attribute-without-creating-a-dictionary-item)\n8. [THE AUTO-SAVE FEATURE](#8-the-auto-save-feature)\n9. [CREATING YOUR OWN AUTO-SAVE FUNCTION](#9-creating-your-own-auto-save-function)\n10. [CONTRIBUTING](#10-contributing)\n11. [CREDITS](#11-credits)\n\n\n## 1. OVERVIEW\n\n`CleverDict` is a hybrid Python data class which allows both `object.attribute` and `dictionary['key']` notation to be used simultaneously and interchangeably.  It's particularly handy when your code is mainly object-orientated but you want a 'DRY' and extensible way to import data in json/dictionary/list format into your objects... or vice versa... without having to write extra code just to handle the translation.\n\nPython dictionaries are simple yet powerful, but many people find `object.attribute` syntax easier to type and more intuitive to read, so why not have the best of both worlds?\n\n    \u003e\u003e\u003e from cleverdict import CleverDict\n    \u003e\u003e\u003e x = CleverDict({'total':6, 'usergroup': \"Knights of Ni\"})\n\n    \u003e\u003e\u003e x.total\n    6\n    \u003e\u003e\u003e x['total']\n    6\n\n    \u003e\u003e\u003e x.usergroup\n    'Knights of Ni'\n    \u003e\u003e\u003e x['usergroup']\n    'Knights of Ni'\n\n    \u003e\u003e\u003e x['life'] = 42\n    \u003e\u003e\u003e x.life += 1\n    \u003e\u003e\u003e x['life']\n    43\n\n    \u003e\u003e\u003e del x['life']\n    \u003e\u003e\u003e x.life\n    AttributeError: 'life'\n\n`CleverDict` automatically generates **Aliases** which map to your original dictionary keys, handling various edge cases we've unearthed along the way so you don't have to.  You can add and delete your own custom **Aliases** too, which is really handy for adding shortcuts, mapping API responses to existing data structures, local language variants, and more.\n\n`CleverDict` plays nicely with JSON and also includes some great convenience functions for importing/export lists, dicts, and lines.  It even offers two built-in `.autosave()` options and you can specify your own autosave/autodelete functions to be called automatically whenever an attribute changes.  No more explicitly writing lines to save your data or prompt for confirmation etc. every... single... time... a value changes (or *worse*, forgetting to...).\n\n## 2. INSTALLATION\n\nVery lightweight - no dependencies:\n\n    pip install cleverdict\n\nor to cover all bases...\n\n    python -m pip install cleverdict --upgrade\n\n\n## 3. IMPORTING TO CLEVERDICT\n\nYou can create a `CleverDict` instance using keyword arguments:\n\n    \u003e\u003e\u003e x = CleverDict(created = \"today\", review = \"tomorrow\")\n\n    \u003e\u003e\u003e x.created\n    'today'\n    \u003e\u003e\u003e x['review']\n    'tomorrow'\n\nOr use a list of tuple pairs and/or list pairs:\n\n    \u003e\u003e\u003e x = CleverDict([(\"value1\", \"one\"), [\"value2\", \"two\"], (\"value3\", \"three\")])\n\n    \u003e\u003e\u003e x.value1\n    'one'\n    \u003e\u003e\u003e x['value2']\n    'two'\n    \u003e\u003e\u003e getattr(x, \"value3\")\n    'three'\n\nOr use an existing `CleverDict` object as input:\n\n    \u003e\u003e\u003e x = CleverDict({1: \"one\", 2: \"two\"})\n    \u003e\u003e\u003e y = CleverDict(x)\n\n    \u003e\u003e\u003e y\n    CleverDict({1: 'one', 2: 'two'}, _aliases={'_1': 1, '_True': 1, '_2': 2}, _vars={})\n\n    \u003e\u003e\u003e y.items()\n    dict_items([(1, 'one'), (2, 'two')])\n\n\u003e *(\\*) See Sections 5 and 7 to understand `_aliases={}` and `_vars={}` shown in the output above...*\n\nA really nice feature is the ability to import JSON strings or files using `.from_json()` (try with `resources/mydata.json`):\n\n    \u003e\u003e\u003e json_data = '{\"None\": null}'\n    \u003e\u003e\u003e x = CleverDict.from_json(json_data)\n    \u003e\u003e\u003e x\n    CleverDict({'None': None}, _aliases={'_None': 'None'}, _vars={})\n\n    \u003e\u003e\u003e y = CleverDict.from_json(file_path=\"mydata.json\")\n\nAnd the built-in dictionary method `.fromkeys()` works as normal, like this:\n\n    \u003e\u003e\u003e x = CleverDict.fromkeys([\"Abigail\", \"Tino\", \"Isaac\"], \"Year 9\")\n\n    \u003e\u003e\u003e x\n    CleverDict({'Abigail': 'Year 9', 'Tino': 'Year 9', 'Isaac': 'Year 9'}, _aliases={}, _vars={})\n\nYou can also use `vars()` to import another object's data (but not its methods):\n\n    \u003e\u003e\u003e class X: pass\n    \u003e\u003e\u003e a = X(); a.name = \"Percival\"\n    \u003e\u003e\u003e x = CleverDict(vars(a))\n\n    \u003e\u003e\u003e x\n    CleverDict({'name': 'Percival'}, _aliases={}, _vars={})\n\n## 4. EXPORTING FROM CLEVERDICT\n\nTo return a regular Python `dict` from `CleverDict`'s main data dictionary:\n\n    \u003e\u003e\u003e x.to_dict()\n    {'name': 'Percival'}\n\nYou can export to JSON with `.to_json()` but be aware that your nested values/objects will not be touched, and must therefore be capable of being serialised to JSON individually.  If they're not essential to your output, you can simply add the  `ignore=` (or `exclude=`) argument to exclude them entirely:\n\n    \u003e\u003e\u003e x.to_json()\n    '{\\n    \"name\": \"Percival\"\\n}'\n\n    \u003e\u003e\u003e x.now = datetime.datetime.now()\n    \u003e\u003e\u003e x.to_json()\n    TypeError: Object of type datetime is not JSON serializable\n\n    \u003e\u003e\u003e x.to_json(ignore=[\"now\"])\n    '{\\n    \"name\": \"Percival\"\\n}'\n\n    # Or output to a file:\n    \u003e\u003e\u003e x.to_json(file_path=\"mydata.json\")\n\nYou can also use the `.to_list()` method to generate a list of key/value pairs:\n\n    \u003e\u003e\u003e x = CleverDict({1: \"one\", 2: \"two\"})\n\n    \u003e\u003e\u003e x.to_list()\n    [(1, 'one'), (2, 'two')]\n\nAnd you can import/export text files using `.from_lines()` and `.to_lines()` which is useful for things like subtitles, README files, code, and to-do lists:\n\n    \u003e\u003e\u003e lines =\"This is my first line\\nMy second...\\n\\n\\n\\n\\nMy LAST\\n\"\n    \u003e\u003e\u003e x = CleverDict.from_lines(lines)\n\n    \u003e\u003e\u003e from pprint import pprint\n    \u003e\u003e\u003e pprint(x)\n    {1: 'This is my first line',\n     2: 'My second...',\n     3: '',\n     4: '',\n     5: '',\n     6: '',\n     7: 'My LAST',\n     8: ''}\n\n     \u003e\u003e\u003e x.to_lines(file_path=\"lines.txt\")\n\nBy default `.from_lines()` uses 'human' numbering starting with key 1 (integer) but you can specify another starting number with `start_from_key=`:\n\n    \u003e\u003e\u003e x = CleverDict.from_lines(lines, start_from_key=0)\n    \u003e\u003e\u003e pprint(x)\n\n    {0: 'This is my first line',\n     1: 'My second...',\n     2: '',\n     3: '',\n     4: '',\n     5: '',\n     6: 'My LAST',\n     7: ''}\n\nIf you want to start your output from a specific line you can again use `start_from_key=`, this time with `.to_lines()`:\n\n    \u003e\u003e\u003e x.to_lines(start_from_key=6)\n    'My LAST\\n'\n\n\u003e That '`\\n`' at the end of the output is actually Line 7 which is empty.\n\n\nAlthough primarily intended for numerical indexing, you can also use *strings* with `.to_lines()`, which is handy for setting 'bookmarks' for example.  You can choose between creating an **alias** (recommended - see next Section) or actually creating/overwriting with a new **key**:\n\n    \u003e\u003e\u003e x.add_alias(6, \"The End\")\n    \u003e\u003e\u003e new_lines = x.to_lines(start_from_key=\"The End\")\n    \u003e\u003e\u003e new_lines\n    'My LAST\\n'\n\n    \u003e\u003e\u003e x.footnote1 = \"Source: Wikipedia\"\n    \u003e\u003e\u003e x.update({8:\"All references to living persons are accidental\"})\n    \u003e\u003e\u003e x.to_lines(start_from_key=\"footnote1\")\n    'Source: Wikipedia\\nAll references to living persons are accidental'\n\n\u003e NB:  Like regular dictionaries from Python 3.6 onwards, `CleverDict`,  stores values **in the order you create them**.  By default though `pprint` will helpfully (!) **sort** the keys, so don't panic if they seem out of order... Just use `repr()` to confirm the actual order, or `.info()` which is explained more fully in Section 6.\n\n![Keep Calm](https://raw.githubusercontent.com/PFython/cleverdict/master/resources/keep_calm_use_info.png)\n\nIf you want to *only* include particular keys in the output of `.to_json()`, `.to_list()`, `.to_dict`, `.to_lines()`, `.info()` and even `__repr__()`, you can use the `only=` argument followed by a list of attribute/key names:\n\n    \u003e\u003e\u003e x = CleverDict({\"Apple\": \"Green\", \"Banana\": \"Yellow\", \"Orange\": \"Blue\"})\n    \u003e\u003e\u003e x.to_dict(only=[\"Apple\", \"Orange\"])\n    {'Apple': 'Green', 'Orange': 'Blue'}\n\nAnd finally, if you want to **exclude** (perhaps sensitive) attributes such as `.password`, just add the argument `ignore=` (or `exclude=`)  to ignore:\n\n    \u003e\u003e\u003e x.password = \"Top Secret - don't ever save to file!\"\n\n    \u003e\u003e\u003e x.to_lines(start_from_key=6)\n    \"My LAST\\n\\nTop Secret - don't ever save to file!\"\n\n    \u003e\u003e\u003e x.to_lines(start_from_key=6, ignore=[\"password\"])\n    'My LAST\\n'\n\nYou can add common exceptions at a *class* level too:\n\n    \u003e\u003e\u003e CleverDict.ignore\n    {\"_aliases\", \"save_path\", \"save\", \"delete\"}\n\n     \u003e\u003e\u003e CleverDict.ignore.add(\"password\")\n\n## 5. ATTRIBUTE NAMES AND ALIASES\n\nPython dictionaries accept keywords, null strings, strings incorporating punctuation marks, and integers as their keys, but these *aren't* valid names for object attributes.  `CleverDict` helps by generating valid names where a straight copy of the dictionary keys would otherwise fail.    So for example `CleverDict` will automatically create the attribute name`\"_7\"` (string) to map to a dictionary key of `7` (integer):\n\n    \u003e\u003e\u003e x = CleverDict({7: \"Seven\"})\n\n    \u003e\u003e\u003e x._7\n    'Seven'\n    \u003e\u003e\u003e x\n    CleverDict({7: 'Seven'}, _aliases={'_7': 7}, _vars={})\n\n```CleverDict``` keeps the original dictionary keys and values unchanged and remembers any normalised attribute names as aliases in ```._aliases```.  You can add or delete further aliases with ```.add_alias``` and ```.delete_alias```, but the original dictionary key will never be deleted, even if all aliases and attributes are removed:\n\n    \u003e\u003e\u003e x.add_alias(7, \"NumberSeven\")\n    \u003e\u003e\u003e x.add_alias(7, \"zeven\")\n\n    \u003e\u003e\u003e x\n    CleverDict({7: 'Seven'}, _aliases={'_7': 7, 'NumberSeven': 7, 'zeven': 7}, _vars={})\n\n    \u003e\u003e\u003e x.get_aliases()\n    [7, '_7', 'NumberSeven', 'zeven']\n\n`CleverDict` objects continue to work as dictionaries even if you accidentally use any of the built-in method names for dictionary keys or aliases.  As you'd expect and hope, it won't overwrite those methods, and the dictionary will remain intact:\n\n    \u003e\u003e\u003e 'to_list' in dir(CleverDict)\n    True\n\n    \u003e\u003e\u003e y = CleverDict({'to_list': \"Some information\"})\n\n    \u003e\u003e\u003e y['to_list']\n    'Some information'\n\n    \u003e\u003e\u003e type(y.to_list)\n    \u003cclass 'method'\u003e\n\nBack to our **alias** example, if you specify `ignore=` (or `exclude=`) when using `.to_json()`, `.to_list()`, `.info()`, `to_lines()`, `.to_dict`, or `__repr__()`, you can rest easy knowing that all aliases *and* the primary key(s) you've specified will be excluded too:\n\n    \u003e\u003e\u003e x.info(ignore=[7])\n    CleverDict:\n\n    \u003e\u003e\u003e x.to_dict(ignore=[\"zeven\"])\n    {}\n\n    \u003e\u003e\u003e x.to_list(ignore=[\"NumberSeven\"])\n    []\n\nAs you probably guessed, you can safely delete an alias with `.delete_alias()`, and the original dictionary key will be retained until/unless you use `del`:\n\n    \u003e\u003e\u003e x.delete_alias([\"_7\",\"NumberSeven\"])\n\n    \u003e\u003e\u003e x\n    \"CleverDict({7: 'Seven'}, _aliases={'zeven': 7}, _vars={})\"\n\n    \u003e\u003e\u003e x._7\n    AttributeError: '_7'\n\n    \u003e\u003e\u003e x.delete_alias([7])\n    KeyError: \"primary key 7 can't be deleted\"\n\n    \u003e\u003e\u003e del x[7]\n    \u003e\u003e\u003e x\n    CleverDict({}, _aliases={}, _vars={})\n\n\n## 6. DEEPER DIVE INTO ATTRIBUTE NAMES\n\n\n**QUIZ QUESTION:** Did you know that since [PEP3131](https://www.python.org/dev/peps/pep-3131/) many (but not all) unicode characters are valid in attribute names?\n\n    \u003e\u003e\u003e x = CleverDict(значение = \"znacheniyeh: Russian word for 'value'\")\n    \u003e\u003e\u003e x.значение\n    \"znacheniyeh: Russian word for 'value'\"\n\n`CleverDict` replaces all *invalid* characters such as (most) punctuation marks with \"`_`\" on a *first come, first served* basis.  To avoid duplicates or over-writing, a `KeyError` is raised in the event of a 'clash', which is your **strong hint** to rename one of the offending dictionary keys to something that won't result in a duplicate alias.  For example:\n\n    \u003e\u003e\u003e x = CleverDict({\"one-two\": \"hypen\",\n                        \"one/two\": \"forward slash\"})\n    KeyError: \"'one_two' already an alias for 'one-two'\"\n\n    \u003e\u003e\u003e x = CleverDict({\"one-two\": \"hypen\",\n                        \"one_or_two\": \"forward slash\"})\n\n**BONUS QUESTION:** Did you also know that the dictionary keys `0`, `0.0`, and `False` are considered the same in Python?  Likewise `1`, `1.0`, and `True`, and `1234` and `1234.0`?  If you create a regular dictionary using more than one of these different identities, they'll appear to 'overwrite' each other, keeping the **first Key** specified but the **last Value** specified, reading left to right:\n\n    \u003e\u003e\u003e x = {1: \"one\", True: \"the truth\"}\n\n    \u003e\u003e\u003e x\n    {1: 'the truth'}\n\nYou'll be relieved to know `CleverDict` handles these cases but we thought it was worth mentioning in case you came across them first and wondered what the heck was going on!  *\"Explicit is better than implicit\"*, right?  If in doubt, you can inspect all the keys, `.attributes`, and aliases using the `.info()` method, as well as any aliases linking to the object itself:\n\n    \u003e\u003e\u003e x = y = z = CleverDict({1: \"one\", True: \"the truth\"})\n    \u003e\u003e\u003e x.info()\n\n    CleverDict:\n    x is y is z\n    x[1] == x['_1'] == x['_True'] == x._1 == x._True == 'the truth'\n\n\nAnd if you use `info(as_str=True)` you'll get the results as a printable string:\n\n    \u003e\u003e\u003e x.info(as_str=True)\n\n    \"CleverDict:\\n    x is y is z\\n    x[1] == x['_1'] == x['_True'] == x._1 == x._True == 'the truth'\"\n\n\n## 7. SETTING AN ATTRIBUTE WITHOUT CREATING A DICTIONARY ITEM\nWe've included the `.setattr_direct()` method in case you want to set an attribute *without* creating the corresponding dictionary key/value.  This could be useful for storing 'internal' data, objects and methods for example, and is used by `CleverDict` itself to store aliases in `._aliases` and the location of the autosave file in `save_path`.  Any variables which are set directly with `.setattr_direct()` are stored in `_vars`:\n\n    \u003e\u003e\u003e x = CleverDict()\n    \u003e\u003e\u003e x.setattr_direct(\"direct\", True)\n\n    \u003e\u003e\u003e x\n    CleverDict({}, _aliases={}, _vars={'direct': True})\n\nNeither `._vars`, nor `_aliases`, nor any of `CleverDict`'s own functions/methods/variables are included in the output of `.to_json()`, `.to_lines()`, and `.to_list()` etc. ***unless*** you specify `(fullcopy=True)`.  Otherwise the output will simply be your basic data dictionary, since `CleverDict` is a *dictionary* first and foremost.\n\nSubject to normal JSON limitations, you can *completely reconstruct* your original `CleverDict` using one created with `.from_json(fullcopy=True)`:\n\n    \u003e\u003e\u003e x = CleverDict({\"total\":6, \"usergroup\": \"Knights of Ni\"})\n    \u003e\u003e\u003e x.add_alias(\"usergroup\", \"knights\")\n    \u003e\u003e\u003e x.setattr_direct(\"Quest\", \"The Holy Grail\")\n    \u003e\u003e\u003e x.to_json(file_path=\"mydata.json\", fullcopy=True)\n\n    \u003e\u003e\u003e y = CleverDict.from_json(file_path=\"mydata.json\")\n    \u003e\u003e\u003e y == x\n    True\n\n    \u003e\u003e\u003e y\n    CleverDict({'total': 6, 'usergroup': 'Knights of Ni'}, _aliases={'knights': 'usergroup'}, _vars={'Quest': 'The Holy Grail'})\n\nThis even solves the pesky problem of `json.dumps()` converting numeric keys to strings e.g. `{1: \"one\"}` to `{\"1\": \"one\"}`.  By recording the mappings as part of the JSON, `CleverDict` is able to remember whether your initial key was numeric or a string.  Niiiiice.\n\n\n## 8. THE AUTO-SAVE FEATURE\n\n\nFollowing the \"*batteries included*\" philosophy, we've included not one but **two** powerful autosave/autodelete options which, when activated, will save your `CleverDict` data to the recommended 'Settings' folder of whichever Operating System you're using.\n\n---\n\n**AUTOSAVE OPTION #1: DICTIONARY DATA ONLY**\n\n\n    \u003e\u003e\u003e x = CleverDict({\"Patient Name\": \"Wobbly Joe\", \"Test Result\": \"Positive\"})\n    \u003e\u003e\u003e x.autosave()\n\n    ⚠ Autosaving to:\n    C:\\Users\\Peter\\AppData\\Roaming\\CleverDict\\2021-01-20-15-03-54-30.json\n\n    \u003e\u003e\u003e x.Prognosis = \"Not good\"\n\nIf you browse the json file, you should see `.Prognosis` has been saved along with any other new/changed values.  Any values you delete will be automatically removed.\n\n---\n**AUTOSAVE OPTION #2: FULL COPY**\n\nIn **Section 7** you saw how to use `.to_json(fullcopy=True)` to create a complete image of your `CleverDict` as a JSON string or file.  You can set this as the autosave/autodelete method using the same simple syntax:\n\n    \u003e\u003e\u003e x.autosave(fullcopy=True)\n\nWith this autosave option, **all dictionary data**, **all aliases** (in `_aliases`), and **all attributes** (including `_vars`) will be saved whenever they're created, changed, or deleted.\n\n---\n\n\nIn both `.autosave()` options above, the file location is stored as `.save_path` using `.setattr_direct()` which you read about above (unless you skipped or fell asleep!).\n\n    \u003e\u003e\u003e x.save_path\n    WindowsPath('C:/Users/Peter/AppData/Roaming/CleverDict/2021-01-20-15-03-54-30.json')\n\nTo disable autosaving/autodeletion  just enter:\n\n    \u003e\u003e\u003e x.autosave(\"off\")\n\n    ⚠ Autosave disabled.\n\n    ⓘ Previous updates saved to:\n       C:\\Users\\Peter\\AppData\\Roaming\\CleverDict\\2021-01-20-15-03-54-30.json\n\nTo deactivate `.save()` or `.delete()` separately:\n\n    \u003e\u003e\u003e x.set_autosave()\n    \u003e\u003e\u003e x.set_autodelete()\n\n\u003e If you want to periodically open the `CleverDict` save folder to check for orphaned `.json` files from time to time, a handy shortcut is:\n\n    \u003e\u003e\u003e import webbrowser\n    \u003e\u003e\u003e webbrowser.open(x.save_path.parent)\n\n## 9. CREATING YOUR OWN AUTO-SAVE/AUTO-DELETE FUNCTION\n\nAs well as autosave/autodelete options baked in to `CleverDict`, you can set pretty much any custom function to run **automatically** when a `CleverDict` value is *created, changed, or deleted*, for example to update a database, save to a file, or synchronise with cloud storage etc.  Less code for you, and less chance you'll forget to explicitly call that crucial update function...\n\nThis can be enabled at a *class* level, or by creating subclasses of `CleverDict` with different options, or an *object/instance* level.  We strongly recommend the *object/instance* approach wherever possible, but you have the choice.\n\n### **Autosaving a particular object/instance:**\n\nYou can either overwrite the `.save()` / `.delete()` methods when you create your object, or use `.set_autosave()` / `.set_autodelete()` after the event:\n\n    \u003e\u003e\u003e x = CleverDict({\"Patient Name\": \"Wobbly Joe\", \"Test Result\": \"Positive\"},\n        save=your_save_function)\n\n    # Or for an existing object:\n    \u003e\u003e\u003e x.set_autosave(your_save_function)\n\n\n    \u003e\u003e\u003e x = CleverDict({\"Patient Name\": \"Wobbly Joe\", \"Test Result\": \"Positive\"},\n        delete=your_delete_function)\n\n    # Or for an existing object:\n    \u003e\u003e\u003e x.set_autodelete(your_delete_function)\n\n### **Autosaving at a class level:**\n\nSimple to do, but beware this could change all existing `CleverDict` instances as well as all future ones:\n\n    \u003e\u003e\u003e CleverDict.save = your_save_function\n    \u003e\u003e\u003e CleverDict.delete = your_delete_function\n\n### **Creating Subclasses:**\n\nIf you create a subclass of `CleverDict` remember to call `super().__init__()` *before* trying to set any further class or object attributes, otherwise you'll run into trouble:\n\n    class AutoStore(CleverDict):\n        def __init__(self, *args, **kwargs):\n            self.setattr_direct('index', [])\n            super().__init__(*args, **kwargs)\n\n        def save(self, name, value):\n            \"\"\" Keep a separate 'store' for data in .index \"\"\"\n            self.index.append((name, value))\n\n    class AutoConfirm(CleverDict): pass\n        def __init__(self, *args, **kwargs):\n            super().__init__(*args, **kwargs)\n\n        def save(self, name, value):\n            \"\"\" Print confirmation of the latest change \"\"\"\n            print(f\"{name.title()}: {value.upper()}\")\n\n\u003e NB: The `.append()` method  in `AutoStore.save()` above doesn't trigger the `.save()` method again itself (thankfully) otherwise we'd in a whole world of recursion pain...  If we'd used `self.index +=`, the `.save()` method *would* have been called recursively.\n\n### **Writing Your Own Function:**\n\nWhen writing your own `.save()` function, you'll need to accept three arguments as shown in the following example:\n\n    \u003e\u003e\u003e def your_save_function(self, name, value):\n            \"\"\" Custom save function by you \"\"\"\n            print(f\" ⓘ  .{name} (object type: {self.__class__.__name__}) = {value} {type(value)}\")\n\n    \u003e\u003e\u003e CleverDict.delete = your_delete_function\n    \u003e\u003e\u003e x=CleverDict()\n    \u003e\u003e\u003e x.new = \"Novelty\"\n    ⓘ  .new (object type: CleverDict) = Novelty \u003cclass 'str'\u003e\n\n    \u003e\u003e\u003e x.save.__doc__\n    ' Custom save function by you '\n\n* **self**: because we're dealing with objects and classes...\n* **key**: a valid Python `.attribute` or *key* name preferably, otherwise you'll only be able to access it using `dictionary['key']` notation later on.\n* **value**: anything\n\nWhen writing your own `.delete()` function, the same applies, except there is no `value` parameter supplied.\n\n## 10. CONTRIBUTING\n\nWe'd love to see Pull Requests (and relevant tests) from other contributors, particularly if you can help:\n\n* Evolve `CleverDict` to make it play nicely with other classes and formats.  [For example: `datetime`](https://github.com/PFython/cleverdict/issues/5).\n* Put the finishing touches on the **docstrings** to enable autocompletion in modern IDEs (this is neither the author's strong suit nor his passion!).\n* Improve the structure and coverage of `test_cleverdict.py`.\n\nFor a list of all outstanding **Feature Requests** and (heaven forbid!) actual *Issues* please have a look here and maybe you can help out?\n\nhttps://github.com/PFython/cleverdict/issues?q=is%3Aopen+is%3Aissue\n\n\n## 11. CREDITS\n`CleverDict` was developed jointly by Ruud van der Ham, Peter Fison, Loic Domaigne, and Rik Huygen who met on the friendly and excellent Pythonista Cafe forum (www.pythonistacafe.com).  Peter got the ball rolling after noticing a super-convenient, but not fully-fledged feature in Pandas that allows you to (mostly) use `object.attribute` syntax or `dictionary['key']` syntax interchangeably. Ruud, Loic and Rik then started swapping ideas for a hybrid  dictionary/data class, originally based on `UserDict` and the magic of `__getattr__` and `__setattr__`.\n\n\u003e **Fun Fact:** `CleverDict` was originally called `attr_dict` but several confusing flavours of this and `AttrDict` exist on PyPi and Github already.  Hopefully this new tongue-in-cheek name is more memorable and raises a smile ;)\n\nIf you find `cleverdict` helpful, please feel free to:\n\n\u003ca href=\"https://www.buymeacoffee.com/pfython\" target=\"_blank\"\u003e\u003cimg src=\"https://cdn.buymeacoffee.com/buttons/v2/arial-yellow.png\" alt=\"Buy Me A Coffee\" width=\"217px\" \u003e\u003c/a\u003e\n","funding_links":["https://www.buymeacoffee.com/pfython"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpfython%2Fcleverdict","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpfython%2Fcleverdict","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpfython%2Fcleverdict/lists"}