cbrkit


Namecbrkit JSON
Version 0.9.0 PyPI version JSON
download
home_pagehttps://wi2trier.github.io/cbrkit/
SummaryCustomizable Case-Based Reasoning (CBR) toolkit for Python with a built-in API and CLI.
upload_time2024-04-02 07:46:12
maintainerNone
docs_urlNone
authorMirko Lenz
requires_python<3.13,>=3.11
licenseMIT
keywords cbr case-based reasoning api similarity nlp retrieval cli tool library
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            <!-- markdownlint-disable MD033 MD041 -->
<h2><p align="center">CBRkit</p></h2>
<p align="center">
  <img width="256px" alt="cbrkit logo" src="https://raw.githubusercontent.com/wi2trier/cbrkit/main/assets/logo.png" />
</p>
<p align="center">
  <a href="https://pypi.org/project/cbrkit/">PyPI</a> |
  <a href="https://wi2trier.github.io/cbrkit/">Docs</a> |
  <a href="https://github.com/wi2trier/cbrkit/tree/main/tests/test_retrieve.py">Example</a>
</p>
<p align="center">
  Customizable Case-Based Reasoning (CBR) toolkit for Python with a built-in API and CLI.
</p>

---

# CBRkit

CBRkit is a customizable and modular toolkit for Case-Based Reasoning (CBR) in Python.
It provides a set of tools for loading cases and queries, defining similarity measures, and retrieving cases based on a query.
The toolkit is designed to be flexible and extensible, allowing you to define custom similarity measures or use built-in ones.
Retrieval pipelines are declared by composing these metrics, and the toolkit provides utility functions for applying them on a casebase.
Additionally, it offers ready-to-use API and CLI interfaces for easy integration into your projects.
The library is fully typed, enabling autocompletion and type checking in modern IDEs like VSCode and PyCharm.

