{"id":21127016,"url":"https://github.com/pyrustic/hooking","last_synced_at":"2025-07-08T23:32:44.980Z","repository":{"id":61214702,"uuid":"540682360","full_name":"pyrustic/hooking","owner":"pyrustic","description":"Generic dual-paradigm hooking mechanism ","archived":false,"fork":false,"pushed_at":"2023-02-25T01:38:56.000Z","size":97,"stargazers_count":14,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-07-01T16:21:18.927Z","etag":null,"topics":["bind","callback","coupling","decorator","dual-paradigm","event","generic","hook","hooking","hooks","library","pyrustic","python"],"latest_commit_sha":null,"homepage":"https://pyrustic.github.io","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/pyrustic.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}},"created_at":"2022-09-24T02:21:19.000Z","updated_at":"2024-01-22T14:18:23.000Z","dependencies_parsed_at":"2023-01-22T18:46:02.225Z","dependency_job_id":"99f8e8f4-d115-41b5-9dd6-42e0dd31807b","html_url":"https://github.com/pyrustic/hooking","commit_stats":{"total_commits":32,"total_committers":1,"mean_commits":32.0,"dds":0.0,"last_synced_commit":"e3d16cb90d0d8c5ab7f04470ae00649471269cba"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/pyrustic/hooking","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrustic%2Fhooking","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrustic%2Fhooking/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrustic%2Fhooking/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrustic%2Fhooking/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pyrustic","download_url":"https://codeload.github.com/pyrustic/hooking/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrustic%2Fhooking/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264365839,"owners_count":23596912,"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":["bind","callback","coupling","decorator","dual-paradigm","event","generic","hook","hooking","hooks","library","pyrustic","python"],"created_at":"2024-11-20T04:46:16.240Z","updated_at":"2025-07-08T23:32:44.660Z","avatar_url":"https://github.com/pyrustic.png","language":"Python","readme":"[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![PyPI package version](https://img.shields.io/pypi/v/hooking)](https://pypi.org/project/hooking)\n[![Downloads](https://pepy.tech/badge/hooking)](https://pepy.tech/project/hooking)\n\n\u003c!-- Cover --\u003e\n\u003cdiv align=\"center\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/pyrustic/misc/master/assets/hooking/hooks.jpg\" alt=\"Hooks\" width=\"960\"\u003e\n    \u003cp align=\"center\"\u003e\n        \u003ca href=\"https://commons.wikimedia.org/wiki/File:%D0%9A%D0%B0%D0%BD%D0%B0%D1%82%D0%BD%D1%8B%D0%B9_%D1%81%D1%82%D1%80%D0%BE%D0%BF%D1%8B.jpg\"\u003eHardMediaGroup\u003c/a\u003e, \u003ca href=\"https://creativecommons.org/licenses/by-sa/3.0\"\u003eCC BY-SA 3.0\u003c/a\u003e, via Wikimedia Commons\n    \u003c/p\u003e\n\u003c/div\u003e\n\n\n\u003c!-- Intro Text --\u003e\n# Pyrustic Hooking\n\u003cb\u003e Generic dual-paradigm hooking mechanism \u003c/b\u003e\n\n\nThis project is part of the [Pyrustic Open Ecosystem](https://pyrustic.github.io).\n\u003e [Modules documentation](https://github.com/pyrustic/hooking/tree/master/docs/modules#readme)\n\n\n## Table of contents\n- [Overview](#overview)\n- [Examples](#examples)\n- [Functions and methods as targets](#functions-and-methods-as-targets)\n- [Anatomy of a hook](#anatomy-of-a-hook)\n- [Tight coupling](#tight-coupling)\n    - [Override a target](#override-a-target)\n    - [Wrap a target](#wrap-a-target)\n- [Loose coupling](#loose-coupling)\n    - [Tagging mechanism](#tagging-mechanism)\n    - [Bind hooks to tags](#bind-hooks-to-tags)\n    - [Chain break](#chain-break)\n    - [Freeze the hooking class](#freeze-the-hooking-class)\n    - [Exposed data](#exposed-data)\n    - [Reset the hooking class](#reset-the-hooking-class)\n    - [Subclassing the hooking class](#subclassing-the-hooking-class)\n- [Miscellaneous](#miscellaneous)\n- [Installation](#installation)\n\n# Overview\nThis project is a minimalist [Python](https://www.python.org) library that implements an intuitive, flexible, and generic dual-paradigm [hooking](https://en.wikipedia.org/wiki/Hooking) mechanism.\n\nIn short, **methods** and **functions**, called **targets**, are [decorated](https://peps.python.org/pep-0318/) and assigned user-defined **hooks**. So when a target is called, the assigned hooks will be automatically executed **upstream** or **downstream** according to the specification provided by the programmer.\n\nThe programmer may wish to have **tight** or **loose** [coupling](https://en.wikipedia.org/wiki/Coupling_(computer_programming)) between targets and hooks, depending on the requirement. Hence, for a nice developer experience, this library provides two interfaces each representing a **paradigm** (tight or loose coupling) to cover the needs.\n\nFrom a hook, the programmer has access to the keyword arguments passed to the decorator, the arguments passed to the target, the target itself, and other useful information through a `Context` object.\n\nDepending on whether the hook is executed upstream or downstream, the programmer can modify arguments to target, override the target itself with an arbitrary [callable](https://en.wikipedia.org/wiki/Callable_object), break the execution of the chain of hooks, modify the return of the target, et cetera.\n\n\n## Why use this library\nThis library allows the programmer to **wrap**, **augment**, or **override** a function or method with either a tight or loose coupling. It is therefore the perfect solution to create a [plugin mechanism](https://en.wikipedia.org/wiki/Plug-in_(computing)) for a project.\n\nIt can also be used for **debugging**, **benchmarking**, [event-driven programming](https://en.wikipedia.org/wiki/Event-driven_programming), implementing routing in a web development framework ([Flask](https://en.wikipedia.org/wiki/Flask_(web_framework)) uses decorators to implement [routing](https://hackersandslackers.com/flask-routes/)), et cetera.\n\nThe interface of this library is designed to be intuitive to use not only for **crafting** an [API](https://en.wikipedia.org/wiki/API) but also for **consuming** it.\n\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n# Examples\nHere are some examples of using this library in the tight and loose coupling paradigm.\n\n## Measure execution time\nHere we will use the tight coupling paradigm to override the target function with a hook. From inside the hook, the target will be executed and we will measure the execution time.\n\nThe following example can be copy-pasted into a file (e.g. `test.py`) and run as is:\n\n```python\nimport time\nfrom hooking import override\n\n\ndef timeit(context, *args, **kwargs):\n    # execute and measure the target run time\n    start_time = time.perf_counter()\n    context.result = context.target(*args, **kwargs)\n    total_time = time.perf_counter() - start_time\n    # print elapsed time\n    text = context.config.get(\"text\")  # get 'text' from config data\n    print(text.format(total=total_time))\n\n\n# decorate 'heavy_computation' with 'override' (tight coupling)\n# here, 'timeit' is the hook to execute when the target is called\n# all following keyword arguments are part of the configuration data\n@override(timeit, text=\"Done in {total:.3f} seconds !\")\ndef heavy_computation(a, b):\n    time.sleep(2)  # doing some heavy computation !\n    return a*b\n\n\nif __name__ == \"__main__\":\n    # run 'heavy_computation'\n    result = heavy_computation(6, 9)\n    print(\"Result:\", result)\n```\n\n```bash\n$ python -m test\nDone in 2.001 seconds !\nResult: 54\n```\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n## Routing by a fictional web framework\nThis example is divided into two parts:\n- the server-side Python script;\n- and the internals of the web framework.\n\n\n### Server-side Python script\n\n```python\n# Script for the server-side (API consumer side)\n# web_script.py\nfrom my_web_framework import Routing, start\n\n# bind to 'home_view', three tags representing\n# three paths. This view will be executed when\n# the user will request the home page for example\n@Routing.tag(\"/\")\n@Routing.tag(\"/home\")\n@Routing.tag(\"/index\")\ndef home_view():\n    return \"Welcome !\"\n\n\n@Routing.tag(\"/about\")\ndef about_view():\n    return \"About...\"\n\n\nif __name__ == \"__main__\":\n    start()\n```\n\n### Web framework internals\n```python\n# Framework internals (API creator side)\n# my_web_framework.py\nimport random\nfrom hooking import H\n\n# implementing custom routing mechanism by subclassing hooking.H\nRouting = H.subclass(\"Routing\")\n\ndef start():\n    # entry point of the web framework\n    # Get user request, then serve the appropriate page\n    path = get_user_request()\n    serve_page(path)\n\ndef get_user_request():\n    # use randomness to simulate user page request\n    paths = (\"/\", \"/home\", \"/index\", \"/about\")\n    return random.choice(paths)\n\ndef serve_page(path):\n    # get the list of functions and methods\n    # tagged with the Routing.tag decorator\n    cache = Routing.targets.get(path, list())\n    for target_info in cache:\n        view = target_info.target\n        html = view()\n        render_html(html)\n\ndef render_html(html):\n    print(html)\n```\n\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n\n# Functions and methods as targets\nAs mentioned in the [Overview](#overview) section, functions and methods are the targets to which hooks are attached with a tight or loose coupling. A hook is a function that can be attached one or more times to one or more targets.\n\nStatic methods, class methods, or decorated methods or functions should work fine with this library as long as one comes as close as possible to the native definition of the function or method. Example:\n\n```python\nfrom hooking import H, on_enter, on_leave\n\nclass MyClass:\n    # Good !\n    @staticmethod\n    @H.tag  # innermost\n    def do_something1(arg):\n        pass\n\n    # BAD !!!\n    @H.tag  # outermost\n    @classmethod\n    def do_something2(cls, arg):\n        pass\n\ndef my_hook(context, *arg, **kwargs):\n    pass\n\n# Good !\n@ExoticDecorator\n@on_enter(my_hook)  # innermost\ndef my_func():\n    pass\n\n# BAD !!!\n@on_leave(my_hook)  # outermost\n@ExoticDecorator\ndef my_func():\n    pass\n```\n\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n# Anatomy of a hook\nA hook is a callable that accepts an instance of `hooking.Context` and arguments passed to the target.\n\nThe `hooking.Context` instance exposes the following attributes:\n\n- **cls**: the hook class;\n- **hid**: the Hook ID (HID) as returned by the class methods `H.wrap`, `H.on_enter`, and `H.on_leave`;\n- **tag**: label string used to tag a function or method;\n- **config**: dictionary representing keyword arguments passed to the decorator;\n- **spec**: either the `hooking.ENTER` constant or the `hooking.LEAVE` constant;\n- **target**: the decorated function or method;\n- **args**: tuple representing the positional arguments passed to the target;\n- **kwargs**: dictionary representing the keyword arguments passed to the target;\n- **result**: depending on the context, this attribute may contain the value returned by the target;\n- **shared**: ordered dictionary to store shared data between hooks (from upstream to downstream).\n\nThe attributes listed above can be updated with the `Context.update` method that accepts keyword arguments.\n\n\n```python\nfrom hooking import H\n\n# defining my_hook\ndef my_hook(context, *args, **kwargs):\n    if context.tag != \"target\":\n        raise Exception(\"Wrong tag !\")\n    # reset arguments\n    context.update(args=tuple(), kwargs=dict())\n\n@H.tag(\"target\")  # tagging my_func with \"target\"\ndef my_func(*args, **kwargs):\n    pass\n\n# binding my_hook to the tag \"target\"\nH.on_enter(\"target\", my_hook)\n```\n\n## Modify arguments to target\nFrom an upstream hook, we can change the arguments passed to a target:\n```python\n# ...\n\ndef upstream_hook(context, *args, **kwargs):\n    # positional arguments are represented with a tuple\n    context.args = (val1, val2)\n    # keywords arguments are represented with a dictionary\n    context.kwargs = {\"item1\": val1, \"item2\": val2}\n \n# ...\n``` \n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n## Override the target from a hook\nThe library exposes the `hooking.override` to override a target function:\n\n```python\nfrom hooking import override\n\ndef myhook(context, *args, **kwargs):\n    context.result = new_target_function(*args, **kwargs)\n\n@override(myhook) \ndef target():\n    pass\n```\n\nbut one can still override the target from an arbitrary **upstream** hook:\n\n```python\nfrom hooking import on_enter\n\ndef upstream_hook(context, *args, **kwargs):\n    # override target with a new target that\n    # that accepts same type signature\n    context.target = new_target_function\n \n@on_enter(upstream_hook) \ndef target():\n    pass\n``` \n\nNote that you can set `None` to `context.target` to prevent the library for automatically running the target between the execution of upstream and downstream hooks.\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n## Modify the return of a target\nFrom a downstream hook, we can change the return of a target:\n```python\n# ...\n\ndef downstream_hook(context, *args, **kwargs):\n    context.result = new_value\n \n# ...\n``` \n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n\n# Tight coupling\nIn this paradigm, hooks are directly bound to target. The library exposes the following decorators to decorate targets:\n\n|Decorator|Description|Signature\n|---|---|---|\n|`hooking.override`|Bind to a target a hook that will override it|`@override(hook, **config)`|\n|`hooking.wrap`|Bind to a target two hooks that will be executed upstream and downstream|`@wrap(hook1, hook2, **config)`|\n|`hooking.on_enter`|Bind to a target a hook that will be executed upstream, i.e, before the target|`@on_enter(hook, **config)`|\n|`hooking.on_leave`|Bind to a target a hook that will be executed downstream, i.e, after the target|`@on_leave(hook, **config)`|\n\n## Override a target\nThe library exposes the `hooking.override` decorator to bind to a target a hook that will override it:\n\n\n```python\nfrom hooking import override\n\ndef myhook(context, *args, **kwargs):\n    context.result = context.target(*args, **kwargs)\n    # or\n    my_new_target = lambda *args, **kwargs: print(\"New Target Here !\")\n    context.result = my_new_target(*args, **kwargs)\n\n# override target with myhook\n@override(hook3, foo=42, bar=\"Alex\")  # foo and bar are config data\ndef target():\n    pass\n```\n\n\u003e Note that with the `hooking.override` decorator, the programmer must execute the target or its replacement inside the hook and set the result to `context.result`.\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n## Wrap a target\nThe `hooking.wrap` decorator allows the programmer to wrap the target between two hooks that will be executed upstream and downstream. Two additional decorators `hooking.on_enter` and `hooking.on_leave` allow the programmer to bind either a upstream or a downstream hook to a target.\n\n```python\nfrom hooking import wrap, on_enter, on_leave\n\ndef hook1(context, *args, **kwargs):\n    pass\n\ndef hook2(context, *args, **kwargs):\n    pass\n\n# bind an upstream hook and a downstream hook to my_func1\n@wrap(hook1, hook2, foo=42, bar=\"Alex\")  # foo and bar are config data\ndef my_func1():\n    pass\n\n# bind an upstream hook to my_func2\n@on_enter(hook1, foo=42, bar=\"Alex\")\ndef my_func2():\n    pass\n\n# bind a downstream hook to my_func3\n@on_leave(hook2, foo=42, bar=\"Alex\")\ndef my_func3():\n    pass\n\n```\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n# Loose coupling\nIn this paradigm, hooks aren't directly bound to target but to tags which are linked to targets. The library exposes the `hooking.H` class to support the loose coupling paradigm. In short, the `hooking.H.tag` decorator is used to tag targets, then class methods `hooking.H.on_enter`, `hooking.H.on_leave`, and `hooking.H.wrap` are used to bind hooks to tags.\n\n## Tagging mechanism\nThe `hooking.H.tag` class method allows you to tag a function or a method:\n```python\nfrom hooking import H\n\n@H.tag\ndef my_func(*args, **kwargs):\n    pass\n\nclass MyClass:\n    @H.tag\n    def my_method(self, *args, **kwargs):\n        pass\n```\nThe `hooking.H.tag` decorator accepts a `label` string as argument. By default, when this argument isn't provided, the library uses the [qualified name](https://peps.python.org/pep-3155/) of the method or function as the `label`.\n\nHere we provide the `label` argument:\n```python\nfrom hooking import H\n\n@H.tag(\"my_func\")\ndef my_func(*args, **kwargs):\n    pass\n\nclass MyClass:\n    @H.tag(\"MyClass.my_method\")\n    def my_method(self, *args, **kwargs):\n        pass\n```\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n## Bind hooks to tags\nThese are the `hooking.H` class methods to bind hooks to tags:\n\n|Class method|Description|Signature\n|---|---|---|\n|`hooking.H.on_enter`|Bind to a tag a hook that will be executed downstream, i.e, before the target|`on_enter(tag, hook)`|\n|`hooking.H.on_leave`|Bind to a tag a hook that will be executed downstream, i.e, after the target|`on_leave(tag, hook)`|\n|`hooking.H.wrap`|Bind to a tag two hooks that will be executed upstream and downstream|`wrap(tag, hook1, hook2)`|\n\n\n```python\nfrom hooking import H\n\n@H.tag(\"target\")\ndef my_func(*args, **kwargs):\n    pass\n\ndef my_hook1(context, *args, **kwargs):\n    pass\n\ndef my_hook2(context, *args, **kwargs):\n    pass\n\n# bind my_hook1 to \"target\" and run it upstream\nhook_id = H.on_enter(\"target\", my_hook1)\n\n# bind my_hook2 to \"target\" and run it downstream\nhook_id = H.on_leave(\"target\", my_hook2)\n\n# bind my_hook1 and my_hook2 to \"target\"\nhook_id1, hook_id2 = H.wrap(\"target\", my_hook1, my_hook2)\n```\n\n### Unbind hooks\n\nWhenever a hook is bound to a tag, the Hook ID (HID) which could be used later to **unbind** the hook, is returned:\n\n```python\nfrom hooking import H\n\ndef hook(context, *args, **kwargs):\n    pass\n\n# bind\nhid = H.on_enter(\"tag\", hook)\n\n# unbind\nH.unbind(hid)\n```\n\n**Multiple** hooks can be unbound in a single statement:\n\n```python\nfrom hooking import H\n\ndef hook1(context, *args, **kwargs):\n    pass\n\ndef hook2(context, *args, **kwargs):\n    pass\n\n# bind\nhid1 = H.on_enter(\"tag\", hook1)\nhid2 = H.on_leave(\"tag\", hook2)\n\n# unbind multiple hooks manually\nH.unbind(hid1, hid2)\n```\n\n\n### Clear hooks bound to a specific tag\nThe `clear` class method of `hooking.H` unbinds all hooks bound to a specific tag:\n```python\nfrom hooking import H\n\ndef hook1(context, *args, **kwargs):\n    pass\n\ndef hook2(context, *args, **kwargs):\n    pass\n\n@H.tag\ndef target():\n    pass\n\n# bind\nhid1 = H.on_enter(\"target\", hook1)\nhid2 = H.on_enter(\"target\", hook2)\n\n# unbind hook1 and hook2 from \"target\"\nH.clear(\"target\")\n\n```\n\nYou can clear **multiple** tags in a single statement:\n```python\nfrom hooking import H\n\ndef hook1(context, *args, **kwargs):\n    pass\n\ndef hook2(context, *args, **kwargs):\n    pass\n\n@H.tag\ndef target1():\n    pass\n\n@H.tag\ndef target2():\n    pass\n\n# bind\nhid1 = H.on_enter(\"target1\", hook1)\nhid2 = H.on_enter(\"target2\", hook2)\n\n# unbind hook1 and hook2 from \"target1\" and \"target2\"\nH.clear(\"target1\", \"target2\")\n\n```\n\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n\n## Chain break\nThis library exposes an [Exception](https://docs.python.org/3/library/exceptions.html#Exception) subclass to allow the programmer to break the execution of a chain of hooks:\n\n```python\nfrom hooking import H, ChainBreak\n\n@H.tag(\"target\")\ndef my_func(*args, **kwargs):\n    pass\n    \ndef hook1(context, *args, **kwargs):\n    pass\n\ndef hook2(context, *args, **kwargs):\n    raise ChainBreak\n\ndef hook3(context, *args, **kwargs):\n    pass\n\n\n# bind hook1, hook2 and hook3 to 'target'\nfor hook in (hook1, hook2, hook3):\n    H.on_enter(\"target\", hook)\n\n# call the target\nmy_func()\n\n# since the target was called,\n# the chain of hooks (hook1, hook2, hook3)\n# must be executed.\n\n# hook2 having used ChainBreak,\n# the chain of execution will be broken\n# and hook3 will be ignored\n\n```\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n## Freeze the hooking class\nWe could freeze the hooking class and thus prevent the execution of all hooks:\n```python\nfrom hooking import H\n\n@H.tag\ndef my_func(*args, **kwargs):\n    pass\n\nH.freeze()\n\n# from now, no hook will be executed anymore\n```\n\n### Unfreeze the hooking class\n\nTo **unfreeze** the hooking class, use the `H.unfreeze` class method:\n```python\nfrom hooking import H\n\nH.unfreeze()\n\n# from now, hooks will be executed when needed\n```\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n## Exposed data\nThe library exposes data through a class method, class variables, and data transfer objects (namedtuples).\n\n### Get the list of upstream and downstream hooks\nUpstream and downstream hooks bound to a specific tag can be retrieved with the `get_hooks` class method.\n\n```python\nfrom hooking import H\n\n# returns a 2-tuple of upstream hooks and\n# downstream hooks\nupstream_hooks, downstream_hooks = H.get_hooks(\"tag\")\n\n# iterate through upstream_hooks which is\n# a list of instances of hooking.HookInfo\nfor hook_info in upstream_hooks:\n    print(hook_info)\n```\n\n### Read-only class variables\nThe `hooking.H` class exposes the following class variables:\n\n|Class variable|Description|\n|---|---|\n|`targets`| Ordered dictionary. Keys are tags and values are lists of instances of `hooking.TargetInfo`. Example: {\"tag1\": [TargetInfo(), TargetInfo(), ...], ...}|\n|`hooks`| Ordered dictionary. Keys are tags and values are lists of instances of `hooking.HookInfo`. Example: {\"tag1\": [HookInfo(), HookInfo(), ...], ...}| \n|`tags`| The set of registered tags|\n|`frozen`| Boolean to tell whether the hooking mechanism is frozen or not|\n\n\u003e **Note:** it is not recommended to modify the contents of these class variables directly. Use the appropriate class methods for this purpose.\n\nBoth `hooking.TargetInfo` and `hoooking.HookInfo` are namedtuples that will be explored in the next subsection.\n\n### Data transfer object\nHere are the fields from the `hooking.TargetInfo` namedtuple:\n- **cls**: the hooking class;\n- **tag**: the string label that represents the tag;\n- **target**: the target method or function;\n- **config**: dictionary containing the configuration data passed to the decorator.\n\nHere are the fields from the `hooking.HookInfo` namedtuple:\n- **cls**: the hooking class;\n- **hid**: the hook identifier;\n- **hook**: the callable representing the hook;\n- **tag**: the string label that represents the tag;\n- **spec**: either `hooking.ENTER` or `hooking.LEAVE`.\n\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n\n## Reset the hooking class\nYou may need to reset the hooking class, i.e., reinitialize the contents of the following class variables: `hooking.H.hooks`, `hooking.H.tags`, and `hooking.H.frozen`. In this case, you just have to call the `hooking.H.reset` class method.\n\n\u003e **Note:** targets won't be removed.\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n## Subclassing the hooking class\nThis library is flexible enough to allow the programmer to create their own subclass of `hooking.H` like this:\n\n```python\nfrom hooking import H\n\nMyCustomHookingClass = H.subclass(\"MyCustomHookingClass\")\n```\n\nSubclassing `hooking.H` allows the programmer to apply the [separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns). For example, a web framework creator might create a `Routing` subclass to implement a routing mechanism, and also create an `Extension` subclass to implement a plugin mechanism. Each subclass would have its own set of tags, hooks, and targets.\n\n\u003e **Note:** class variables are automatically reset when subclassing `hooking.H`.\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n# Miscellaneous\n\n## Multithreading\nWhenever threads are introduced into a program, the state shared between threads becomes vulnerable to corruption. To avoid this situation, this library uses [threading.Lock](https://docs.python.org/3/library/threading.html#lock-objects) as a synchronization tool.\n\n\n\u003cp align=\"right\"\u003e\u003ca href=\"#readme\"\u003eBack to top\u003c/a\u003e\u003c/p\u003e\n\n# Installation\n**Hooking** is **cross-platform** and should work on **Python 3.5** or [newer](https://www.python.org/downloads/).\n\n## First time\n\n```bash\n$ pip install hooking\n```\n\n## Upgrade\n```bash\n$ pip install hooking --upgrade --upgrade-strategy eager\n\n```\n\n## Show package information\n```bash\n$ pip show hooking\n```\n\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\n[Back to top](#readme)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpyrustic%2Fhooking","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpyrustic%2Fhooking","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpyrustic%2Fhooking/lists"}