{"id":13815139,"url":"https://github.com/gaogaotiantian/watchpoints","last_synced_at":"2025-05-14T21:10:45.636Z","repository":{"id":41503792,"uuid":"318874633","full_name":"gaogaotiantian/watchpoints","owner":"gaogaotiantian","description":"watchpoints is an easy-to-use, intuitive variable/object monitor tool for python that behaves similar to watchpoints in gdb.","archived":false,"fork":false,"pushed_at":"2024-12-23T16:21:52.000Z","size":101,"stargazers_count":530,"open_issues_count":4,"forks_count":22,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-15T00:43:48.541Z","etag":null,"topics":["debugging","debugging-tool","python","python3"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gaogaotiantian.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"gaogaotiantian"}},"created_at":"2020-12-05T19:42:09.000Z","updated_at":"2025-04-11T15:41:02.000Z","dependencies_parsed_at":"2025-01-27T07:00:34.816Z","dependency_job_id":"e7cba368-59fd-49c9-aff6-fe86c5389eb3","html_url":"https://github.com/gaogaotiantian/watchpoints","commit_stats":{"total_commits":61,"total_committers":3,"mean_commits":"20.333333333333332","dds":"0.032786885245901676","last_synced_commit":"4f93eba4ed94ab66892ba24d75fc54bf783699da"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gaogaotiantian%2Fwatchpoints","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gaogaotiantian%2Fwatchpoints/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gaogaotiantian%2Fwatchpoints/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gaogaotiantian%2Fwatchpoints/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gaogaotiantian","download_url":"https://codeload.github.com/gaogaotiantian/watchpoints/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254227631,"owners_count":22035671,"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":["debugging","debugging-tool","python","python3"],"created_at":"2024-08-04T04:03:00.690Z","updated_at":"2025-05-14T21:10:40.620Z","avatar_url":"https://github.com/gaogaotiantian.png","language":"Python","funding_links":["https://github.com/sponsors/gaogaotiantian"],"categories":["Python"],"sub_categories":[],"readme":"# watchpoints\n\n[![build](https://github.com/gaogaotiantian/watchpoints/workflows/build/badge.svg)](https://github.com/gaogaotiantian/watchpoints/actions?query=workflow%3Abuild)  [![coverage](https://img.shields.io/codecov/c/github/gaogaotiantian/watchpoints)](https://codecov.io/gh/gaogaotiantian/watchpoints)  [![pypi](https://img.shields.io/pypi/v/watchpoints.svg)](https://pypi.org/project/watchpoints/)  [![support-version](https://img.shields.io/pypi/pyversions/watchpoints)](https://img.shields.io/pypi/pyversions/watchpoints)  [![license](https://img.shields.io/github/license/gaogaotiantian/watchpoints)](https://github.com/gaogaotiantian/watchpoints/blob/master/LICENSE)  [![commit](https://img.shields.io/github/last-commit/gaogaotiantian/watchpoints)](https://github.com/gaogaotiantian/watchpoints/commits/master)\n\nwatchpoints is an easy-to-use, intuitive variable/object monitor tool for python that behaves similar to watchpoints in gdb.\n\n## Install\n\n```\npip install watchpoints\n```\n\n## Usage\n\n### watch\n\nSimply ```watch``` the variables you need to monitor!\n\n```python\nfrom watchpoints import watch\n\na = 0\nwatch(a)\na = 1\n```\n\nwill generate\n\n```\n====== Watchpoints Triggered ======\nCall Stack (most recent call last):\n  \u003cmodule\u003e (my_script.py:5):\n\u003e   a = 1\na:\n0\n-\u003e\n1\n```\n\nIt works on both variable change and object change\n\n```python\nfrom watchpoints import watch\n\na = []\nwatch(a)\na.append(1)  # Trigger\na = {}  # Trigger\n```\n\nEven better, it can track the changes of the object after the changes of the variable\n\n```python\nfrom watchpoints import watch\n\na = []\nwatch(a)\na = {}  # Trigger\na[\"a\"] = 2  # Trigger\n```\n\nWithout doubts, it works whenever the object is changed, even if it's not in the same scope\n\n```python\nfrom watchpoints import watch\n\ndef func(var):\n    var[\"a\"] = 1\n\na = {}\nwatch(a)\nfunc(a)\n```\n\n```\n====== Watchpoints Triggered ======\nCall Stack (most recent call last):\n  \u003cmodule\u003e (my_script.py:8):\n\u003e   func(a)\n  func (my_script.py:4):\n\u003e   var[\"a\"] = 1\na:\n{}\n-\u003e\n{'a': 1}\n```\n\nAs you can imagine, you can monitor attributes of an object, or a specific element of a list or a dict\n\n```python\nfrom watchpoints import watch\n\nclass MyObj:\n    def __init__(self):\n        self.a = 0\n\nobj = MyObj()\nd = {\"a\": 0}\nwatch(obj.a, d[\"a\"])  # Yes you can do this\nobj.a = 1  # Trigger\nd[\"a\"] = 1  # Trigger\n```\n\nAlso, watchpoints supports native ```threading``` library for multi-threading. It will tell you which thread is changing the\nvalue as well.\n\n```\n====== Watchpoints Triggered ======\n---- Thread-1 ----\nCall Stack (most recent call last):\n  _bootstrap (/usr/lib/python3.8/threading.py:890):\n\u003e   self._bootstrap_inner()\n  _bootstrap_inner (/usr/lib/python3.8/threading.py:932):\n\u003e   self.run()\n  run (my_script.py:15):\n\u003e   a[0] = i\na:\n[0]\n-\u003e\n[1]\n```\n\n**watchpoints will try to guess what you want to monitor, and monitor it as you expect**(well most of the time)\n\n### unwatch\n\nWhen you are done with the variable, you can unwatch it.\n\n```python\nfrom watchpoints import watch, unwatch\n\na = 0\nwatch(a)\na = 1\nunwatch(a)\na = 2  # nothing will happen\n```\n\nOr you can unwatch everything by passing no argument to it\n\n```python\nunwatch()  # unwatch everything\n```\n\n### print to different stream\n\nLike the ``print`` function, you can choose the output stream for watch print using ``file`` argument. The default\nvalue is ``sys.stderr``.\n\n```python\nf = open(\"watch.log\", \"w\")\na = 0\nwatch(a, file=f)\na = 1\nf.close()\n```\n\nBe aware that **the stream needs to be available when the variable is changed**! So the following code **WON'T WORK**:\n\n```python\na = 0\nwith open(\"watch.log\", \"w\") as f:\n    watch(a, file=f)\na = 1\n```\n\nOr you could just give a filename to ``watch``. It will append to the file.\n\n```python\nwatch(a, file=\"watch.log\")\n```\n\nUse config if you want to make it global\n\n```python\nwatch.config(file=\"watch.log\")\n```\n\n### customize printer\n\nYou can use your own printer function to print the object, instead of the default ``objprint`` with ``custom_printer``\n\n```python\n# This will use built-in print function for the objects\nwatch(a, custom_printer=print)\n```\n\nUse config if you want to make it global\n\n```python\nwatch.config(custom_printer=print)\n```\n\n### alias\n\nYou can give an alias to a monitored variable, so you can unwatch it anywhere. And the alias will be printed instead of the variable name\n```python\nfrom watchpoints import watch, unwatch\n\nwatch(a, alias=\"james\")\n# Many other stuff, scope changes\nunwatch(\"james\")\n```\n\n### conditional callback\n\nYou can give an extra condition filter to do \"conditional watchpoints\". Pass a function ```func(obj)``` which returns ```True```\nif you want to trigger the callback to ```when``` of ```watch```\n\n```python\na = 0\nwatch(a, when=lambda x: x \u003e 0)\na = -1  # Won't trigger\na = 1  # Trigger\n```\n\n### variable vs object\n\nWhen you do ```watch()``` on an object, you are actually tracking both the object and the variable holding it. In most cases, that's what\nyou want anyways. However, you can configure precisely which you want to track.\n\n```python\na = []\nwatch(a, track=\"object\")\na.append(1)  # Trigger\na = {}  # Won't trigger because the list object does not change\n\na = []\nwatch(a, track=\"variable\")\na.append(1)  #  Won't trigger, because \"a\" still holds the same object\na = {}  # Trigger\n```\n\n### object compare and deepcopy\n\nNested object comparison is tricky. It's hard to find a solid standard to compare complicated customized objects.\nBy default, watchpoints will do a shallow copy of the object. You can override this behavior by passing ```deepcopy=True``` to ```watch()```\n\n```python\nwatch(a, deepcopy=True)\n```\n\nwatchpoints will honor ```__eq__``` method for user-defined classes first. If ```__eq__``` is not implemented, watchpoints will compare\n```__dict__```(basically attibures) of the object if using shallow copy, and raise an ```NotImplementedError``` if using deepcopy.\n\nThe reason behind this is, if you deepcopied a complicated structure, there's no way for watchpoints to figure out if it's the same object\nwithout user defined ```__eq__``` function.\n\n#### customize copy and compare\n\nFor your own data structures, you can provide a customized copy and/or customized compare function for watchpoints to better suit your need.\n\nwatchpoints will use the copy function you provide to copy the object for reference, and use your compare function to check if that\nobject is changed. If copy function or compare function is not provided, it falls to default as mentioned above.\n\n```cmp``` argument takes a function that will take two objects as arguments and return a ```boolean``` representing whether the objects\nare **different**\n\n```python\ndef my_cmp(obj1, obj2):\n    return obj1.id != obj2.id\n\nwatch(a, cmp=my_cmp)\n```\n\n```copy``` argument takes a function that will take a object and return a copy of it\n\n```python\ndef my_copy(obj):\n    return MyObj(id=obj.id)\n\nwatch(a, copy=my_copy)\n```\n\n### stack limit\n\nYou can specify the call stack limit printed using ```watch.config()```. The default value is ```5```, any positive integer is accepted.\nYou can use ```None``` for unlimited call stack, which means it will prints out all the frames.\n\n```python\nwatch.config(stack_limit=10)\n```\n\nYou can also set different stack limits for each monitored variable by passing ``stack_limit`` argument to ``watch``\n\n```python\n# This will only change stack_limit for a\nwatch(a, stack_limit=10)\n```\n\n### customize callback\n\nOf course sometimes you want to print in your own format, or even do something more than print. You can use your own callback for monitored variables\n\n```python\nwatch(a, callback=my_callback)\n```\n\nThe callback function takes three arguments\n\n```python\ndef my_callback(frame, elem, exec_info)\n```\n\n* ```frame``` is the current frame when a change is detected.\n* ```elem``` is a ```WatchElement``` object that I'm too lazy to describe for now.\n* ```exec_info``` is a tuple of ```(funcname, filename, lineno)``` of the line that changed the variable\n\nYou can also set change the callback function globally by\n\n```python\nwatch.config(callback=my_callback)\n```\n\nUse ```restore()``` to restore the default callback\n```python\nwatch.restore()\n```\n\n### Integrating with pdb\n\nwatchpoints can be used with pdb with ease. You can trigger pdb just like using ```breakpoint()``` when\nyour monitored variable is changed. Simply do\n\n```python\nwatch.config(pdb=True)\n```\n\nWhen you are in pdb, use ```q(uit)``` command to exit pdb, and the next change on the variable will trigger the pdb again.\n\n### Avoid import\n\nSometimes it's a hassle having to import the function in every single file. You can install the watch function to builtins\nand be able to call it in any files:\n\n```python\nwatch.install()  # or watch.install(\"func_name\") and use it as func_name()\n# Remove it from the builtins\nwatch.uninstall()  # if installed with a name, pass it to uninstall() as well\n```\n\n## Limitations\n\n* watchpoints uses ```sys.settrace()``` so it is not compatible with other libraries that use the same function.\n* watchpoints will slow down your program significantly, like other debuggers, so use it for debugging purpose only\n* ```watch()``` needs to be used by itself, not nested in other functions, to be correctly parsed\n* at this point, there might be other issues because it's still in development phase\n\n## Bugs/Requests\n\nPlease send bug reports and feature requests through [github issue tracker](https://github.com/gaogaotiantian/watchpoints/issues).\n\n## License\n\nCopyright Tian Gao, 2020.\n\nDistributed under the terms of the  [Apache 2.0 license](https://github.com/gaogaotiantian/watchpoints/blob/master/LICENSE).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgaogaotiantian%2Fwatchpoints","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgaogaotiantian%2Fwatchpoints","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgaogaotiantian%2Fwatchpoints/lists"}