To get started, we provide a [demo project](https://github.com/wi2trier/cbrkit-demo) which contains a casebase and a predefined retriever.
Further examples can be found in our [tests](./tests/test_retrieve.py) and [documentation](https://wi2trier.github.io/cbrkit/).
The following modules are part of CBRkit:

- `cbrkit.loaders`: Functions for loading cases and queries.
- `cbrkit.sim`: Similarity generator functions for common data types like strings and numbers.
- `cbrkit.retrieval`: Functions for defining and applying retrieval pipelines.
- `cbrkit.typing`: Generic type definitions for defining custom functions.

## Installation

The library is available on [PyPI](https://pypi.org/project/cbrkit/), so you can install it with `pip`:

```shell
pip install cbrkit
```

It comes with several optional dependencies for certain tasks like NLP which can be installed with:

```shell
pip install cbrkit[EXTRA_NAME,...]
```

where `EXTRA_NAME` is one of the following:

- `nlp`: Standalone NLP tools `levenshtein`, `nltk`, `openai`, and `spacy`
- `transformers`: Advanced NLP tools based on `pytorch` and `transformers`
- `cli`: Command Line Interface (CLI)
- `api`: REST API Server
- `all`: All of the above

## Loading Cases

The first step is to load cases and queries.
We provide predefined functions for the most common formats like CSV, JSON, and XML.
Additionally, CBRkit also integrates with `pandas` for loading data frames.
The following example shows how to load cases and queries from a CSV file using `pandas`:

```python
import pandas as pd
import cbrkit

df = pd.read_csv("path/to/cases.csv")
casebase = cbrkit.loaders.dataframe(df)
```

When dealing with formats like JSON, the files can be loaded directly:

```python
casebase = cbrkit.loaders.json("path/to/cases.json")
```

## Defining Queries

CBRkit expects the type of the queries to match the type of the cases.
You may define a single query directly in Python as follows

```python
# for pandas
query = pd.Series({"name": "John", "age": 25})
# for json
query = {"name": "John", "age": 25}
```

If you have a collection of queries, you can load them using the same loader functions as for the cases.

```python
 # for pandas
queries = cbrkit.loaders.dataframe(pd.read_csv("path/to/queries.csv"))
# for json
queries = cbrkit.loaders.json("path/to/queries.json")
```

In case your query collection only contains a single entry, you can use the `singleton` function to extract it.

```python
query = cbrkit.helpers.singleton(queries)
```

## Similarity Measures and Aggregation

The next step is to define similarity measures for the cases and queries.
It is possible to define custom measures, use built-in ones, or combine both.

### Custom Similarity Measures

In CBRkit, a similarity measure is defined as a function that takes two arguments (a case and a query) and returns a similarity score: `sim = f(x, y)`.
It also supports pipeline-based similarity measures that are popular in NLP where a list of tuples is passed to the similarity measure: `sims = f([(x1, y1), (x2, y2), ...])`.
This generic approach allows you to define custom similarity measures for your specific use case.
For instance, the following function not only checks for strict equality, but also for partial matches (e.g., `x = "blue"` and `y = "light blue"`):

```python
def color_similarity(x: str, y: str) -> float:
    if x == y:
        return 1.0
    elif x in y or y in x:
        return 0.5

    return 0.0
```

**Please note:** CBRkit inspects the signature of custom similarity functions to perform some checks.
You need to make sure that the two parameters are named `x` and `y`, otherwise CBRkit will throw an error.

### Built-in Similarity Measures

CBRkit also contains a selection of built-in similarity measures for the most common data types in the module `cbrkit.sim`.
They are provided through **generator functions** that allow you to customize the behavior of the built-in measures.
For example, an spacy-based embedding similarity measure can be obtained as follows:

```python
semantic_similarity = cbrkit.sim.strings.spacy(model_name="en_core_web_lg")
```

**Please note:** Calling the function `cbrkit.sim.strings.spacy` returns a similarity function itself that has the same signature as the `color_similarity` function defined above.

An overview of all available similarity measures can be found in the [module documentation](https://wi2trier.github.io/cbrkit/cbrkit/sim.html).

### Global Similarity and Aggregation

When dealing with cases that are not represented through elementary data types like strings, we need to aggregate individual measures to obtain a global similarity score.
We provide a predefined `aggregator` that transforms a list of similarities into a single score.
It can be used with custom and/or built-in measures.

```python
similarities = [0.8, 0.6, 0.9]
aggregator = cbrkit.sim.aggregator(pooling="mean")
global_similarity = aggregator(similarities)
```

For the common use case of attribute-value based data, CBRkit provides a predefined global similarity measure that can be used as follows:

```python
cbrkit.sim.attribute_value(
    attributes={
        "price": cbrkit.sim.numbers.linear(),
        "color": color_similarity # custom measure
        ...
    },
    aggregator=cbrkit.sim.aggregator(pooling="mean"),
)
```

The `attribute_value` function lets you define measures for each attribute of the cases/queries as well as the aggregation function.
It also allows to use custom measures like the `color_similarity` function defined above.

**Please note:** The custom measure is not executed (i.e., there are **no** parenthesis at the end), but instead passed as a reference to the `attribute_value` function.

You may even nest similarity functions to create measures for object-oriented cases:

```python
cbrkit.sim.attribute_value(
    attributes={
        "manufacturer": cbrkit.sim.attribute_value(
            attributes={
                "name": cbrkit.sim.strings.spacy(model_name="en_core_web_lg"),
                "country": cbrkit.sim.strings.levenshtein(),
            },
            aggregator=cbrkit.sim.aggregator(pooling="mean"),
        ),
        "color": color_similarity # custom measure
        ...
    },
    aggregator=cbrkit.sim.aggregator(pooling="mean"),
)
```

## Retrieval

The final step is to retrieve cases based on the loaded queries.
The `cbrkit.retrieval` module provides utility functions for this purpose.
You first build a retrieval pipeline by specifying a global similarity function and optionally a limit for the number of retrieved cases.

```python
retriever = cbrkit.retrieval.build(
    cbrkit.sim.attribute_value(...),
    limit=10
)
```

This retriever can then be applied on a casebase to retrieve cases for a given query.

```python
result = cbrkit.retrieval.apply(casebase, query, retriever)
```

Our result has the following attributes:

- `similarities`: A dictionary containing the similarity scores for each case.
- `ranking` A list of case indices sorted by their similarity score.
- `casebase` The casebase containing only the retrieved cases (useful for downstream tasks).

## Combining Multiple Retrieval Pipelines

In some cases, it is useful to combine multiple retrieval pipelines, for example when applying the MAC/FAC pattern where a cheap pre-filter is applied to the whole casebase before a more expensive similarity measure is applied on the remaining cases.
To use this pattern, first create the corresponding retrievers using the builder:

```python
retriever1 = cbrkit.retrieval.build(..., min_similarity=0.5, limit=20)
retriever2 = cbrkit.retrieval.build(..., limit=10)
```

Then apply all of them sequentially by passing them as a list or tuple to the `apply` function:

```python
result = cbrkit.retrieval.apply(casebase, query, (retriever1, retriever2))
```

The result has the following two attributes:

- `final`: Result of the last retriever in the list.
- `intermediates`: A list of results for each retriever in the list.

Both `final` and each entry in `intermediates` have the same attributes as discussed previously.
The returned result also has these entries which are an alias for the corresponding entries in `final` (i.e., `result.ranking == result.final.ranking`).

## REST API and CLI

In order to use the built-in API and CLI, you need to define a retriever in a Python module using the function `cbrkit.retrieval.build()`.
For example, the file `./retriever_module.py` could contain the following code:

```python
import cbrkit

custom_retriever = cbrkit.retrieval.build(
    cbrkit.sim.attribute_value(...),
    limit=10
)
```

Our custom retriever can then be specified for the API/CLI using standard Python module syntax: `retriever_module:custom_retriever`.

### CLI

When installing with the `cli` extra, CBRkit provides a command line interface that can be started with the following command:

```shell
cbrkit retrieve PATH_TO_CASEBASE PATH_TO_QUERY retriever_module:custom_retriever
```

It will then print the retrieval results to the console, so you could pipe the output to a file or another command.

### API

When installing with the `api` extra, CBRkit provides a REST API server that can be started with the following command:

```shell
CBRKIT_RETRIEVER=retriever_module.module:name uvicorn cbrkit.api:app --reload
```

It offers a single endpoint `/retrieve` that accepts POST requests with a JSON body with the following structure:

```json
{
  "casebase": {
    "case1": ...,
    "case2": ...
  },
  "queries": {
    "query1": ...,
    "query2": ...
  }
}
```

The server will return a JSON object containing the retrieval results for each query.


            

Raw data

            {
    "_id": null,
    "home_page": "https://wi2trier.github.io/cbrkit/",
    "name": "cbrkit",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<3.13,>=3.11",
    "maintainer_email": null,
    "keywords": "cbr, case-based reasoning, api, similarity, nlp, retrieval, cli, tool, library",
    "author": "Mirko Lenz",
    "author_email": "mirko@mirkolenz.com",
    "download_url": "https://files.pythonhosted.org/packages/55/ad/9dd96a108131c3384b68b3752ffcec0deb4761273e21ef1970a37a188642/cbrkit-0.9.0.tar.gz",
    "platform": null,
    "description": "<!-- markdownlint-disable MD033 MD041 -->\n<h2><p align=\"center\">CBRkit</p></h2>\n<p align=\"center\">\n  <img width=\"256px\" alt=\"cbrkit logo\" src=\"https://raw.githubusercontent.com/wi2trier/cbrkit/main/assets/logo.png\" />\n</p>\n<p align=\"center\">\n  <a href=\"https://pypi.org/project/cbrkit/\">PyPI</a> |\n  <a href=\"https://wi2trier.github.io/cbrkit/\">Docs</a> |\n  <a href=\"https://github.com/wi2trier/cbrkit/tree/main/tests/test_retrieve.py\">Example</a>\n</p>\n<p align=\"center\">\n  Customizable Case-Based Reasoning (CBR) toolkit for Python with a built-in API and CLI.\n</p>\n\n---\n\n# CBRkit\n\nCBRkit is a customizable and modular toolkit for Case-Based Reasoning (CBR) in Python.\nIt provides a set of tools for loading cases and queries, defining similarity measures, and retrieving cases based on a query.\nThe toolkit is designed to be flexible and extensible, allowing you to define custom similarity measures or use built-in ones.\nRetrieval pipelines are declared by composing these metrics, and the toolkit provides utility functions for applying them on a casebase.\nAdditionally, it offers ready-to-use API and CLI interfaces for easy integration into your projects.\nThe library is fully typed, enabling autocompletion and type checking in modern IDEs like VSCode and PyCharm.\n\nTo get started, we provide a [demo project](https://github.com/wi2trier/cbrkit-demo) which contains a casebase and a predefined retriever.\nFurther examples can be found in our [tests](./tests/test_retrieve.py) and [documentation](https://wi2trier.github.io/cbrkit/).\nThe following modules are part of CBRkit:\n\n- `cbrkit.loaders`: Functions for loading cases and queries.\n- `cbrkit.sim`: Similarity generator functions for common data types like strings and numbers.\n- `cbrkit.retrieval`: Functions for defining and applying retrieval pipelines.\n- `cbrkit.typing`: Generic type definitions for defining custom functions.\n\n## Installation\n\nThe library is available on [PyPI](https://pypi.org/project/cbrkit/), so you can install it with `pip`:\n\n```shell\npip install cbrkit\n```\n\nIt comes with several optional dependencies for certain tasks like NLP which can be installed with:\n\n```shell\npip install cbrkit[EXTRA_NAME,...]\n```\n\nwhere `EXTRA_NAME` is one of the following:\n\n- `nlp`: Standalone NLP tools `levenshtein`, `nltk`, `openai`, and `spacy`\n- `transformers`: Advanced NLP tools based on `pytorch` and `transformers`\n- `cli`: Command Line Interface (CLI)\n- `api`: REST API Server\n- `all`: All of the above\n\n## Loading Cases\n\nThe first step is to load cases and queries.\nWe provide predefined functions for the most common formats like CSV, JSON, and XML.\nAdditionally, CBRkit also integrates with `pandas` for loading data frames.\nThe following example shows how to load cases and queries from a CSV file using `pandas`:\n\n```python\nimport pandas as pd\nimport cbrkit\n\ndf = pd.read_csv(\"path/to/cases.csv\")\ncasebase = cbrkit.loaders.dataframe(df)\n```\n\nWhen dealing with formats like JSON, the files can be loaded directly:\n\n```python\ncasebase = cbrkit.loaders.json(\"path/to/cases.json\")\n```\n\n## Defining Queries\n\nCBRkit expects the type of the queries to match the type of the cases.\nYou may define a single query directly in Python as follows\n\n```python\n# for pandas\nquery = pd.Series({\"name\": \"John\", \"age\": 25})\n# for json\nquery = {\"name\": \"John\", \"age\": 25}\n```\n\nIf you have a collection of queries, you can load them using the same loader functions as for the cases.\n\n```python\n # for pandas\nqueries = cbrkit.loaders.dataframe(pd.read_csv(\"path/to/queries.csv\"))\n# for json\nqueries = cbrkit.loaders.json(\"path/to/queries.json\")\n```\n\nIn case your query collection only contains a single entry, you can use the `singleton` function to extract it.\n\n```python\nquery = cbrkit.helpers.singleton(queries)\n```\n\n## Similarity Measures and Aggregation\n\nThe next step is to define similarity measures for the cases and queries.\nIt is possible to define custom measures, use built-in ones, or combine both.\n\n### Custom Similarity Measures\n\nIn CBRkit, a similarity measure is defined as a function that takes two arguments (a case and a query) and returns a similarity score: `sim = f(x, y)`.\nIt also supports pipeline-based similarity measures that are popular in NLP where a list of tuples is passed to the similarity measure: `sims = f([(x1, y1), (x2, y2), ...])`.\nThis generic approach allows you to define custom similarity measures for your specific use case.\nFor instance, the following function not only checks for strict equality, but also for partial matches (e.g., `x = \"blue\"` and `y = \"light blue\"`):\n\n```python\ndef color_similarity(x: str, y: str) -> float:\n    if x == y:\n        return 1.0\n    elif x in y or y in x:\n        return 0.5\n\n    return 0.0\n```\n\n**Please note:** CBRkit inspects the signature of custom similarity functions to perform some checks.\nYou need to make sure that the two parameters are named `x` and `y`, otherwise CBRkit will throw an error.\n\n### Built-in Similarity Measures\n\nCBRkit also contains a selection of built-in similarity measures for the most common data types in the module `cbrkit.sim`.\nThey are provided through **generator functions** that allow you to customize the behavior of the built-in measures.\nFor example, an spacy-based embedding similarity measure can be obtained as follows:\n\n```python\nsemantic_similarity = cbrkit.sim.strings.spacy(model_name=\"en_core_web_lg\")\n```\n\n**Please note:** Calling the function `cbrkit.sim.strings.spacy` returns a similarity function itself that has the same signature as the `color_similarity` function defined above.\n\nAn overview of all available similarity measures can be found in the [module documentation](https://wi2trier.github.io/cbrkit/cbrkit/sim.html).\n\n### Global Similarity and Aggregation\n\nWhen dealing with cases that are not represented through elementary data types like strings, we need to aggregate individual measures to obtain a global similarity score.\nWe provide a predefined `aggregator` that transforms a list of similarities into a single score.\nIt can be used with custom and/or built-in measures.\n\n```python\nsimilarities = [0.8, 0.6, 0.9]\naggregator = cbrkit.sim.aggregator(pooling=\"mean\")\nglobal_similarity = aggregator(similarities)\n```\n\nFor the common use case of attribute-value based data, CBRkit provides a predefined global similarity measure that can be used as follows:\n\n```python\ncbrkit.sim.attribute_value(\n    attributes={\n        \"price\": cbrkit.sim.numbers.linear(),\n        \"color\": color_similarity # custom measure\n        ...\n    },\n    aggregator=cbrkit.sim.aggregator(pooling=\"mean\"),\n)\n```\n\nThe `attribute_value` function lets you define measures for each attribute of the cases/queries as well as the aggregation function.\nIt also allows to use custom measures like the `color_similarity` function defined above.\n\n**Please note:** The custom measure is not executed (i.e., there are **no** parenthesis at the end), but instead passed as a reference to the `attribute_value` function.\n\nYou may even nest similarity functions to create measures for object-oriented cases:\n\n```python\ncbrkit.sim.attribute_value(\n    attributes={\n        \"manufacturer\": cbrkit.sim.attribute_value(\n            attributes={\n                \"name\": cbrkit.sim.strings.spacy(model_name=\"en_core_web_lg\"),\n                \"country\": cbrkit.sim.strings.levenshtein(),\n            },\n            aggregator=cbrkit.sim.aggregator(pooling=\"mean\"),\n        ),\n        \"color\": color_similarity # custom measure\n        ...\n    },\n    aggregator=cbrkit.sim.aggregator(pooling=\"mean\"),\n)\n```\n\n## Retrieval\n\nThe final step is to retrieve cases based on the loaded queries.\nThe `cbrkit.retrieval` module provides utility functions for this purpose.\nYou first build a retrieval pipeline by specifying a global similarity function and optionally a limit for the number of retrieved cases.\n\n```python\nretriever = cbrkit.retrieval.build(\n    cbrkit.sim.attribute_value(...),\n    limit=10\n)\n```\n\nThis retriever can then be applied on a casebase to retrieve cases for a given query.\n\n```python\nresult = cbrkit.retrieval.apply(casebase, query, retriever)\n```\n\nOur result has the following attributes:\n\n- `similarities`: A dictionary containing the similarity scores for each case.\n- `ranking` A list of case indices sorted by their similarity score.\n- `casebase` The casebase containing only the retrieved cases (useful for downstream tasks).\n\n## Combining Multiple Retrieval Pipelines\n\nIn some cases, it is useful to combine multiple retrieval pipelines, for example when applying the MAC/FAC pattern where a cheap pre-filter is applied to the whole casebase before a more expensive similarity measure is applied on the remaining cases.\nTo use this pattern, first create the corresponding retrievers using the builder:\n\n```python\nretriever1 = cbrkit.retrieval.build(..., min_similarity=0.5, limit=20)\nretriever2 = cbrkit.retrieval.build(..., limit=10)\n```\n\nThen apply all of them sequentially by passing them as a list or tuple to the `apply` function:\n\n```python\nresult = cbrkit.retrieval.apply(casebase, query, (retriever1, retriever2))\n```\n\nThe result has the following two attributes:\n\n- `final`: Result of the last retriever in the list.\n- `intermediates`: A list of results for each retriever in the list.\n\nBoth `final` and each entry in `intermediates` have the same attributes as discussed previously.\nThe returned result also has these entries which are an alias for the corresponding entries in `final` (i.e., `result.ranking == result.final.ranking`).\n\n## REST API and CLI\n\nIn order to use the built-in API and CLI, you need to define a retriever in a Python module using the function `cbrkit.retrieval.build()`.\nFor example, the file `./retriever_module.py` could contain the following code:\n\n```python\nimport cbrkit\n\ncustom_retriever = cbrkit.retrieval.build(\n    cbrkit.sim.attribute_value(...),\n    limit=10\n)\n```\n\nOur custom retriever can then be specified for the API/CLI using standard Python module syntax: `retriever_module:custom_retriever`.\n\n### CLI\n\nWhen installing with the `cli` extra, CBRkit provides a command line interface that can be started with the following command:\n\n```shell\ncbrkit retrieve PATH_TO_CASEBASE PATH_TO_QUERY retriever_module:custom_retriever\n```\n\nIt will then print the retrieval results to the console, so you could pipe the output to a file or another command.\n\n### API\n\nWhen installing with the `api` extra, CBRkit provides a REST API server that can be started with the following command:\n\n```shell\nCBRKIT_RETRIEVER=retriever_module.module:name uvicorn cbrkit.api:app --reload\n```\n\nIt offers a single endpoint `/retrieve` that accepts POST requests with a JSON body with the following structure:\n\n```json\n{\n  \"casebase\": {\n    \"case1\": ...,\n    \"case2\": ...\n  },\n  \"queries\": {\n    \"query1\": ...,\n    \"query2\": ...\n  }\n}\n```\n\nThe server will return a JSON object containing the retrieval results for each query.\n\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Customizable Case-Based Reasoning (CBR) toolkit for Python with a built-in API and CLI.",
    "version": "0.9.0",
    "project_urls": {
        "Homepage": "https://wi2trier.github.io/cbrkit/",
        "Repository": "https://github.com/wi2trier/cbrkit"
    },
    "split_keywords": [
        "cbr",
        " case-based reasoning",
        " api",
        " similarity",
        " nlp",
        " retrieval",
        " cli",
        " tool",
        " library"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "6bf9525c5272acad84d0a6d22c5d2bdbd88f097694faa27cd2b6cb5609f12db7",
                "md5": "5b75344fa8878b304b77041595f9edd5",
                "sha256": "8be4e3c98bb8c61c1501217fbf364d286f60e2afeff073d8f0cfb1a34f24112e"
            },
            "downloads": -1,
            "filename": "cbrkit-0.9.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "5b75344fa8878b304b77041595f9edd5",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<3.13,>=3.11",
            "size": 29714,
            "upload_time": "2024-04-02T07:46:11",
            "upload_time_iso_8601": "2024-04-02T07:46:11.031058Z",
            "url": "https://files.pythonhosted.org/packages/6b/f9/525c5272acad84d0a6d22c5d2bdbd88f097694faa27cd2b6cb5609f12db7/cbrkit-0.9.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "55ad9dd96a108131c3384b68b3752ffcec0deb4761273e21ef1970a37a188642",
                "md5": "f15827b3c308349cb0edff0f64368812",
                "sha256": "7afe2fb560bdebec34ab6a2676c452a63781e155d6131aceff47f0c86390a631"
            },
            "downloads": -1,
            "filename": "cbrkit-0.9.0.tar.gz",
            "has_sig": false,
            "md5_digest": "f15827b3c308349cb0edff0f64368812",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<3.13,>=3.11",
            "size": 27168,
            "upload_time": "2024-04-02T07:46:12",
            "upload_time_iso_8601": "2024-04-02T07:46:12.826033Z",
            "url": "https://files.pythonhosted.org/packages/55/ad/9dd96a108131c3384b68b3752ffcec0deb4761273e21ef1970a37a188642/cbrkit-0.9.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-02 07:46:12",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "wi2trier",
    "github_project": "cbrkit",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "cbrkit"
}
        
Elapsed time: 0.22299s