{"id":21611713,"url":"https://github.com/ktsstudio/ktx","last_synced_at":"2025-04-11T05:34:43.948Z","repository":{"id":257822049,"uuid":"871297369","full_name":"ktsstudio/ktx","owner":"ktsstudio","description":"Shared context library for Python","archived":false,"fork":false,"pushed_at":"2025-04-06T10:51:44.000Z","size":57,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-04-06T11:28:40.641Z","etag":null,"topics":["asyncio","context","logging","python"],"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/ktsstudio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2024-10-11T16:56:11.000Z","updated_at":"2025-03-23T11:18:43.000Z","dependencies_parsed_at":null,"dependency_job_id":"2ab8ff9b-ce67-4883-a3e2-286dcc48cf9e","html_url":"https://github.com/ktsstudio/ktx","commit_stats":null,"previous_names":["ktsstudio/ktx"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktsstudio%2Fktx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktsstudio%2Fktx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktsstudio%2Fktx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ktsstudio%2Fktx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ktsstudio","download_url":"https://codeload.github.com/ktsstudio/ktx/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248348294,"owners_count":21088844,"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":["asyncio","context","logging","python"],"created_at":"2024-11-24T21:13:38.962Z","updated_at":"2025-04-11T05:34:43.940Z","avatar_url":"https://github.com/ktsstudio.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ktx - Shared context library\n\n[![Build](https://github.com/ktsstudio/ktx/actions/workflows/actions.yaml/badge.svg?branch=main)](https://github.com/ktsstudio/ktx/actions)\n[![PyPI](https://img.shields.io/pypi/v/ktx.svg)](https://pypi.python.org/pypi/ktx)\n\nktx is a Context library aimed to simplify a process of creating and managing shared data in Python.\n\n## Quick start\n\nContext example:\n\n```python\nfrom ktx import ctx_bind\nfrom ktx.ctx import ContextFactory\n\nctx_factory = ContextFactory()\n\nwith ctx_bind(ctx_factory.create()) as ctx:\n    ctx.set(\"some_attribute\", \"value1\")\n```\n\nContextUser example:\n\n```python\nfrom ktx import ctx_user_bind\nfrom ktx.user import ContextUserFactory\n\nuser_factory = ContextUserFactory()\n\nwith ctx_user_bind(user_factory.create()) as user:\n    user.set_id(42)\n    user.set_email(\"foo@example.com\")\n    user.set_username(\"foo\")\n    user.set_ip_address(\"127.0.0.1\")\n\n    # then you can get all this data through getter methods:\n    print(user.get_id())\n    print(user.get_email())\n    print(user.get_username())\n    print(user.get_ip_address())\n```\n\n## Sentry integration\n\nThere's a Sentry integration in the `ktx` library that allows all key-value pairs be propagated to Sentry events:\n```python\nfrom ktx.ctx import ContextFactory\nfrom ktx.adapters.sentry import SentryDataAdapter\n\nfactory = ContextFactory(adapters=[SentryDataAdapter()])\nctx = factory.create()\n\nctx.set(\"attr1\", \"val1\")\nctx.set(\"_attr2\", \"val2\")  # private fields don't get sent to Sentry\n\n# attr1 will be in Sentry event's extras, and _attr2 will not be\n```\n\nThe similar situation is with `ContextUser`:\n\n```python\nfrom ktx.user import ContextUserFactory\nfrom ktx.adapters.sentry import SentryUserAdapter\n\nfactory = ContextUserFactory(adapters=[SentryUserAdapter()])\nuser = factory.create()\n\nuser.set_id(42)\nuser.set_email(\"foo@example.com\")\nuser.set_username(\"foo\")\nuser.set_ip_address(\"127.0.0.1\")\n\n# Sentry will receive user object with all proper fields set\n```\n\n## Introduction\n\n### Context\n\nYou may set created Context object as *current* for current thread or asyncio.Task:\n\n```python\nfrom ktx import ctx_bind, get_current_ctx\nfrom ktx.ctx import ContextFactory\n\nctx_factory = ContextFactory()\n\nwith ctx_bind(ctx_factory.create()) as ctx:\n    ctx.set(\"some_attribute\", \"value1\")\n\n    assert get_current_ctx() is ctx\n```\n\nBut also (thanks for `ContextVar`) context is available in any place of current coroutine in asyncio-world (note that asyncio-context is copied in the creating of new tasks):\n\n````python\nimport asyncio\n\nfrom ktx import get_current_ctx, ctx_bind\nfrom ktx.ctx import ContextFactory\n\n\nasync def f1():\n    ctx = get_current_ctx()\n    assert ctx.get(\"some_attribute\") == \"value1\"\n\n\nasync def main():\n    ctx_factory = ContextFactory()\n\n    with ctx_bind(ctx_factory.create()) as ctx:\n        ctx.set(\"some_attribute\", \"value1\")\n\n        task1 = asyncio.create_task(f1())\n        await asyncio.sleep(1)\n\n    await task1\n````\n\nThere exists an abstract interface (Protocol) for any \"kind of Contex\", so you may implement your own Context classes by implementing `ktx.abc.Context` protocol:\n\n\n## API\n\n### Context\n\nContext provides the following methods:\n\n- `set(key: str, value: Any) -\u003e Any`: set value by key\n- `get(key: str) -\u003e Any`: get value by key\n- `get_data() -\u003e Mapping[str, Any]`: get all shared data\n- `ktx_id() -\u003e str`: get unique id of context\n\n## Data Inheritance\n\nThis is best described using the following snippet:\n\n```python\nfrom ktx import ctx_bind\nfrom ktx.ctx import ContextFactory\n\nctx_factory = ContextFactory()\n\nwith ctx_bind(ctx_factory.create()) as parent_ctx:\n    parent_ctx.set(\"attr1\", \"val1\")\n    parent_ctx.set(\"attr2\", \"val2\")\n\n    with ctx_bind(ctx_factory.create()) as child_ctx:\n        assert child_ctx.get(\"attr1\") == \"val1\"\n        assert child_ctx.get(\"attr2\") == \"val2\"\n\n        child_ctx.set(\"attr2\", \"val3\")\n        assert child_ctx.get(\"attr2\") == \"val3\"\n\n    assert parent_ctx.get(\"attr2\") == \"val2\"\n```\n\n\n## Logging\n\n### Structlog\nThere is a helper function `ktx.log.ktx_add_log` useful for [structlog](https://structlog.org/) processors that propagates all Context-specific attributes to a logging event dict.\n\n## Custom context\n\nIt is possible to define a custom Context class in order to better support strong typing. You would need to implement `ktx.abc.`Context protocol and then you may use it with `ctx_bind` functions as usual.\n\nNote that you would need to implement general `get()` and `set()` methods for arbitrary fields as they may be accessed by other libraries which are using `Context`.\n\nAnd the `get_data()` method must return all *shared* data of this context so it will be available during logging. For example:\n\n```python\nfrom typing import Mapping, Any\n\nfrom ktx.abc import AbstractContext\n\n\nclass MyContext(AbstractContext):  # Note that inheritance from the protocol is not required.\n    def __init__(self, ktx_id: str, *, custom_field: str):\n        self.custom_field = custom_field\n\n        self._ktx_id = ktx_id\n        self._data: dict[str, Any] = {}\n\n    def ktx_id(self) -\u003e str:\n        return self._ktx_id\n\n    def get_data(self) -\u003e Mapping[str, Any]:\n        return {\n            **self._data,\n            \"custom_field\": self.custom_field,\n        }\n\n    def get(self, key: str) -\u003e Any:\n        return self._data.get(key)\n\n    def set(self, key: str, value: Any):\n        self._data[key] = value\n```\n\nAlso you may benefit from extending included `Context` class:\n```python\nfrom typing import Mapping, Any\n\nfrom ktx.ctx import Context\n\nclass MyContext(Context):  # Note that inheritance from the protocol is not required.\n    def __init__(self, ktx_id: str, *, custom_field: str):\n        super().__init__(ktx_id)\n        self.custom_field = custom_field\n```\n\n\nAnd then you may use this `MyContext` in safe manner like this:\n\n```python\nfrom ktx import ctx_bind, get_current_ctx\n\nwith ctx_bind(MyContext(\"id1\")) as ctx:\n    ctx.custom_field = \"value1\"\n\n    assert get_current_ctx(MyContext) is ctx\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fktsstudio%2Fktx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fktsstudio%2Fktx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fktsstudio%2Fktx/lists"}