{"id":16287620,"url":"https://github.com/ionite34/einspect","last_synced_at":"2025-04-08T03:13:37.543Z","repository":{"id":65234949,"uuid":"577423561","full_name":"ionite34/einspect","owner":"ionite34","description":"Extended Inspect - View and modify memory structures of runtime objects.","archived":false,"fork":false,"pushed_at":"2025-04-07T19:48:05.000Z","size":870,"stargazers_count":40,"open_issues_count":16,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-07T20:40:43.225Z","etag":null,"topics":["cpython","cpython-api","cpython-internals","inspect","python"],"latest_commit_sha":null,"homepage":"https://docs.ionite.io/einspect","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/ionite34.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-12T17:49:51.000Z","updated_at":"2025-02-01T18:59:36.000Z","dependencies_parsed_at":"2023-01-15T18:45:21.139Z","dependency_job_id":"5d85a734-f25c-4c60-8a8e-cca2a674acc0","html_url":"https://github.com/ionite34/einspect","commit_stats":{"total_commits":644,"total_committers":3,"mean_commits":"214.66666666666666","dds":"0.021739130434782594","last_synced_commit":"f0fee451aa7afa4052a96b4c1d22107c99c481f6"},"previous_names":[],"tags_count":41,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionite34%2Feinspect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionite34%2Feinspect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionite34%2Feinspect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionite34%2Feinspect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ionite34","download_url":"https://codeload.github.com/ionite34/einspect/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247767236,"owners_count":20992548,"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":["cpython","cpython-api","cpython-internals","inspect","python"],"created_at":"2024-10-10T19:45:38.454Z","updated_at":"2025-04-08T03:13:37.519Z","avatar_url":"https://github.com/ionite34.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# einspect\n\n\u003c!-- start badges --\u003e\n\n[![Build](https://github.com/ionite34/einspect/actions/workflows/build.yml/badge.svg)](https://github.com/ionite34/einspect/actions/workflows/build.yml)\n[![codecov](https://codecov.io/gh/ionite34/einspect/branch/main/graph/badge.svg?token=v71SdG5Bo6)](https://codecov.io/gh/ionite34/einspect)\n[![security](https://snyk-widget.herokuapp.com/badge/pip/einspect/badge.svg)](https://security.snyk.io/package/pip/einspect)\n\n[![PyPI](https://img.shields.io/pypi/v/einspect)][pypi]\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/einspect)][pypi]\n\n[pypi]: https://pypi.org/project/einspect/\n\n\u003c!-- end badges --\u003e\n\n\u003e Extended Inspections for CPython\n\n## [Documentation](https://docs.ionite.io/einspect)\n\n- [View and modify memory structures of live objects.](#check-detailed-states-of-built-in-objects)\n- [Able to mutate immutable objects like tuples and ints.](#mutate-tuples-strings-ints-or-other-immutable-types)\n- [Modify slot functions or attributes of built-in types.](#modify-attributes-of-built-in-types-get-original-attributes-with-orig)\n- [Fully typed, extensible framework in pure Python.](#move-objects-in-memory)\n\n\u003c!-- start intro --\u003e\n\n## Check detailed states of built-in objects\n```python\nfrom einspect import view\n\nls = [1, 2, 3]\nv = view(ls)\nprint(v.info())\n```\n```python\nPyListObject(at 0x2833738):\n   ob_refcnt: Py_ssize_t = 5\n   ob_type: *PyTypeObject = \u0026[list]\n   ob_item: **PyObject = \u0026[\u0026[1], \u0026[2], \u0026[3]]\n   allocated: Py_ssize_t = 4\n```\n\n[doc_tuple_view]: https://docs.ionite.io/einspect/api/views/view_tuple.html#einspect.views.view_tuple\n[doc_str_view]: https://docs.ionite.io/einspect/api/views/view_str.html#einspect.views.view_str\n[py_doc_mutable_seq]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types\n## Mutate tuples, strings, ints, or other immutable types\n\u003e [TupleView][doc_tuple_view] and [StrView][doc_str_view] supports all [MutableSequence][py_doc_mutable_seq] methods (append, extend, insert, pop, remove, reverse, clear).\n\n\u003e ⚠️ A note on [safety.](#safety)\n```python\nfrom einspect import view\n\ntup = (1, 2)\nv = view(tup)\n\nv[1] = 500\nprint(tup)      # (1, 500)\nv.append(3)\nprint(tup)      # (1, 500, 3)\n\ndel v[:2]\nprint(tup)      # (3,)\nprint(v.pop())  # 3\n\nv.extend([1, 2])\nprint(tup)      # (1, 2)\n\nv.clear()\nprint(tup)      # ()\n```\n```python\nfrom einspect import view\n\ntext = \"hello\"\n\nv = view(text)\nv[1] = \"3\"\nv[4:] = \"o~\"\nv.append(\"!\")\n\nprint(text)  # h3llo~!\nv.reverse()\nprint(text)  # !~oll3h\n```\n```python\nfrom einspect import view\n\nn = 500\nview(n).value = 10\n\nprint(500)        # 10\nprint(500 == 10)  # True\n```\n\n## Modify attributes of built-in types, get original attributes with `orig`\n```python\nfrom einspect import view, orig\n\nv = view(int)\nv[\"__name__\"] = \"custom_int\"\nv[\"__iter__\"] = lambda s: iter(range(s))\nv[\"__repr__\"] = lambda s: \"custom: \" + orig(int).__repr__(s)\n\nprint(int)\nfor i in 3:\n    print(i)\n```\n```\n\u003cclass 'custom_int'\u003e\ncustom: 0\ncustom: 1\ncustom: 2\n```\n\n## Implement methods on built-in types\n\u003e See the [Extending Types](https://docs.ionite.io/einspect/extending_types.html) docs page for more information.\n```python\nfrom einspect import impl, orig\n\n@impl(int)\ndef __add__(self, other):\n    other = int(other)\n    return orig(int).__add__(self, other)\n\nprint(50 + \"25\")  # 75\n```\n\n## Move objects in memory\n```python\nfrom einspect import view\n\ns = \"meaning of life\"\n\nv = view(s)\nwith v.unsafe():\n    v \u003c\u003c= 42\n\nprint(\"meaning of life\")        # 42\nprint(\"meaning of life\" == 42)  # True\n```\n\n## CPython Struct bindings and API methods\n- Easily make calls to CPython stable ABI (`ctypes.pythonapi`) as bound methods on `PyObject` instances.\n```python\nfrom einspect.structs import PyDictObject\n\nd = {\"a\": (1, 2), \"b\": (3, 4)}\n\nres = PyDictObject(d).GetItem(\"a\")\n\nif res:\n    print(res.contents.NewRef())\n```\n\u003e Equivalent to the following with ctypes:\n```python\nfrom ctypes import pythonapi, py_object, c_void_p, cast\n\nd = {\"a\": (1, 2), \"b\": (3, 4)}\n\nPyDict_GetItem = pythonapi[\"PyDict_GetItem\"]\n# Can't use auto cast py_object for restype,\n# since missing keys return NULL and causes segmentation fault with no set error\nPyDict_GetItem.restype = c_void_p\nPyDict_GetItem.argtypes = [py_object, py_object]\n\nres = PyDict_GetItem(d, \"a\")\nres = cast(res, py_object)\n\nPy_NewRef = pythonapi[\"Py_NewRef\"]\nPy_NewRef.restype = py_object\nPy_NewRef.argtypes = [py_object]\n\ntry:\n    print(Py_NewRef(res.value))\nexcept ValueError:\n    pass\n```\n\n- Create new instances of PyObject structs with field values, from existing objects, or from address.\n```python\nfrom einspect.structs import PyLongObject, PyTypeObject\n\nx = PyLongObject(\n    ob_refcnt=1,\n    ob_type=PyTypeObject(int).as_ref(),\n    ob_size=1,\n    ob_item=[15],\n).into_object()\n\nprint(x)        # 15\nprint(x == 15)  # True\nprint(x is 15)  # False\n```\n\n\u003c!-- end intro --\u003e\n\n## Fully typed interface\n\u003cimg width=\"551\" alt=\"image\" src=\"https://user-images.githubusercontent.com/13956642/211129165-38a1c405-9d54-413c-962e-6917f1f3c2a1.png\"\u003e\n\n## Safety\n\n This project is mainly for learning purposes or inspecting and debugging CPython internals for development and fun. You should not violate language conventions like mutability in production software and libraries.\n\nThe interpreter makes assumptions regarding types that are immutable, and changing them causes all those usages to be affected. While the intent of the project is to make a memory-correct mutation without further side effects, there can be very significant runtime implications of mutating interned strings with lots of shared references, including interpreter crashes.\n\nFor example, some strings like \"abc\" are interned and used by the interpreter. Changing them changes all usages of them, even attribute calls like `collections.abc`.\n\n\u003e The spirit of safety maintained by einspect is to do with memory layouts, not functional effects.\n\n### For example, appending to tuple views (without an unsafe context) will check that the resize can fit within allocated memory\n```python\nfrom einspect import view\n\ntup = (1, 2)\nv = view(tup)\n\nv.append(3)\nprint(tup)  # (1, 2, 3)\n\nv.append(4)\n# UnsafeError: insert required tuple to be resized beyond current memory allocation. Enter an unsafe context to allow this.\n```\n- Despite this, mutating shared references like empty tuples can cause issues in interpreter shutdown and other runtime operations.\n```python\nfrom einspect import view\n\ntup = ()\nview(tup).append(1)\n```\n```\nException ignored in: \u003cmodule 'threading' from '/lib/python3.11/threading.py'\u003e\nTraceback (most recent call last):\n  File \"/lib/python3.11/threading.py\", line 1563, in _shutdown\n    _main_thread._stop()\n  File \"/lib/python3.11/threading.py\", line 1067, in _stop\n    with _shutdown_locks_lock:\nTypeError: 'str' object cannot be interpreted as an integer\n```\n\n### Similarly, memory moves are also checked for GC-header compatibility and allocation sizes\n```python\nfrom einspect import view\n\nv = view(101)\nv \u003c\u003c= 2\n\nprint(101)  # 2\n\nv \u003c\u003c= \"hello\"\n# UnsafeError: memory move of 54 bytes into allocated space of 32 bytes is out of bounds. Enter an unsafe context to allow this.\n```\n\n- However, this will not check the fact that small integers between (-5, 256) are interned and used by the interpreter. Changing them may cause issues in any library or interpreter Python code.\n```python\nfrom einspect import view\n\nview(0) \u003c\u003c 100\n\nexit()\n# sys:1: ImportWarning: can't resolve package from __spec__ or __package__, falling back on __name__ and __path__\n# IndexError: string index out of range\n```\n\n## Table of Contents\n- [Views](#views)\n  - [Using the `einspect.view` constructor](#using-the-einspectview-constructor)\n  - [Inspecting struct attributes](#inspecting-struct-attributes)\n\n## Views\n\n### Using the `einspect.view` constructor\n\nThis is the recommended and simplest way to create a `View` onto an object. Equivalent to constructing a specific `View` subtype from `einspect.views`, except the choice of subtype is automatic based on object type.\n\n```python\nfrom einspect import view\n\nprint(view(1))\nprint(view(\"hello\"))\nprint(view([1, 2]))\nprint(view((1, 2)))\n```\n\u003e ```\n\u003e IntView(\u003cPyLongObject at 0x102058920\u003e)\n\u003e StrView(\u003cPyUnicodeObject at 0x100f12ab0\u003e)\n\u003e ListView(\u003cPyListObject at 0x10124f800\u003e)\n\u003e TupleView(\u003cPyTupleObject at 0x100f19a00\u003e)\n\u003e ```\n\n### Inspecting struct attributes\n\nAttributes of the underlying C Struct of objects can be accessed through the view's properties.\n```python\nfrom einspect import view\n\nls = [1, 2]\nv = view(ls)\n\n# Inherited from PyObject\nprint(v.ref_count)  # ob_refcnt\nprint(v.type)       # ob_type\n# Inherited from PyVarObject\nprint(v.size)       # ob_size\n# From PyListObject\nprint(v.item)       # ob_item\nprint(v.allocated)  # allocated\n```\n\u003e ```\n\u003e 4\n\u003e \u003cclass 'tuple'\u003e\n\u003e 3\n\u003e \u003ceinspect.structs.c_long_Array_3 object at 0x105038ed0\u003e\n\u003e ```\n\n## 2. Writing to view attributes\n\nWriting to these attributes will affect the underlying object of the view.\n\nNote that most memory-unsafe attribute modifications require entering an unsafe context manager with `View.unsafe()`\n```python\nwith v.unsafe():\n    v.size -= 1\n\nprint(obj)\n```\n\u003e `(1, 2)`\n\nSince `items` is an array of integer pointers to python objects, they can be replaced by `id()` addresses to modify\nindex items in the tuple.\n```python\nfrom einspect import view\n\ntup = (100, 200)\n\nwith view(tup).unsafe() as v:\n    s = \"dog\"\n    v.item[0] = id(s)\n\nprint(tup)\n```\n\u003e ```\n\u003e ('dog', 200)\n\u003e\n\u003e \u003e\u003e Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)\n\u003e ```\n\nSo here we did set the item at index 0 with our new item, the string `\"dog\"`, but this also caused a segmentation fault.\nNote that the act of setting an item in containers like tuples and lists \"steals\" a reference to the object, even\nif we only supplied the address pointer.\n\nTo make this safe, we will have to manually increment a ref-count before the new item is assigned. To do this we can\neither create a `view` of our new item, and increment its `ref_count += 1`, or use the apis from `einspect.api`, which\nare pre-typed implementations of `ctypes.pythonapi` methods.\n```python\nfrom einspect import view\nfrom einspect.api import Py\n\ntup = (100, 200)\n\nwith view(tup).unsafe() as v:\n    a = \"bird\"\n    Py.IncRef(a)\n    v.item[0] = id(a)\n\n    b = \"kitten\"\n    Py.IncRef(b)\n    v.item[1] = id(b)\n\nprint(tup)\n```\n\u003e `('bird', 'kitten')`\n\n🎉 No more seg-faults, and we just successfully set both items in an otherwise immutable tuple.\n\nTo make the above routine easier, you can access an abstraction by simply indexing the view.\n\n```python\nfrom einspect import view\n\ntup = (\"a\", \"b\", \"c\")\n\nv = view(tup)\nv[0] = 123\nv[1] = \"hm\"\nv[2] = \"🤔\"\n\nprint(tup)\n```\n\u003e `(123, 'hm', '🤔')`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fionite34%2Feinspect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fionite34%2Feinspect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fionite34%2Feinspect/lists"}