ldict


Nameldict JSON
Version 3.220128.3 PyPI version JSON
download
home_page
SummaryLazy dict
upload_time2022-12-15 13:23:31
maintainer
docs_urlNone
authordavips
requires_python>=3.8,<4.0
licenseGPL
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ![test](https://github.com/davips/ldict/workflows/test/badge.svg)
[![codecov](https://codecov.io/gh/davips/ldict/branch/main/graph/badge.svg)](https://codecov.io/gh/davips/ldict)
<a href="https://pypi.org/project/ldict">
<img src="https://img.shields.io/pypi/v/ldict.svg?label=release&color=blue&style=flat-square" alt="pypi">
</a>
![Python version](https://img.shields.io/badge/python-3.8%20%7C%203.9-blue.svg)
[![license: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)

<!--- [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5501845.svg)](https://doi.org/10.5281/zenodo.5501845) --->
[![arXiv](https://img.shields.io/badge/arXiv-2109.06028-b31b1b.svg?style=flat-square)](https://arxiv.org/abs/2109.06028)
[![API documentation](https://img.shields.io/badge/doc-API%20%28auto%29-a0a0a0.svg)](https://davips.github.io/ldict)

# ldict

A lazy `dict`.

[Latest release](https://pypi.org/project/ldict) |
[Current code](https://github.com/davips/ldict) |
[API documentation](https://davips.github.io/ldict)

## See also

* laziness+identity+persistence ([idict](https://pypi.org/project/idict))

## Overview

A `ldict` is a `dict` with `str` keys.

**Simple usage example**
<details>
<p>

```python3
from ldict import ldict

a = ldict(x=3)
print(a)
"""
{
    "x": 3
}
"""
```

```python3

b = ldict(y=5)
print(b)
"""
{
    "y": 5
}
"""
```

```python3

print(a >> b)
"""
{
    "x": 3,
    "y": 5
}
"""
```


</p>
</details>

We consider that every value is generated by a process, starting from an `empty` ldict. The process is a sequence of
transformation steps done through the operator `>>`, which symbolizes a data flow. There are two types of steps:

* **value insertion** - represented by dict-like objects
* **function application** - represented by ordinary python functions

A `ldict` is completely defined by its key-value pairs so that
it can be converted from/to a built-in dict.

Creating a ldict is not different from creating an ordinary dict. Optionally it can be created through the `>>` operator
used after `empty`:
![img.png](https://raw.githubusercontent.com/davips/ldict/main/examples/img.png)

Function application is done in the same way. The parameter names define the input fields, while the keys in the
returned dict define the output fields:
![img_1.png](https://raw.githubusercontent.com/davips/ldict/main/examples/img_1.png)

Similarly, for anonymous functions:
![img_5.png](https://raw.githubusercontent.com/davips/ldict/main/examples/img_5.png)

Finally, the result is only evaluated at request:
![img_6.png](https://raw.githubusercontent.com/davips/ldict/main/examples/img_6.png)


## Installation
### ...as a standalone lib
```bash
# Set up a virtualenv. 
python3 -m venv venv
source venv/bin/activate

# Install from PyPI...
pip install --upgrade pip
pip install -U ldict
pip install -U ldict[full]  # use this for extra functionality (recommended)

# ...or, install from updated source code.
pip install git+https://github.com/davips/ldict
```

### ...from source
```bash
git clone https://github.com/davips/ldict
cd ldict
poetry install
```

## Examples
**Merging two ldicts**
<details>
<p>

```python3
from ldict import ldict

a = ldict(x=3)
print(a)
"""
{
    "x": 3
}
"""
```

```python3

b = ldict(y=5)
print(b)
"""
{
    "y": 5
}
"""
```

```python3

print(a >> b)
"""
{
    "x": 3,
    "y": 5
}
"""
```


</p>
</details>

**Lazily applying functions to ldict**
<details>
<p>

```python3
from ldict import ldict

a = ldict(x=3)
print(a)
"""
{
    "x": 3
}
"""
```

```python3

a = a >> ldict(y=5) >> {"z": 7} >> (lambda x, y, z: {"r": x ** y // z})
print(a)
"""
{
    "x": 3,
    "y": 5,
    "z": 7,
    "r": "→(x y z)"
}
"""
```

```python3

print(a.r)
"""
34
"""
```

```python3

print(a)
"""
{
    "x": 3,
    "y": 5,
    "z": 7,
    "r": 34
}
"""
```


</p>
</details>

**Parameterized functions and sampling**
<details>
<p>

```python3
from random import Random

from ldict import empty, let


# A function provide input fields and, optionally, parameters.
# For instance:
# 'a' is sampled from an arithmetic progression
# 'b' is sampled from a geometric progression
# Here, the syntax for default parameter values is borrowed with a new meaning.
def fun(x, y, a=[-100, -99, -98, ..., 100], b=[0.0001, 0.001, 0.01, ..., 100000000]):
    return {"z": a * x + b * y}


def simplefun(x, y):
    return {"z": x * y}


# Creating an empty ldict. Alternatively: d = ldict().
d = empty >> {}
print(d)
"""
{}
"""
```

```python3

# Putting some values. Alternatively: d = ldict(x=5, y=7).
d["x"] = 5
d["y"] = 7
print(d)
"""
{
    "x": 5,
    "y": 7
}
"""
```

```python3

# Parameter values are uniformly sampled.
d1 = d >> simplefun
print(d1)
print(d1.z)
"""
{
    "x": 5,
    "y": 7,
    "z": "→(x y)"
}
35
"""
```

```python3

d2 = d >> simplefun
print(d2)
print(d2.z)
"""
{
    "x": 5,
    "y": 7,
    "z": "→(x y)"
}
35
"""
```

```python3

# Parameter values can also be manually set.
e = d >> let(fun, a=5, b=10)
print(e.z)
"""
95
"""
```

```python3

# Not all parameters need to be set.
e = d >> Random() >> let(fun, a=5)
print("e =", e.z)
"""
e = 25.007
"""
```

```python3

# Each run will be a different sample for the missing parameters.
e = e >> Random() >> let(fun, a=5)
print("e =", e.z)
"""
e = 25.007
"""
```

```python3

# We can define the initial state of the random sampler.
# It will be in effect from its location place onwards in the expression.
e = d >> Random(0) >> let(fun, a=5)
print(e.z)
"""
725.0
"""
```

```python3

# All runs will yield the same result,
# if starting from the same random number generator seed.
e = e >> Random(0) >> let(fun, a=[555, 777])
print("Let 'a' be a list:", e.z)
"""
Let 'a' be a list: 700003885.0
"""
```

```python3

# Reproducible different runs are achievable by using a single random number generator.
e = e >> Random(0) >> let(fun, a=[5, 25, 125, ..., 10000])
print("Let 'a' be a geometric progression:", e.z)
"""
Let 'a' be a geometric progression: 700003125.0
"""
```

```python3
rnd = Random(0)
e = d >> rnd >> let(fun, a=5)
print(e.z)
e = d >> rnd >> let(fun, a=5)  # Alternative syntax.
print(e.z)
"""
725.0
700000025.0
"""
```

```python3

# Output fields can be defined dynamically through parameter values.
# Input fields can be defined dynamically through kwargs.
copy = lambda source=None, target=None, **kwargs: {target: kwargs[source]}
d = empty >> {"x": 5}
d >>= let(copy, source="x", target="y")
print(d)
d.evaluate()
print(d)

"""
{
    "x": 5,
    "y": "→(source target x)"
}
{
    "x": 5,
    "y": 5
}
"""
```


</p>
</details>

**Composition of sets of functions**
<details>
<p>

```python3
from random import Random

from ldict import empty


# A multistep process can be defined without applying its functions


def g(x, y, a=[1, 2, 3, ..., 10], b=[0.00001, 0.0001, 0.001, ..., 100000]):
    return {"z": a * x + b * y}


def h(z, c=[1, 2, 3]):
    return {"z": c * z}


# In the ldict framework 'data is function',
# so the alias ø represents the 'empty data object' and the 'reflexive function' at the same time.
# In other words: 'inserting nothing' has the same effect as 'doing nothing'.
fun = empty >> g >> h  # empty enable the cartesian product of the subsequent sets of functions within the expression.
print(fun)
"""
«λ{} × λ»
"""
```

```python3

# An unnapplied function has its free parameters unsampled.
# A compostition of functions results in an ordered set (Cartesian product of sets).
# It is a set because the parameter values of the functions are still undefined.
d = {"x": 5, "y": 7} >> (Random(0) >> fun)
print(d)
"""
{
    "x": 5,
    "y": 7,
    "z": "→(c z→(a b x y))"
}
"""
```

```python3

print(d.z)
"""
105.0
"""
```

```python3

d = {"x": 5, "y": 7} >> (Random(0) >> fun)
print(d.z)
"""
105.0
"""
```

```python3

# Reproducible different runs by passing a stateful random number generator.
rnd = Random(0)
e = d >> rnd >> fun
print(e.z)
"""
105.0
"""
```

```python3

e = d >> rnd >> fun
print(e.z)
"""
14050.0
"""
```

```python3

# Repeating the same results.
rnd = Random(0)
e = d >> rnd >> fun
print(e.z)
"""
105.0
"""
```

```python3

e = d >> rnd >> fun
print(e.z)
"""
14050.0
"""
```


</p>
</details>

<!--- ## Persistence
Extra dependencies can be installed to support saving data to disk or to a server in the network. 

**[still an ongoing work...]**

`poetry install -E full`
--->

## Concept

A ldict is like a common Python dict, with extra functionality and lazy. It is a mapping between string keys, called
fields, and any serializable (pickable protocol=5) object.

## Grants
This work was partially supported by Fapesp under supervision of
Prof. André C. P. L. F. de Carvalho at CEPID-CeMEAI (Grants 2013/07375-0 – 2019/01735-0).

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "ldict",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8,<4.0",
    "maintainer_email": "",
    "keywords": "",
    "author": "davips",
    "author_email": "dpsabc@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/12/47/b2ab81a45cd41bf534bd6d0a6068a1c6b938db49554197ebcbe381baf777/ldict-3.220128.3.tar.gz",
    "platform": null,
    "description": "![test](https://github.com/davips/ldict/workflows/test/badge.svg)\n[![codecov](https://codecov.io/gh/davips/ldict/branch/main/graph/badge.svg)](https://codecov.io/gh/davips/ldict)\n<a href=\"https://pypi.org/project/ldict\">\n<img src=\"https://img.shields.io/pypi/v/ldict.svg?label=release&color=blue&style=flat-square\" alt=\"pypi\">\n</a>\n![Python version](https://img.shields.io/badge/python-3.8%20%7C%203.9-blue.svg)\n[![license: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)\n\n<!--- [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5501845.svg)](https://doi.org/10.5281/zenodo.5501845) --->\n[![arXiv](https://img.shields.io/badge/arXiv-2109.06028-b31b1b.svg?style=flat-square)](https://arxiv.org/abs/2109.06028)\n[![API documentation](https://img.shields.io/badge/doc-API%20%28auto%29-a0a0a0.svg)](https://davips.github.io/ldict)\n\n# ldict\n\nA lazy `dict`.\n\n[Latest release](https://pypi.org/project/ldict) |\n[Current code](https://github.com/davips/ldict) |\n[API documentation](https://davips.github.io/ldict)\n\n## See also\n\n* laziness+identity+persistence ([idict](https://pypi.org/project/idict))\n\n## Overview\n\nA `ldict` is a `dict` with `str` keys.\n\n**Simple usage example**\n<details>\n<p>\n\n```python3\nfrom ldict import ldict\n\na = ldict(x=3)\nprint(a)\n\"\"\"\n{\n    \"x\": 3\n}\n\"\"\"\n```\n\n```python3\n\nb = ldict(y=5)\nprint(b)\n\"\"\"\n{\n    \"y\": 5\n}\n\"\"\"\n```\n\n```python3\n\nprint(a >> b)\n\"\"\"\n{\n    \"x\": 3,\n    \"y\": 5\n}\n\"\"\"\n```\n\n\n</p>\n</details>\n\nWe consider that every value is generated by a process, starting from an `empty` ldict. The process is a sequence of\ntransformation steps done through the operator `>>`, which symbolizes a data flow. There are two types of steps:\n\n* **value insertion** - represented by dict-like objects\n* **function application** - represented by ordinary python functions\n\nA `ldict` is completely defined by its key-value pairs so that\nit can be converted from/to a built-in dict.\n\nCreating a ldict is not different from creating an ordinary dict. Optionally it can be created through the `>>` operator\nused after `empty`:\n![img.png](https://raw.githubusercontent.com/davips/ldict/main/examples/img.png)\n\nFunction application is done in the same way. The parameter names define the input fields, while the keys in the\nreturned dict define the output fields:\n![img_1.png](https://raw.githubusercontent.com/davips/ldict/main/examples/img_1.png)\n\nSimilarly, for anonymous functions:\n![img_5.png](https://raw.githubusercontent.com/davips/ldict/main/examples/img_5.png)\n\nFinally, the result is only evaluated at request:\n![img_6.png](https://raw.githubusercontent.com/davips/ldict/main/examples/img_6.png)\n\n\n## Installation\n### ...as a standalone lib\n```bash\n# Set up a virtualenv. \npython3 -m venv venv\nsource venv/bin/activate\n\n# Install from PyPI...\npip install --upgrade pip\npip install -U ldict\npip install -U ldict[full]  # use this for extra functionality (recommended)\n\n# ...or, install from updated source code.\npip install git+https://github.com/davips/ldict\n```\n\n### ...from source\n```bash\ngit clone https://github.com/davips/ldict\ncd ldict\npoetry install\n```\n\n## Examples\n**Merging two ldicts**\n<details>\n<p>\n\n```python3\nfrom ldict import ldict\n\na = ldict(x=3)\nprint(a)\n\"\"\"\n{\n    \"x\": 3\n}\n\"\"\"\n```\n\n```python3\n\nb = ldict(y=5)\nprint(b)\n\"\"\"\n{\n    \"y\": 5\n}\n\"\"\"\n```\n\n```python3\n\nprint(a >> b)\n\"\"\"\n{\n    \"x\": 3,\n    \"y\": 5\n}\n\"\"\"\n```\n\n\n</p>\n</details>\n\n**Lazily applying functions to ldict**\n<details>\n<p>\n\n```python3\nfrom ldict import ldict\n\na = ldict(x=3)\nprint(a)\n\"\"\"\n{\n    \"x\": 3\n}\n\"\"\"\n```\n\n```python3\n\na = a >> ldict(y=5) >> {\"z\": 7} >> (lambda x, y, z: {\"r\": x ** y // z})\nprint(a)\n\"\"\"\n{\n    \"x\": 3,\n    \"y\": 5,\n    \"z\": 7,\n    \"r\": \"\u2192(x y z)\"\n}\n\"\"\"\n```\n\n```python3\n\nprint(a.r)\n\"\"\"\n34\n\"\"\"\n```\n\n```python3\n\nprint(a)\n\"\"\"\n{\n    \"x\": 3,\n    \"y\": 5,\n    \"z\": 7,\n    \"r\": 34\n}\n\"\"\"\n```\n\n\n</p>\n</details>\n\n**Parameterized functions and sampling**\n<details>\n<p>\n\n```python3\nfrom random import Random\n\nfrom ldict import empty, let\n\n\n# A function provide input fields and, optionally, parameters.\n# For instance:\n# 'a' is sampled from an arithmetic progression\n# 'b' is sampled from a geometric progression\n# Here, the syntax for default parameter values is borrowed with a new meaning.\ndef fun(x, y, a=[-100, -99, -98, ..., 100], b=[0.0001, 0.001, 0.01, ..., 100000000]):\n    return {\"z\": a * x + b * y}\n\n\ndef simplefun(x, y):\n    return {\"z\": x * y}\n\n\n# Creating an empty ldict. Alternatively: d = ldict().\nd = empty >> {}\nprint(d)\n\"\"\"\n{}\n\"\"\"\n```\n\n```python3\n\n# Putting some values. Alternatively: d = ldict(x=5, y=7).\nd[\"x\"] = 5\nd[\"y\"] = 7\nprint(d)\n\"\"\"\n{\n    \"x\": 5,\n    \"y\": 7\n}\n\"\"\"\n```\n\n```python3\n\n# Parameter values are uniformly sampled.\nd1 = d >> simplefun\nprint(d1)\nprint(d1.z)\n\"\"\"\n{\n    \"x\": 5,\n    \"y\": 7,\n    \"z\": \"\u2192(x y)\"\n}\n35\n\"\"\"\n```\n\n```python3\n\nd2 = d >> simplefun\nprint(d2)\nprint(d2.z)\n\"\"\"\n{\n    \"x\": 5,\n    \"y\": 7,\n    \"z\": \"\u2192(x y)\"\n}\n35\n\"\"\"\n```\n\n```python3\n\n# Parameter values can also be manually set.\ne = d >> let(fun, a=5, b=10)\nprint(e.z)\n\"\"\"\n95\n\"\"\"\n```\n\n```python3\n\n# Not all parameters need to be set.\ne = d >> Random() >> let(fun, a=5)\nprint(\"e =\", e.z)\n\"\"\"\ne = 25.007\n\"\"\"\n```\n\n```python3\n\n# Each run will be a different sample for the missing parameters.\ne = e >> Random() >> let(fun, a=5)\nprint(\"e =\", e.z)\n\"\"\"\ne = 25.007\n\"\"\"\n```\n\n```python3\n\n# We can define the initial state of the random sampler.\n# It will be in effect from its location place onwards in the expression.\ne = d >> Random(0) >> let(fun, a=5)\nprint(e.z)\n\"\"\"\n725.0\n\"\"\"\n```\n\n```python3\n\n# All runs will yield the same result,\n# if starting from the same random number generator seed.\ne = e >> Random(0) >> let(fun, a=[555, 777])\nprint(\"Let 'a' be a list:\", e.z)\n\"\"\"\nLet 'a' be a list: 700003885.0\n\"\"\"\n```\n\n```python3\n\n# Reproducible different runs are achievable by using a single random number generator.\ne = e >> Random(0) >> let(fun, a=[5, 25, 125, ..., 10000])\nprint(\"Let 'a' be a geometric progression:\", e.z)\n\"\"\"\nLet 'a' be a geometric progression: 700003125.0\n\"\"\"\n```\n\n```python3\nrnd = Random(0)\ne = d >> rnd >> let(fun, a=5)\nprint(e.z)\ne = d >> rnd >> let(fun, a=5)  # Alternative syntax.\nprint(e.z)\n\"\"\"\n725.0\n700000025.0\n\"\"\"\n```\n\n```python3\n\n# Output fields can be defined dynamically through parameter values.\n# Input fields can be defined dynamically through kwargs.\ncopy = lambda source=None, target=None, **kwargs: {target: kwargs[source]}\nd = empty >> {\"x\": 5}\nd >>= let(copy, source=\"x\", target=\"y\")\nprint(d)\nd.evaluate()\nprint(d)\n\n\"\"\"\n{\n    \"x\": 5,\n    \"y\": \"\u2192(source target x)\"\n}\n{\n    \"x\": 5,\n    \"y\": 5\n}\n\"\"\"\n```\n\n\n</p>\n</details>\n\n**Composition of sets of functions**\n<details>\n<p>\n\n```python3\nfrom random import Random\n\nfrom ldict import empty\n\n\n# A multistep process can be defined without applying its functions\n\n\ndef g(x, y, a=[1, 2, 3, ..., 10], b=[0.00001, 0.0001, 0.001, ..., 100000]):\n    return {\"z\": a * x + b * y}\n\n\ndef h(z, c=[1, 2, 3]):\n    return {\"z\": c * z}\n\n\n# In the ldict framework 'data is function',\n# so the alias \u00f8 represents the 'empty data object' and the 'reflexive function' at the same time.\n# In other words: 'inserting nothing' has the same effect as 'doing nothing'.\nfun = empty >> g >> h  # empty enable the cartesian product of the subsequent sets of functions within the expression.\nprint(fun)\n\"\"\"\n\u00ab\u03bb{} \u00d7 \u03bb\u00bb\n\"\"\"\n```\n\n```python3\n\n# An unnapplied function has its free parameters unsampled.\n# A compostition of functions results in an ordered set (Cartesian product of sets).\n# It is a set because the parameter values of the functions are still undefined.\nd = {\"x\": 5, \"y\": 7} >> (Random(0) >> fun)\nprint(d)\n\"\"\"\n{\n    \"x\": 5,\n    \"y\": 7,\n    \"z\": \"\u2192(c z\u2192(a b x y))\"\n}\n\"\"\"\n```\n\n```python3\n\nprint(d.z)\n\"\"\"\n105.0\n\"\"\"\n```\n\n```python3\n\nd = {\"x\": 5, \"y\": 7} >> (Random(0) >> fun)\nprint(d.z)\n\"\"\"\n105.0\n\"\"\"\n```\n\n```python3\n\n# Reproducible different runs by passing a stateful random number generator.\nrnd = Random(0)\ne = d >> rnd >> fun\nprint(e.z)\n\"\"\"\n105.0\n\"\"\"\n```\n\n```python3\n\ne = d >> rnd >> fun\nprint(e.z)\n\"\"\"\n14050.0\n\"\"\"\n```\n\n```python3\n\n# Repeating the same results.\nrnd = Random(0)\ne = d >> rnd >> fun\nprint(e.z)\n\"\"\"\n105.0\n\"\"\"\n```\n\n```python3\n\ne = d >> rnd >> fun\nprint(e.z)\n\"\"\"\n14050.0\n\"\"\"\n```\n\n\n</p>\n</details>\n\n<!--- ## Persistence\nExtra dependencies can be installed to support saving data to disk or to a server in the network. \n\n**[still an ongoing work...]**\n\n`poetry install -E full`\n--->\n\n## Concept\n\nA ldict is like a common Python dict, with extra functionality and lazy. It is a mapping between string keys, called\nfields, and any serializable (pickable protocol=5) object.\n\n## Grants\nThis work was partially supported by Fapesp under supervision of\nProf. Andr\u00e9 C. P. L. F. de Carvalho at CEPID-CeMEAI (Grants 2013/07375-0 \u2013 2019/01735-0).\n",
    "bugtrack_url": null,
    "license": "GPL",
    "summary": "Lazy dict",
    "version": "3.220128.3",
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "md5": "b338d6bfbfe04847de258d095b673949",
                "sha256": "a08a1ab573991090720f721950f6313d4958e4209acdaf20230c79fd3b6097bf"
            },
            "downloads": -1,
            "filename": "ldict-3.220128.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "b338d6bfbfe04847de258d095b673949",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8,<4.0",
            "size": 29299,
            "upload_time": "2022-12-15T13:23:28",
            "upload_time_iso_8601": "2022-12-15T13:23:28.956398Z",
            "url": "https://files.pythonhosted.org/packages/27/32/66382169df67baa2a813767f5aed3dd03bf4925e19e27d2980177e31d05f/ldict-3.220128.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "md5": "62387e8510a3fd23ca4d3d27a5c40dc3",
                "sha256": "1e2e3b3d6884c6617496b7202b46dbd2cbda25889c1d555e61709806a939cf7f"
            },
            "downloads": -1,
            "filename": "ldict-3.220128.3.tar.gz",
            "has_sig": false,
            "md5_digest": "62387e8510a3fd23ca4d3d27a5c40dc3",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8,<4.0",
            "size": 20786,
            "upload_time": "2022-12-15T13:23:31",
            "upload_time_iso_8601": "2022-12-15T13:23:31.218197Z",
            "url": "https://files.pythonhosted.org/packages/12/47/b2ab81a45cd41bf534bd6d0a6068a1c6b938db49554197ebcbe381baf777/ldict-3.220128.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2022-12-15 13:23:31",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "lcname": "ldict"
}
        
Elapsed time: 0.01946s