ducer


Nameducer JSON
Version 1.0.3 PyPI version JSON
download
home_pageNone
SummaryFast and compact maps and sets with Billions of keys, based on finite-state-transducers.
upload_time2024-08-10 11:33:51
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseCopyright (c) 2024 Joachim Folz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords big data compression dataset dict finite-state-machines finite-state-transducers frozen fst map serialization set streaming
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Ducer documentation

This package provides fast and compact read-only
[maps](https://ducer.readthedocs.io/stable/api_reference.html#Map)
and
[sets](https://ducer.readthedocs.io/stable/api_reference.html#Set)
that scale up to Billions of keys, while being light on resources
with a streamable file format.
Complex search patterns that would be infeasible with Python's builtin
[`dict`](https://docs.python.org/3/library/stdtypes.html#dict)
and [`set`](https://docs.python.org/3/library/stdtypes.html#set)
are not just possible, but very efficient.
All of these amazing things are achieved with finite-state-transducers
provided by the excellent Rust crate
[fst](https://github.com/BurntSushi/fst) by Andrew Gallant.



## Performance

Ducer maps and sets can be built and queried at millions of keys per second.
Consider the following example:

```Python
import ducer
n = 1_000_000_000
items = ((b"%09d" % i, n-i) for i in range(n))
data = ducer.Map.build(":memory:", items)
m = Map(data)
assert m[b"900000000"] == 100_000_000
```

In our example, most of the time is spent in Python creating item tuples.
Regardless, building happens at almost 4 Million items per second
on my humble laptop.
Retrieving individual keys is similarly speedy, and simply iterating
over all items is twice as fast at 8 Million items per second.

This toy example is almost a best case for FSTs, so the resulting
output is just 464 bytes.
A real-world example with 1.1 Billion keys, where the key-value pairs
occupy 21 GiB without any kind of searchability (stored in already quite
compact [msgpack](https://github.com/msgpack/msgpack-python) format),
results in a 4.6 GiB file.
Building and retrieval are naturally a bit slower,
but still within the 2-3 Million items per second range.



## Limitations

Performance is rarely free,
so there are some limitations you should consider before proceeding:

* Keys **must** be **[`bytes`](https://docs.python.org/3/library/stdtypes.html#bytes)**
* Keys **must** be inserted in lexicographical order
* Map values **must** be non-negative integers less than 2^64
* Once built, maps and sets **cannot** be altered



## Installation

Most users should be able to simply do:

```
pip install ducer
```

To build from source you will need a recent Rust toolchain.
Use your preferred method of installation, or follow the official
[instructions](https://www.rust-lang.org/tools/install) to install Rust.
Then run the following at the toplevel of the repository:

```
pip install .
```



## Building

Above, we already showed that
[`Map.build`](https://ducer.readthedocs.io/stable/api_reference.html#Map.build)
can build maps in memory by passing
`":memory:"` as path:

```Python
data = ducer.Map.build(":memory:", items)
```

If you pass any other path
(either [`str`](https://docs.python.org/3/library/stdtypes.html#str)
or [`Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path);
the parent directory must exist),
your map will be written directly to that file:

```Python
ducer.Map.build("path/to/my.map", items)
```

Building a map like this uses virtually no extra memory.



## Opening

One key advantage of ducer maps is streamability.
Unlike the builtin [`dict`](https://docs.python.org/3/library/stdtypes.html#dict),
a [`Map`](https://ducer.readthedocs.io/stable/api_reference.html#Map)
does not have to reside entirely in memory.
You can, e.g., use the builtin
[`mmap`](https://docs.python.org/3/library/mmap.html#mmap.mmap) to stream map data:

```Python
with open("path/to/my.map", "rb") as f:
    mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
m = ducer.Map(mm)
```

Note that you can safely close the file once the
[`mmap`](https://docs.python.org/3/library/mmap.html#mmap.mmap) is created.
See [mmap(2)](https://www.man7.org/linux/man-pages/man2/mmap.2.html) for details.

Thanks to high compression ratios, pre-loading maps entirely into memory can be feasible.
In our experience, at least for local SSD storage, performance is virtually
identical, expect pre-loading data takes extra time.
Pre-loading can still make sense though, e.g., with networked storage.

```Python
with open("path/to/my.map", "rb") as f:
    data = f.read()
m = ducer.Map(data)
```



## Access

To the extent that it's feasible,
[`Map`](https://ducer.readthedocs.io/stable/api_reference.html#Map)
and [`Set`](https://ducer.readthedocs.io/stable/api_reference.html#Set)
are intended to be direct replacements for the builtin Python
[`dict`](https://docs.python.org/3/library/stdtypes.html#dict)
and [`set`](https://docs.python.org/3/library/stdtypes.html#set).
For `m, o: Map` and `k: bytes`, the following works as intended:

```Python
k in m
m == o
m[k]
m.get(k)
m.get(k, 42)
len(m)
for k in m:
    pass
for k in m.keys():
    pass
for v in m.values():
    pass
for k, v in m.items():
    pass
```

For `s, o: Set`, and `k: bytes`, the following works as intended:

```Python
k in s
s == o
len(s)
for k in s:
    pass
s.isdisjoint(o)
s.issubset(o)
s <= o  # subset
s < o  # proper subset
s.issuperset(o)
s >= o  # superset
s > o  # proper superset
```

**Note:** Comparison operations are currently only implemented
for other [`Set`](https://ducer.readthedocs.io/stable/api_reference.html#Set)
objects, not the builtin
[`set`](https://docs.python.org/3/library/stdtypes.html#set).
This may change in a future version if there is demand for it.



## Differences to builtins

### Not implemented

Since [`Map`](https://ducer.readthedocs.io/stable/api_reference.html#Map)
is immutable, the following are **not implemented**:
- `clear`
- `fromkeys`
- `pop`
- `popitem`
- `setdefault`
- `update`, `|=`

Since [`Set`](https://ducer.readthedocs.io/stable/api_reference.html#Set)
is immutable, the following are **not implemented**:
- `add`
- `clear`
- `difference_update`, `-=`
- `discard`
- `intersection_update`, `&=`
- `pop`
- `remove`
- `symmetric_difference_update`, `^=`
- `update`, `|=`

Further, the `|`, `&`, `-`, `^` operators are also not implemented,
since it is not possible to specify the storage path.
Use the respective `union`, `intersection`, `difference`,
and `symmetric_difference` methods instead.


### Incompatible syntax

`difference`, `intersection`, `symmetric_difference`, and `union`
have slightly different syntax to accomodate the necessary path.
For `s: Set` and `others: Iterable[Set]`:

```Python
s.difference("path/to/result.set", *others)
s.intersection("path/to/result.set", *others)
s.symmetric_difference("path/to/result.set", *others)
s.union("path/to/result.set", *others)
```

Like the standard library, difference will create the set of
all elements of `s` that are not present in `others`.


### Set operations on maps

Unlike the builtin [`dict`](https://docs.python.org/3/library/stdtypes.html#dict),
the ducer [`Map`](https://ducer.readthedocs.io/stable/api_reference.html#Map)
offers set operations.
The syntax is the same as for sets:

```Python
m.difference("path/to/result.map", *others)
m.intersection("path/to/result.map", *others)
m.symmetric_difference("path/to/result.map", *others)
m.union("path/to/result.map", *others)
```

To resolve conflicts between keys present in multiple maps,
a list of possible values is assembled.
If the key is present in `self`, then it will be the first value.
Values from `others` are added in given order.
By default the last values in the list is used to mimic the behavior
of [`dict.update`](https://docs.python.org/3/library/stdtypes.html#dict.update).
Currently, you can choose between these pre-defined operations:

- [`ducer.Op.First`](https://ducer.readthedocs.io/stable/api_reference.html#Op.First) -- first element
- [`ducer.Op.Mid`](https://ducer.readthedocs.io/stable/api_reference.html#Op.Mid) -- middle element, left if even number of values
- [`ducer.Op.Last`](https://ducer.readthedocs.io/stable/api_reference.html#Op.Last) -- the default
- [`ducer.Op.Min`](https://ducer.readthedocs.io/stable/api_reference.html#Op.Min) -- minimum value
- [`ducer.Op.Avg`](https://ducer.readthedocs.io/stable/api_reference.html#Op.Avg) -- average value cast to `int`
- [`ducer.Op.Median`](https://ducer.readthedocs.io/stable/api_reference.html#Op.Median) -- median value cast to `int`
- [`ducer.Op.Max`](https://ducer.readthedocs.io/stable/api_reference.html#Op.Max) -- maximum value

Some examples:

```Python
m1 = ducer.Map(ducer.Map.build(":memory:", [(b"k1", 1), (b"k2", 1)]))
m2 = ducer.Map(ducer.Map.build(":memory:", [(b"k2", 2), (b"k3", 2)]))
m3 = ducer.Map(ducer.Map.build(":memory:", [(b"k3", 3)]))
mu = ducer.Map(m1.union(":memory:", m2, m3))
print(dict(mu.items()))
# {b'k1': 1, b'k2': 2, b'k3': 3}
mu = ducer.Map(m1.union(":memory:", m2, m3, select=ducer.Op.First))
print(dict(mu.items()))
# {b'k1': 1, b'k2': 1, b'k3': 2}
```



## Advanced search patterns

The underlying FSTs allow for some advanced search patterns that would
otherwise be costly to implement on top of
[`dict`](https://docs.python.org/3/library/stdtypes.html#dict)
and [`set`](https://docs.python.org/3/library/stdtypes.html#set).
Most basic, you can iterate over a range of keys, where
`ge` = greater or equals,
`gt` = greater than,
`le` = less than or equals,
and `lt` = less than:

```Python
m.range(ge=b"key17", lt=b"key42")
m.range(gt=b"key17", le=b"key42")
```

For maps this yields key-value tuples, meaning
[`m.range()`](https://ducer.readthedocs.io/stable/api_reference.html#Map.range)
without limits is equivalent to
[`m.items()`](https://ducer.readthedocs.io/stable/api_reference.html#Map.items).

You can also iterate over all keys that
[start with](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.starts_with)
a certain prefix,
with optional limits same as `range`:

```Python
m.starts_with(b"key", ge=b"key17", lt=b"key42")
m.starts_with(b"key", gt=b"key17", le=b"key42")
```

You can also search for
[subsequences](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.subsequence)
, e.g., all keys matching `*k*7*`,
again with optional limits:

```Python
m.subsequence(b"k7", ge=b"key17", lt=b"key42")
m.subsequence(b"k7", gt=b"key17", le=b"key42")
```

Finally, you can create an `Automaton` to create your own search patterns.
The following automata are available:

- [`always`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.always)
- [`never`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.never)
- [`str`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.str)
- [`subsequence`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.subsequence)
- [`complement`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.complement)
- [`starts_with`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.starts_with)
- [`intersection`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.intersection)
- [`union`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.union)

For example, to recreate
[`Map.starts_with`](https://ducer.readthedocs.io/stable/api_reference.html#Map.starts_with),
you can use the following
automata with the
[`Map.search`](https://ducer.readthedocs.io/stable/api_reference.html#Map.search)
method:

```Python
a = ducer.Automaton.str(b"key").starts_with()
m.search(a)
```

Add
[`complement`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.complement)
to search for keys that do not start with the string:

```Python
a = ducer.Automaton.str(b"key").starts_with().complement()
m.search(a)
```

Finally, you can combine multiple automata, e.g. with
[`union`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.union):

```Python
a1 = ducer.Automaton.str(b"key").starts_with()
a2 = ducer.Automaton.str(b"other").starts_with()
a = a1.union(a2)
m.search(a)
```



## Acknowledgements

Ducer is supported by the [SustainML](https://sustainml.eu/) project.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "ducer",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "big data, compression, dataset, dict, finite-state-machines, finite-state-transducers, frozen, fst, map, serialization, set, streaming",
    "author": null,
    "author_email": "Joachim Folz <joachim.folz@dfki.de>",
    "download_url": "https://files.pythonhosted.org/packages/4a/ae/867b1c4b520bb4458ddcb38fcd27edb6b48130ee236190200eb405603a24/ducer-1.0.3.tar.gz",
    "platform": null,
    "description": "# Ducer documentation\n\nThis package provides fast and compact read-only\n[maps](https://ducer.readthedocs.io/stable/api_reference.html#Map)\nand\n[sets](https://ducer.readthedocs.io/stable/api_reference.html#Set)\nthat scale up to Billions of keys, while being light on resources\nwith a streamable file format.\nComplex search patterns that would be infeasible with Python's builtin\n[`dict`](https://docs.python.org/3/library/stdtypes.html#dict)\nand [`set`](https://docs.python.org/3/library/stdtypes.html#set)\nare not just possible, but very efficient.\nAll of these amazing things are achieved with finite-state-transducers\nprovided by the excellent Rust crate\n[fst](https://github.com/BurntSushi/fst) by Andrew Gallant.\n\n\n\n## Performance\n\nDucer maps and sets can be built and queried at millions of keys per second.\nConsider the following example:\n\n```Python\nimport ducer\nn = 1_000_000_000\nitems = ((b\"%09d\" % i, n-i) for i in range(n))\ndata = ducer.Map.build(\":memory:\", items)\nm = Map(data)\nassert m[b\"900000000\"] == 100_000_000\n```\n\nIn our example, most of the time is spent in Python creating item tuples.\nRegardless, building happens at almost 4 Million items per second\non my humble laptop.\nRetrieving individual keys is similarly speedy, and simply iterating\nover all items is twice as fast at 8 Million items per second.\n\nThis toy example is almost a best case for FSTs, so the resulting\noutput is just 464 bytes.\nA real-world example with 1.1 Billion keys, where the key-value pairs\noccupy 21 GiB without any kind of searchability (stored in already quite\ncompact [msgpack](https://github.com/msgpack/msgpack-python) format),\nresults in a 4.6 GiB file.\nBuilding and retrieval are naturally a bit slower,\nbut still within the 2-3 Million items per second range.\n\n\n\n## Limitations\n\nPerformance is rarely free,\nso there are some limitations you should consider before proceeding:\n\n* Keys **must** be **[`bytes`](https://docs.python.org/3/library/stdtypes.html#bytes)**\n* Keys **must** be inserted in lexicographical order\n* Map values **must** be non-negative integers less than 2^64\n* Once built, maps and sets **cannot** be altered\n\n\n\n## Installation\n\nMost users should be able to simply do:\n\n```\npip install ducer\n```\n\nTo build from source you will need a recent Rust toolchain.\nUse your preferred method of installation, or follow the official\n[instructions](https://www.rust-lang.org/tools/install) to install Rust.\nThen run the following at the toplevel of the repository:\n\n```\npip install .\n```\n\n\n\n## Building\n\nAbove, we already showed that\n[`Map.build`](https://ducer.readthedocs.io/stable/api_reference.html#Map.build)\ncan build maps in memory by passing\n`\":memory:\"` as path:\n\n```Python\ndata = ducer.Map.build(\":memory:\", items)\n```\n\nIf you pass any other path\n(either [`str`](https://docs.python.org/3/library/stdtypes.html#str)\nor [`Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path);\nthe parent directory must exist),\nyour map will be written directly to that file:\n\n```Python\nducer.Map.build(\"path/to/my.map\", items)\n```\n\nBuilding a map like this uses virtually no extra memory.\n\n\n\n## Opening\n\nOne key advantage of ducer maps is streamability.\nUnlike the builtin [`dict`](https://docs.python.org/3/library/stdtypes.html#dict),\na [`Map`](https://ducer.readthedocs.io/stable/api_reference.html#Map)\ndoes not have to reside entirely in memory.\nYou can, e.g., use the builtin\n[`mmap`](https://docs.python.org/3/library/mmap.html#mmap.mmap) to stream map data:\n\n```Python\nwith open(\"path/to/my.map\", \"rb\") as f:\n    mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)\nm = ducer.Map(mm)\n```\n\nNote that you can safely close the file once the\n[`mmap`](https://docs.python.org/3/library/mmap.html#mmap.mmap) is created.\nSee [mmap(2)](https://www.man7.org/linux/man-pages/man2/mmap.2.html) for details.\n\nThanks to high compression ratios, pre-loading maps entirely into memory can be feasible.\nIn our experience, at least for local SSD storage, performance is virtually\nidentical, expect pre-loading data takes extra time.\nPre-loading can still make sense though, e.g., with networked storage.\n\n```Python\nwith open(\"path/to/my.map\", \"rb\") as f:\n    data = f.read()\nm = ducer.Map(data)\n```\n\n\n\n## Access\n\nTo the extent that it's feasible,\n[`Map`](https://ducer.readthedocs.io/stable/api_reference.html#Map)\nand [`Set`](https://ducer.readthedocs.io/stable/api_reference.html#Set)\nare intended to be direct replacements for the builtin Python\n[`dict`](https://docs.python.org/3/library/stdtypes.html#dict)\nand [`set`](https://docs.python.org/3/library/stdtypes.html#set).\nFor `m, o: Map` and `k: bytes`, the following works as intended:\n\n```Python\nk in m\nm == o\nm[k]\nm.get(k)\nm.get(k, 42)\nlen(m)\nfor k in m:\n    pass\nfor k in m.keys():\n    pass\nfor v in m.values():\n    pass\nfor k, v in m.items():\n    pass\n```\n\nFor `s, o: Set`, and `k: bytes`, the following works as intended:\n\n```Python\nk in s\ns == o\nlen(s)\nfor k in s:\n    pass\ns.isdisjoint(o)\ns.issubset(o)\ns <= o  # subset\ns < o  # proper subset\ns.issuperset(o)\ns >= o  # superset\ns > o  # proper superset\n```\n\n**Note:** Comparison operations are currently only implemented\nfor other [`Set`](https://ducer.readthedocs.io/stable/api_reference.html#Set)\nobjects, not the builtin\n[`set`](https://docs.python.org/3/library/stdtypes.html#set).\nThis may change in a future version if there is demand for it.\n\n\n\n## Differences to builtins\n\n### Not implemented\n\nSince [`Map`](https://ducer.readthedocs.io/stable/api_reference.html#Map)\nis immutable, the following are **not implemented**:\n- `clear`\n- `fromkeys`\n- `pop`\n- `popitem`\n- `setdefault`\n- `update`, `|=`\n\nSince [`Set`](https://ducer.readthedocs.io/stable/api_reference.html#Set)\nis immutable, the following are **not implemented**:\n- `add`\n- `clear`\n- `difference_update`, `-=`\n- `discard`\n- `intersection_update`, `&=`\n- `pop`\n- `remove`\n- `symmetric_difference_update`, `^=`\n- `update`, `|=`\n\nFurther, the `|`, `&`, `-`, `^` operators are also not implemented,\nsince it is not possible to specify the storage path.\nUse the respective `union`, `intersection`, `difference`,\nand `symmetric_difference` methods instead.\n\n\n### Incompatible syntax\n\n`difference`, `intersection`, `symmetric_difference`, and `union`\nhave slightly different syntax to accomodate the necessary path.\nFor `s: Set` and `others: Iterable[Set]`:\n\n```Python\ns.difference(\"path/to/result.set\", *others)\ns.intersection(\"path/to/result.set\", *others)\ns.symmetric_difference(\"path/to/result.set\", *others)\ns.union(\"path/to/result.set\", *others)\n```\n\nLike the standard library, difference will create the set of\nall elements of `s` that are not present in `others`.\n\n\n### Set operations on maps\n\nUnlike the builtin [`dict`](https://docs.python.org/3/library/stdtypes.html#dict),\nthe ducer [`Map`](https://ducer.readthedocs.io/stable/api_reference.html#Map)\noffers set operations.\nThe syntax is the same as for sets:\n\n```Python\nm.difference(\"path/to/result.map\", *others)\nm.intersection(\"path/to/result.map\", *others)\nm.symmetric_difference(\"path/to/result.map\", *others)\nm.union(\"path/to/result.map\", *others)\n```\n\nTo resolve conflicts between keys present in multiple maps,\na list of possible values is assembled.\nIf the key is present in `self`, then it will be the first value.\nValues from `others` are added in given order.\nBy default the last values in the list is used to mimic the behavior\nof [`dict.update`](https://docs.python.org/3/library/stdtypes.html#dict.update).\nCurrently, you can choose between these pre-defined operations:\n\n- [`ducer.Op.First`](https://ducer.readthedocs.io/stable/api_reference.html#Op.First) -- first element\n- [`ducer.Op.Mid`](https://ducer.readthedocs.io/stable/api_reference.html#Op.Mid) -- middle element, left if even number of values\n- [`ducer.Op.Last`](https://ducer.readthedocs.io/stable/api_reference.html#Op.Last) -- the default\n- [`ducer.Op.Min`](https://ducer.readthedocs.io/stable/api_reference.html#Op.Min) -- minimum value\n- [`ducer.Op.Avg`](https://ducer.readthedocs.io/stable/api_reference.html#Op.Avg) -- average value cast to `int`\n- [`ducer.Op.Median`](https://ducer.readthedocs.io/stable/api_reference.html#Op.Median) -- median value cast to `int`\n- [`ducer.Op.Max`](https://ducer.readthedocs.io/stable/api_reference.html#Op.Max) -- maximum value\n\nSome examples:\n\n```Python\nm1 = ducer.Map(ducer.Map.build(\":memory:\", [(b\"k1\", 1), (b\"k2\", 1)]))\nm2 = ducer.Map(ducer.Map.build(\":memory:\", [(b\"k2\", 2), (b\"k3\", 2)]))\nm3 = ducer.Map(ducer.Map.build(\":memory:\", [(b\"k3\", 3)]))\nmu = ducer.Map(m1.union(\":memory:\", m2, m3))\nprint(dict(mu.items()))\n# {b'k1': 1, b'k2': 2, b'k3': 3}\nmu = ducer.Map(m1.union(\":memory:\", m2, m3, select=ducer.Op.First))\nprint(dict(mu.items()))\n# {b'k1': 1, b'k2': 1, b'k3': 2}\n```\n\n\n\n## Advanced search patterns\n\nThe underlying FSTs allow for some advanced search patterns that would\notherwise be costly to implement on top of\n[`dict`](https://docs.python.org/3/library/stdtypes.html#dict)\nand [`set`](https://docs.python.org/3/library/stdtypes.html#set).\nMost basic, you can iterate over a range of keys, where\n`ge` = greater or equals,\n`gt` = greater than,\n`le` = less than or equals,\nand `lt` = less than:\n\n```Python\nm.range(ge=b\"key17\", lt=b\"key42\")\nm.range(gt=b\"key17\", le=b\"key42\")\n```\n\nFor maps this yields key-value tuples, meaning\n[`m.range()`](https://ducer.readthedocs.io/stable/api_reference.html#Map.range)\nwithout limits is equivalent to\n[`m.items()`](https://ducer.readthedocs.io/stable/api_reference.html#Map.items).\n\nYou can also iterate over all keys that\n[start with](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.starts_with)\na certain prefix,\nwith optional limits same as `range`:\n\n```Python\nm.starts_with(b\"key\", ge=b\"key17\", lt=b\"key42\")\nm.starts_with(b\"key\", gt=b\"key17\", le=b\"key42\")\n```\n\nYou can also search for\n[subsequences](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.subsequence)\n, e.g., all keys matching `*k*7*`,\nagain with optional limits:\n\n```Python\nm.subsequence(b\"k7\", ge=b\"key17\", lt=b\"key42\")\nm.subsequence(b\"k7\", gt=b\"key17\", le=b\"key42\")\n```\n\nFinally, you can create an `Automaton` to create your own search patterns.\nThe following automata are available:\n\n- [`always`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.always)\n- [`never`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.never)\n- [`str`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.str)\n- [`subsequence`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.subsequence)\n- [`complement`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.complement)\n- [`starts_with`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.starts_with)\n- [`intersection`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.intersection)\n- [`union`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.union)\n\nFor example, to recreate\n[`Map.starts_with`](https://ducer.readthedocs.io/stable/api_reference.html#Map.starts_with),\nyou can use the following\nautomata with the\n[`Map.search`](https://ducer.readthedocs.io/stable/api_reference.html#Map.search)\nmethod:\n\n```Python\na = ducer.Automaton.str(b\"key\").starts_with()\nm.search(a)\n```\n\nAdd\n[`complement`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.complement)\nto search for keys that do not start with the string:\n\n```Python\na = ducer.Automaton.str(b\"key\").starts_with().complement()\nm.search(a)\n```\n\nFinally, you can combine multiple automata, e.g. with\n[`union`](https://ducer.readthedocs.io/stable/api_reference.html#Automaton.union):\n\n```Python\na1 = ducer.Automaton.str(b\"key\").starts_with()\na2 = ducer.Automaton.str(b\"other\").starts_with()\na = a1.union(a2)\nm.search(a)\n```\n\n\n\n## Acknowledgements\n\nDucer is supported by the [SustainML](https://sustainml.eu/) project.\n",
    "bugtrack_url": null,
    "license": "Copyright (c) 2024 Joachim Folz  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
    "summary": "Fast and compact maps and sets with Billions of keys, based on finite-state-transducers.",
    "version": "1.0.3",
    "project_urls": {
        "Changelog": "https://github.com/jfolz/ducer/blob/main/CHANGELOG.md",
        "Documentation": "https://ducer.readthedocs.io/stable/",
        "Homepage": "https://github.com/jfolz/ducer",
        "Issues": "https://github.com/jfolz/ducer/issues",
        "Repository": "https://github.com/jfolz/ducer"
    },
    "split_keywords": [
        "big data",
        " compression",
        " dataset",
        " dict",
        " finite-state-machines",
        " finite-state-transducers",
        " frozen",
        " fst",
        " map",
        " serialization",
        " set",
        " streaming"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "6a4ccf84f9a6c4b49092d815b7cd5993e6888bd33dc32547223c632e2f5b15a4",
                "md5": "f033b9ca7cd24b6f5cdf4ecef4841761",
                "sha256": "40199d4124a8409f7e592a8013ada7de6fe12770dc0c3b1fe55d19e584758bb1"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp310-cp310-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "f033b9ca7cd24b6f5cdf4ecef4841761",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": ">=3.8",
            "size": 322634,
            "upload_time": "2024-08-10T11:33:09",
            "upload_time_iso_8601": "2024-08-10T11:33:09.401180Z",
            "url": "https://files.pythonhosted.org/packages/6a/4c/cf84f9a6c4b49092d815b7cd5993e6888bd33dc32547223c632e2f5b15a4/ducer-1.0.3-cp310-cp310-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ee885c379fc218770ee3dbf580a9cd1e23a480854ad5dd5df6da6dd2139eb3bd",
                "md5": "4798664c6a30bbd1c25090f96d66df68",
                "sha256": "2b897e56d5dcef98365df2db52ee6b138f2008ac588acb59cec42242542edcc3"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp310-cp310-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "4798664c6a30bbd1c25090f96d66df68",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": ">=3.8",
            "size": 306610,
            "upload_time": "2024-08-10T11:33:11",
            "upload_time_iso_8601": "2024-08-10T11:33:11.057059Z",
            "url": "https://files.pythonhosted.org/packages/ee/88/5c379fc218770ee3dbf580a9cd1e23a480854ad5dd5df6da6dd2139eb3bd/ducer-1.0.3-cp310-cp310-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e93c9de02a0a4b8dfe75b754d950d49e20acbedba704f292221a04693a18ba99",
                "md5": "06ef9ce407f3221742b75443df22287f",
                "sha256": "73fb0b0ed9148dbf950545899b74d1f3d75b51e451b6ec0f0b275c2f594733ea"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "has_sig": false,
            "md5_digest": "06ef9ce407f3221742b75443df22287f",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": ">=3.8",
            "size": 321728,
            "upload_time": "2024-08-10T11:33:12",
            "upload_time_iso_8601": "2024-08-10T11:33:12.509362Z",
            "url": "https://files.pythonhosted.org/packages/e9/3c/9de02a0a4b8dfe75b754d950d49e20acbedba704f292221a04693a18ba99/ducer-1.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "120b36d30d7f547f1571e618c4c06bcfe3292b751938f878181e45cdf4b99931",
                "md5": "55c1a4207f5efc9fc1a41f197352cad2",
                "sha256": "73311656b72f754a769c0f747cbdd77b088b115badcdb1513097db06897b29cd"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "55c1a4207f5efc9fc1a41f197352cad2",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": ">=3.8",
            "size": 340955,
            "upload_time": "2024-08-10T11:33:14",
            "upload_time_iso_8601": "2024-08-10T11:33:14.133448Z",
            "url": "https://files.pythonhosted.org/packages/12/0b/36d30d7f547f1571e618c4c06bcfe3292b751938f878181e45cdf4b99931/ducer-1.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "59aca51de2c9257b49e61df874577057994c37535d7cc87b33203cf5072780f7",
                "md5": "2ca9aefba01edecd8dfa7d266bd5d2be",
                "sha256": "48e701530c7cdd33088307ead55eb77c5631db17bc92580b56d79c3dfef5ffd7"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp310-cp310-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "2ca9aefba01edecd8dfa7d266bd5d2be",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": ">=3.8",
            "size": 267609,
            "upload_time": "2024-08-10T11:33:15",
            "upload_time_iso_8601": "2024-08-10T11:33:15.728914Z",
            "url": "https://files.pythonhosted.org/packages/59/ac/a51de2c9257b49e61df874577057994c37535d7cc87b33203cf5072780f7/ducer-1.0.3-cp310-cp310-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "6c3f597621a97132897a95f401eb1c0ac6ad173b6ab28ed75b52ac0e855800d4",
                "md5": "0cca1192eb4807172dce291dabefc406",
                "sha256": "7a0fde0e119cde4479748aee3cbb50c2d9a10dbd98ead79a565f521941b64ecb"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp311-cp311-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "0cca1192eb4807172dce291dabefc406",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.8",
            "size": 322451,
            "upload_time": "2024-08-10T11:33:17",
            "upload_time_iso_8601": "2024-08-10T11:33:17.356770Z",
            "url": "https://files.pythonhosted.org/packages/6c/3f/597621a97132897a95f401eb1c0ac6ad173b6ab28ed75b52ac0e855800d4/ducer-1.0.3-cp311-cp311-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "01f9ea3f523b18c3924e4d3d66499116e1bb2a29e208a34c4fd35c547cfebaa3",
                "md5": "2f240d7ad7558a282c497e7528d743b7",
                "sha256": "1c97074894edad853d8efc758e2153c66d08c14c655b4de8f85b070381a40336"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp311-cp311-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "2f240d7ad7558a282c497e7528d743b7",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.8",
            "size": 306414,
            "upload_time": "2024-08-10T11:33:18",
            "upload_time_iso_8601": "2024-08-10T11:33:18.801115Z",
            "url": "https://files.pythonhosted.org/packages/01/f9/ea3f523b18c3924e4d3d66499116e1bb2a29e208a34c4fd35c547cfebaa3/ducer-1.0.3-cp311-cp311-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7ee7bdda5387c7903a9768865123d442ed7bcfdca8214031d8c90d19ece762d2",
                "md5": "bf5458f377200e68e0c4035926f27d5c",
                "sha256": "578b064ba7344feafecc77471e62d0393749dffdf1a29673b7a971f9cca1460c"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "has_sig": false,
            "md5_digest": "bf5458f377200e68e0c4035926f27d5c",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.8",
            "size": 321576,
            "upload_time": "2024-08-10T11:33:20",
            "upload_time_iso_8601": "2024-08-10T11:33:20.310210Z",
            "url": "https://files.pythonhosted.org/packages/7e/e7/bdda5387c7903a9768865123d442ed7bcfdca8214031d8c90d19ece762d2/ducer-1.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "cb63299a6fbfeb5c31ac55122f5683f3d4b590f86aab6208cc0681eb561651ba",
                "md5": "a184ae588e22b927c5887fc12e5f9d3b",
                "sha256": "91f93dffe44af33d3549905b135d7de3196d7b72b03c988e1c4eb0fa7b2b9c8d"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "a184ae588e22b927c5887fc12e5f9d3b",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.8",
            "size": 340927,
            "upload_time": "2024-08-10T11:33:21",
            "upload_time_iso_8601": "2024-08-10T11:33:21.876679Z",
            "url": "https://files.pythonhosted.org/packages/cb/63/299a6fbfeb5c31ac55122f5683f3d4b590f86aab6208cc0681eb561651ba/ducer-1.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8e8ca877e149441b240e0a4d5c691faa353b859723d81d9cdffc70f664230aed",
                "md5": "fa3eecf8a63b41961d0adb6e2a44516c",
                "sha256": "9432cc54826181daccf0723121a6fd369909244bab38cbeeecddedaa3bc6740e"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp311-cp311-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "fa3eecf8a63b41961d0adb6e2a44516c",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.8",
            "size": 267409,
            "upload_time": "2024-08-10T11:33:23",
            "upload_time_iso_8601": "2024-08-10T11:33:23.380707Z",
            "url": "https://files.pythonhosted.org/packages/8e/8c/a877e149441b240e0a4d5c691faa353b859723d81d9cdffc70f664230aed/ducer-1.0.3-cp311-cp311-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "dbce06b6cf6ddacbf3d70f2cc9fa1dcf8c7a6ef3c1e78682cc5c248141753035",
                "md5": "ae8a1bc5a73c51e4af97f34d01467488",
                "sha256": "c7bca7cf5bcdf9d51e045fbd80eeae00ea3232c873f9e81dd1580c92f5edc9dc"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp312-cp312-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "ae8a1bc5a73c51e4af97f34d01467488",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.8",
            "size": 322123,
            "upload_time": "2024-08-10T11:33:24",
            "upload_time_iso_8601": "2024-08-10T11:33:24.667910Z",
            "url": "https://files.pythonhosted.org/packages/db/ce/06b6cf6ddacbf3d70f2cc9fa1dcf8c7a6ef3c1e78682cc5c248141753035/ducer-1.0.3-cp312-cp312-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f78448fd8ba45d565221ee6cb684e78f93ef34bba590554165c0f633e801de0c",
                "md5": "9773adf2d30fe931c5b9c45d41254023",
                "sha256": "7823babf6004f970d639ceb45d30191cbb05b781a0b9766cf044e7c06bdd7a40"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp312-cp312-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "9773adf2d30fe931c5b9c45d41254023",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.8",
            "size": 307624,
            "upload_time": "2024-08-10T11:33:26",
            "upload_time_iso_8601": "2024-08-10T11:33:26.141084Z",
            "url": "https://files.pythonhosted.org/packages/f7/84/48fd8ba45d565221ee6cb684e78f93ef34bba590554165c0f633e801de0c/ducer-1.0.3-cp312-cp312-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c034dee80bd2b862c4ecc23a859ab2051ac2d4412b1ef1c8440f650f3924f4f9",
                "md5": "c51099975046a01497bb95c4fcf186da",
                "sha256": "7ba36d5e54a32ba068f44f180058319cccfce2dc3d5eaf77709ed2b46d6aedc0"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "has_sig": false,
            "md5_digest": "c51099975046a01497bb95c4fcf186da",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.8",
            "size": 322661,
            "upload_time": "2024-08-10T11:33:27",
            "upload_time_iso_8601": "2024-08-10T11:33:27.459136Z",
            "url": "https://files.pythonhosted.org/packages/c0/34/dee80bd2b862c4ecc23a859ab2051ac2d4412b1ef1c8440f650f3924f4f9/ducer-1.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "70d51675cd38d3f11fe3de19e54429233c4a91f0d610e22dd290efa5a88dca09",
                "md5": "526eb309ecb9ca9016feabdb4409c6a9",
                "sha256": "24a302546bca85ce1ff32a78a9187c6b93924f788a887d4028fe27b86c559f1d"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "526eb309ecb9ca9016feabdb4409c6a9",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.8",
            "size": 342053,
            "upload_time": "2024-08-10T11:33:29",
            "upload_time_iso_8601": "2024-08-10T11:33:29.138638Z",
            "url": "https://files.pythonhosted.org/packages/70/d5/1675cd38d3f11fe3de19e54429233c4a91f0d610e22dd290efa5a88dca09/ducer-1.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "43b30dcef1e13ff260807b0a19d95a885f104aac9881a24812cd1f02384a281a",
                "md5": "d6875469fbd58bc3a030985c943e5764",
                "sha256": "ea8836f2ff35012e4b1f7ee2f4b114c0e28903158b76ef71dc9e7d67f56a0dfd"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp312-cp312-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "d6875469fbd58bc3a030985c943e5764",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.8",
            "size": 271803,
            "upload_time": "2024-08-10T11:33:30",
            "upload_time_iso_8601": "2024-08-10T11:33:30.504226Z",
            "url": "https://files.pythonhosted.org/packages/43/b3/0dcef1e13ff260807b0a19d95a885f104aac9881a24812cd1f02384a281a/ducer-1.0.3-cp312-cp312-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "6b97d305726c55f15c3e9e911911b5a746603872231b5cbdbf15dc4d03082bde",
                "md5": "4284ea048f023a50ae15931d3a410b25",
                "sha256": "5d5c5532b26a8355845521e7b3b09368d48532d39a35f52a523ffc69ea5de95f"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp313-cp313-macosx_10_13_x86_64.whl",
            "has_sig": false,
            "md5_digest": "4284ea048f023a50ae15931d3a410b25",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.8",
            "size": 322121,
            "upload_time": "2024-08-10T11:33:31",
            "upload_time_iso_8601": "2024-08-10T11:33:31.802184Z",
            "url": "https://files.pythonhosted.org/packages/6b/97/d305726c55f15c3e9e911911b5a746603872231b5cbdbf15dc4d03082bde/ducer-1.0.3-cp313-cp313-macosx_10_13_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "82af0bf55c4f118ae06d70d502875161f86b370257df2d538fb1f463ad604171",
                "md5": "b97ce8aed9766bf27d0c33d62514a6ee",
                "sha256": "32c4132b78a18847906068db9867e165e1ef97e3810b118e28b69016be030030"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp313-cp313-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "b97ce8aed9766bf27d0c33d62514a6ee",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.8",
            "size": 307625,
            "upload_time": "2024-08-10T11:33:33",
            "upload_time_iso_8601": "2024-08-10T11:33:33.226722Z",
            "url": "https://files.pythonhosted.org/packages/82/af/0bf55c4f118ae06d70d502875161f86b370257df2d538fb1f463ad604171/ducer-1.0.3-cp313-cp313-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "929289f534459b1d85bfa74d266218d58ae17f3cf088150ffd8de1669ed757d4",
                "md5": "7941cb237d4ac52bcd803a292397027a",
                "sha256": "abf272829a82c6f4846ed409fabb628f787651b951c31d280026465a716d3950"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "has_sig": false,
            "md5_digest": "7941cb237d4ac52bcd803a292397027a",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.8",
            "size": 322661,
            "upload_time": "2024-08-10T11:33:34",
            "upload_time_iso_8601": "2024-08-10T11:33:34.368915Z",
            "url": "https://files.pythonhosted.org/packages/92/92/89f534459b1d85bfa74d266218d58ae17f3cf088150ffd8de1669ed757d4/ducer-1.0.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "051231fb9dc45ea9ca2cb688e3ff7cd85e983ad08093124e91a17b8e8ae0c17b",
                "md5": "7f5cc076c37cd309fb4d7f5d8518a20d",
                "sha256": "1bee5b65e65b0bfc8e2459b40e4a569320ad0ec72772ef8ea5d1da1eb89623d3"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "7f5cc076c37cd309fb4d7f5d8518a20d",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.8",
            "size": 342053,
            "upload_time": "2024-08-10T11:33:35",
            "upload_time_iso_8601": "2024-08-10T11:33:35.822837Z",
            "url": "https://files.pythonhosted.org/packages/05/12/31fb9dc45ea9ca2cb688e3ff7cd85e983ad08093124e91a17b8e8ae0c17b/ducer-1.0.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c3773847e7967477689491fd387cb8ea4d1d47464d4e2302b66397e01c6ef75f",
                "md5": "59fc23ea4643c539b9340f7f04d009c5",
                "sha256": "0de745ee3e5fc7d241a3a4f8d96f792256efa583e943a15324653d79d3f8e044"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp313-cp313-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "59fc23ea4643c539b9340f7f04d009c5",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.8",
            "size": 271803,
            "upload_time": "2024-08-10T11:33:37",
            "upload_time_iso_8601": "2024-08-10T11:33:37.008987Z",
            "url": "https://files.pythonhosted.org/packages/c3/77/3847e7967477689491fd387cb8ea4d1d47464d4e2302b66397e01c6ef75f/ducer-1.0.3-cp313-cp313-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "61cfed0f16c39391361c0408cbb41ef2291713330be52ab459bc1d911400dfa5",
                "md5": "9bfec06569d63a035196ff18597ca842",
                "sha256": "c3b67b485ca732b9606f8d2eda93f0d8619ae49ef3a4df17a40165114150ff54"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp38-cp38-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "9bfec06569d63a035196ff18597ca842",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 323385,
            "upload_time": "2024-08-10T11:33:38",
            "upload_time_iso_8601": "2024-08-10T11:33:38.289351Z",
            "url": "https://files.pythonhosted.org/packages/61/cf/ed0f16c39391361c0408cbb41ef2291713330be52ab459bc1d911400dfa5/ducer-1.0.3-cp38-cp38-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5afb7a644f5aa8f53c5c42e0652d8d7bb87439779fe70d439ccd3010dc275951",
                "md5": "ce075c768913b8328159cb736d1af297",
                "sha256": "135dc100250556a3b261829eece644559bdb42c63e67968599cdd358431b9102"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp38-cp38-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "ce075c768913b8328159cb736d1af297",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 307293,
            "upload_time": "2024-08-10T11:33:39",
            "upload_time_iso_8601": "2024-08-10T11:33:39.354771Z",
            "url": "https://files.pythonhosted.org/packages/5a/fb/7a644f5aa8f53c5c42e0652d8d7bb87439779fe70d439ccd3010dc275951/ducer-1.0.3-cp38-cp38-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5dc0d99c7e2eccb83a6c91bd73ba48f917410f0152ac70c49f3ad4707cadbe8f",
                "md5": "6b3358ec418e62c3b9f685d9c0f901ce",
                "sha256": "2699e9f6aafe1c08bfcc7bf9c47e53616b625c52c1764fb744e9591edb9ce7bb"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "has_sig": false,
            "md5_digest": "6b3358ec418e62c3b9f685d9c0f901ce",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 322606,
            "upload_time": "2024-08-10T11:33:40",
            "upload_time_iso_8601": "2024-08-10T11:33:40.949727Z",
            "url": "https://files.pythonhosted.org/packages/5d/c0/d99c7e2eccb83a6c91bd73ba48f917410f0152ac70c49f3ad4707cadbe8f/ducer-1.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "1e4ada283301d9899736c946cce61cfab9e38e0b957c31d126195d340b17a935",
                "md5": "43c7a3119e973ded34e01b320c6ee91f",
                "sha256": "0620b97ba1996f10b41521e6c13bc486e3d479c8a368d85e36c1d04d52065581"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "43c7a3119e973ded34e01b320c6ee91f",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 341745,
            "upload_time": "2024-08-10T11:33:42",
            "upload_time_iso_8601": "2024-08-10T11:33:42.013732Z",
            "url": "https://files.pythonhosted.org/packages/1e/4a/da283301d9899736c946cce61cfab9e38e0b957c31d126195d340b17a935/ducer-1.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f3dde7f19b9e06de8f6bd96429eb034046b0b8c48b8a6f7bf8bf3249e8be4962",
                "md5": "b96b36a199abf3022a0f0aedfad3064d",
                "sha256": "55a86ec3257df9bce1607a49aa829c9e550284494ac3829ec2c752bdd5a816ae"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp38-cp38-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "b96b36a199abf3022a0f0aedfad3064d",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 268552,
            "upload_time": "2024-08-10T11:33:43",
            "upload_time_iso_8601": "2024-08-10T11:33:43.559095Z",
            "url": "https://files.pythonhosted.org/packages/f3/dd/e7f19b9e06de8f6bd96429eb034046b0b8c48b8a6f7bf8bf3249e8be4962/ducer-1.0.3-cp38-cp38-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "372adfcfddf6d5db546708125321ce0b06cb0dec395d37958a3a04e7d7d220a8",
                "md5": "4afad6c3eaf3c829480a9644edbb2688",
                "sha256": "560068f370b7c252f430b3b8a4f13f6a74bd8522d7bb7f6be68ba3340bd46247"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp39-cp39-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "4afad6c3eaf3c829480a9644edbb2688",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": ">=3.8",
            "size": 323202,
            "upload_time": "2024-08-10T11:33:44",
            "upload_time_iso_8601": "2024-08-10T11:33:44.691943Z",
            "url": "https://files.pythonhosted.org/packages/37/2a/dfcfddf6d5db546708125321ce0b06cb0dec395d37958a3a04e7d7d220a8/ducer-1.0.3-cp39-cp39-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "fdde18dc67242a702909726adb126909602e7c41f0c7db2650da8eff2540ff8f",
                "md5": "d28c50bb0199e9d3ee3a8f38d63da2ff",
                "sha256": "2600e6104eac9462ed11246a4a8a26c8e8f7a7ea74260cb0d764f20997082d56"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp39-cp39-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "d28c50bb0199e9d3ee3a8f38d63da2ff",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": ">=3.8",
            "size": 307227,
            "upload_time": "2024-08-10T11:33:46",
            "upload_time_iso_8601": "2024-08-10T11:33:46.202122Z",
            "url": "https://files.pythonhosted.org/packages/fd/de/18dc67242a702909726adb126909602e7c41f0c7db2650da8eff2540ff8f/ducer-1.0.3-cp39-cp39-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "602eedc06f2f12b653e19593553357415eecc71b642ff33b444e14198482a8b1",
                "md5": "4830320c8a72ac7be1b146e6a69bc7ff",
                "sha256": "ce77de43505e71749bab32a7d74f1032853fa0ab15cfc9c981cf1b8fe09e29dc"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "has_sig": false,
            "md5_digest": "4830320c8a72ac7be1b146e6a69bc7ff",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": ">=3.8",
            "size": 322354,
            "upload_time": "2024-08-10T11:33:47",
            "upload_time_iso_8601": "2024-08-10T11:33:47.422440Z",
            "url": "https://files.pythonhosted.org/packages/60/2e/edc06f2f12b653e19593553357415eecc71b642ff33b444e14198482a8b1/ducer-1.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "eeb3071894ca2cf7b2edbea80b8850fac9a10dfd8403946da779de0699a8d7e3",
                "md5": "ed2eaa18a9af27a3af1417f481d14d1e",
                "sha256": "6917f6b483a17bc577a942d62a8dc3216819510c8873ea19fa9497e905bb2d24"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "ed2eaa18a9af27a3af1417f481d14d1e",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": ">=3.8",
            "size": 341628,
            "upload_time": "2024-08-10T11:33:48",
            "upload_time_iso_8601": "2024-08-10T11:33:48.856656Z",
            "url": "https://files.pythonhosted.org/packages/ee/b3/071894ca2cf7b2edbea80b8850fac9a10dfd8403946da779de0699a8d7e3/ducer-1.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "2958578dbcc034f5f2b8e77f06ba686a785d33d539ef28b5f2e06c3635fefade",
                "md5": "e92c2d286be9ef6af6c82869ae7b12cf",
                "sha256": "170665334793580446e8f4c08e04139388b38f250df42c1ac22a5399bc54ac14"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3-cp39-cp39-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "e92c2d286be9ef6af6c82869ae7b12cf",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": ">=3.8",
            "size": 268464,
            "upload_time": "2024-08-10T11:33:50",
            "upload_time_iso_8601": "2024-08-10T11:33:50.311694Z",
            "url": "https://files.pythonhosted.org/packages/29/58/578dbcc034f5f2b8e77f06ba686a785d33d539ef28b5f2e06c3635fefade/ducer-1.0.3-cp39-cp39-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "4aae867b1c4b520bb4458ddcb38fcd27edb6b48130ee236190200eb405603a24",
                "md5": "8ff558c3957525e71fe1bc6c83da188a",
                "sha256": "c6b6b9656d067eacbab3b4c7b12170b051e77676d3e758c6d73fbeac044f44fd"
            },
            "downloads": -1,
            "filename": "ducer-1.0.3.tar.gz",
            "has_sig": false,
            "md5_digest": "8ff558c3957525e71fe1bc6c83da188a",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 39548,
            "upload_time": "2024-08-10T11:33:51",
            "upload_time_iso_8601": "2024-08-10T11:33:51.729269Z",
            "url": "https://files.pythonhosted.org/packages/4a/ae/867b1c4b520bb4458ddcb38fcd27edb6b48130ee236190200eb405603a24/ducer-1.0.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-08-10 11:33:51",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "jfolz",
    "github_project": "ducer",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "ducer"
}
        
Elapsed time: 2.96933s