ducer 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ducer-0.2.1/.github/workflows/build_wheels.yml +94 -0
- ducer-0.2.1/.gitignore +6 -0
- ducer-0.2.1/CHANGELOG.md +41 -0
- ducer-0.2.1/LICENSE +7 -0
- ducer-0.2.1/MANIFEST.in +2 -0
- ducer-0.2.1/PKG-INFO +122 -0
- ducer-0.2.1/README.md +87 -0
- ducer-0.2.1/ducer/__init__.py +3 -0
- ducer-0.2.1/ducer.egg-info/PKG-INFO +122 -0
- ducer-0.2.1/ducer.egg-info/SOURCES.txt +20 -0
- ducer-0.2.1/ducer.egg-info/dependency_links.txt +1 -0
- ducer-0.2.1/ducer.egg-info/top_level.txt +1 -0
- ducer-0.2.1/pyproject.toml +71 -0
- ducer-0.2.1/rust/Cargo.lock +262 -0
- ducer-0.2.1/rust/Cargo.toml +20 -0
- ducer-0.2.1/rust/src/automaton.rs +480 -0
- ducer-0.2.1/rust/src/buffer.rs +122 -0
- ducer-0.2.1/rust/src/encoding.rs +46 -0
- ducer-0.2.1/rust/src/lib.rs +20 -0
- ducer-0.2.1/rust/src/map.rs +520 -0
- ducer-0.2.1/rust/src/set.rs +374 -0
- ducer-0.2.1/setup.cfg +4 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
name: Build
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
build_wheels:
|
|
7
|
+
name: Build wheels on ${{ matrix.os }} ${{ matrix.arch }}
|
|
8
|
+
runs-on: ${{ matrix.os }}
|
|
9
|
+
strategy:
|
|
10
|
+
matrix:
|
|
11
|
+
os: [ubuntu-20.04, macos-12, macos-14, windows-2019]
|
|
12
|
+
arch: [x86_64, aarch64]
|
|
13
|
+
exclude:
|
|
14
|
+
- os: macos-12
|
|
15
|
+
arch: aarch64
|
|
16
|
+
- os: macos-14
|
|
17
|
+
arch: x86_64
|
|
18
|
+
- os: windows-2019
|
|
19
|
+
arch: aarch64
|
|
20
|
+
steps:
|
|
21
|
+
- name: Debug output memory and processes
|
|
22
|
+
if: runner.os == 'Linux'
|
|
23
|
+
uses: catchpoint/workflow-telemetry-action@v2
|
|
24
|
+
with:
|
|
25
|
+
job_summary: true
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- name: Set up QEMU
|
|
28
|
+
if: runner.os == 'Linux'
|
|
29
|
+
uses: docker/setup-qemu-action@v3.2.0
|
|
30
|
+
with:
|
|
31
|
+
platforms: arm64
|
|
32
|
+
- name: Setup Rust
|
|
33
|
+
if: runner.os != 'Linux'
|
|
34
|
+
uses: hecrj/setup-rust-action@v2
|
|
35
|
+
with:
|
|
36
|
+
profile: minimal
|
|
37
|
+
- name: Setup Rust in containers
|
|
38
|
+
if: runner.os == 'Linux'
|
|
39
|
+
run: |
|
|
40
|
+
echo "CIBW_BEFORE_ALL=curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal" >> $GITHUB_ENV
|
|
41
|
+
echo "CARGO_HOME=/usr/local/" >> $GITHUB_ENV
|
|
42
|
+
#- name: Limit parallel build jobs with QEMU builds
|
|
43
|
+
# if: runner.os == 'Linux' && matrix.arch == 'aarch64'
|
|
44
|
+
# run: echo "CARGO_BUILD_JOBS=1" >> $GITHUB_ENV
|
|
45
|
+
- name: Build wheels
|
|
46
|
+
uses: pypa/cibuildwheel@v2.19.1
|
|
47
|
+
env:
|
|
48
|
+
CIBW_ARCHS_LINUX: ${{ matrix.arch }}
|
|
49
|
+
#CIBW_CONTAINER_ENGINE: "docker; create_args: --memory=8G"
|
|
50
|
+
CIBW_ENVIRONMENT_PASS_LINUX: CARGO_BUILD_JOBS CIBW_BEFORE_ALL CARGO_HOME
|
|
51
|
+
- uses: actions/upload-artifact@v4
|
|
52
|
+
with:
|
|
53
|
+
name: wheels-${{ matrix.os }}-${{ matrix.arch }}
|
|
54
|
+
path: ./wheelhouse/*.whl
|
|
55
|
+
if-no-files-found: error
|
|
56
|
+
make_sdist:
|
|
57
|
+
name: Make SDist
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/checkout@v4
|
|
61
|
+
- name: Build SDist
|
|
62
|
+
run: pipx run build --sdist
|
|
63
|
+
- uses: actions/upload-artifact@v4
|
|
64
|
+
with:
|
|
65
|
+
name: sdist
|
|
66
|
+
path: dist/*.tar.gz
|
|
67
|
+
if-no-files-found: error
|
|
68
|
+
upload_all:
|
|
69
|
+
needs: [build_wheels, make_sdist]
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
environment:
|
|
72
|
+
name: release
|
|
73
|
+
url: https://pypi.org/p/simplefst
|
|
74
|
+
permissions:
|
|
75
|
+
contents: write
|
|
76
|
+
id-token: write
|
|
77
|
+
if: github.ref_type == 'tag'
|
|
78
|
+
steps:
|
|
79
|
+
- uses: actions/download-artifact@v4
|
|
80
|
+
with:
|
|
81
|
+
path: dist
|
|
82
|
+
merge-multiple: true
|
|
83
|
+
- name: Publish to PyPI
|
|
84
|
+
uses: pypa/gh-action-pypi-publish@v1.9.0
|
|
85
|
+
- name: Create Github Release
|
|
86
|
+
uses: actions/create-release@v1
|
|
87
|
+
id: create_release
|
|
88
|
+
with:
|
|
89
|
+
draft: false
|
|
90
|
+
prerelease: false
|
|
91
|
+
release_name: ${{ github.ref_name }}
|
|
92
|
+
tag_name: ${{ github.ref }}
|
|
93
|
+
env:
|
|
94
|
+
GITHUB_TOKEN: ${{ github.token }}
|
ducer-0.2.1/.gitignore
ADDED
ducer-0.2.1/CHANGELOG.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
All notable changes to this project will be documented in this file.
|
|
3
|
+
|
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## [Unreleased] - 2024-07-24
|
|
9
|
+
### Changed
|
|
10
|
+
-
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
-
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
-
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## [0.2.1] - 2024-07-24
|
|
20
|
+
### Fixed
|
|
21
|
+
- `Map.__getitem__` correctly raises KeyError
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- Map.get with optional default
|
|
25
|
+
- `Buffer.__len__`
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## [0.2.0] - 2024-07-23
|
|
29
|
+
### Added
|
|
30
|
+
- CI builds on Linux, MacOS, and Windows.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## [0.1.0] - 2024-07-22
|
|
34
|
+
### Added
|
|
35
|
+
- Initial public release.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
[Unreleased]: https://github.com/jfolz/ducer/compare/0.2.1...main
|
|
39
|
+
[0.2.1]: https://github.com/jfolz/ducer/compare/0.2.0...0.2.1
|
|
40
|
+
[0.2.0]: https://github.com/jfolz/ducer/compare/0.1...0.2.0
|
|
41
|
+
[0.1.0]: https://github.com/jfolz/ducer/releases/tag/0.1
|
ducer-0.2.1/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright (c) 2024 Joachim Folz
|
|
2
|
+
|
|
3
|
+
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:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
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.
|
ducer-0.2.1/MANIFEST.in
ADDED
ducer-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ducer
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Fast and compact maps and sets with Billions of keys, based on finite-state-transducers.
|
|
5
|
+
Author-email: Joachim Folz <joachim.folz@dfki.de>
|
|
6
|
+
License: Copyright (c) 2024 Joachim Folz
|
|
7
|
+
|
|
8
|
+
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:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
11
|
+
|
|
12
|
+
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.
|
|
13
|
+
|
|
14
|
+
Project-URL: Homepage, https://github.com/jfolz/ducer
|
|
15
|
+
Project-URL: Documentation, https://github.com/jfolz/ducer/blob/main/README.md
|
|
16
|
+
Project-URL: Repository, https://github.com/jfolz/ducer
|
|
17
|
+
Project-URL: Issues, https://github.com/jfolz/ducer/issues
|
|
18
|
+
Project-URL: Changelog, https://github.com/jfolz/ducer/blob/main/CHANGELOG.md
|
|
19
|
+
Keywords: big data,compression,dataset,dict,finite-state-machines,finite-state-transducers,frozen,fst,map,serialization,set,streaming
|
|
20
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
21
|
+
Classifier: Intended Audience :: Developers
|
|
22
|
+
Classifier: Intended Audience :: Science/Research
|
|
23
|
+
Classifier: Intended Audience :: Information Technology
|
|
24
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
25
|
+
Classifier: Programming Language :: Rust
|
|
26
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
32
|
+
Requires-Python: >=3.8
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
License-File: LICENSE
|
|
35
|
+
|
|
36
|
+
# Ducer
|
|
37
|
+
|
|
38
|
+
This package provides Python bindings for the excellent Rust crate
|
|
39
|
+
[fst](https://github.com/BurntSushi/fst) by Andrew Gallant.
|
|
40
|
+
`Map` and `Set` classes allow building compact representations from sorted
|
|
41
|
+
Python iterables.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Performance
|
|
46
|
+
|
|
47
|
+
Ducer maps and sets can be built and queried at millions of keys per second.
|
|
48
|
+
Consider the following example:
|
|
49
|
+
|
|
50
|
+
```Python
|
|
51
|
+
n = 1_000_000_000
|
|
52
|
+
items = ((b"%09d" % i, n-i) for i in range(n))
|
|
53
|
+
data = ducer.Map.build(items, ":memory:")
|
|
54
|
+
print(len(data))
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Creating this map takes about 4 minutes on my humble laptop,
|
|
58
|
+
which translates to almost 4 Million items per second.
|
|
59
|
+
About 3 minutes is spent in Python just creating the item tuples.
|
|
60
|
+
This is a pathological scenario, the resulting output is just 464 bytes.
|
|
61
|
+
A real-world example with 1.1 Billion keys, where the msgpacked
|
|
62
|
+
key-value pairs occupy 21 GiB (without any kind of searchability),
|
|
63
|
+
results in a 4.6 GiB file.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## Limitations
|
|
68
|
+
|
|
69
|
+
Performance is rarely free,
|
|
70
|
+
so there are some limitations you should consider before proceeding:
|
|
71
|
+
|
|
72
|
+
* Keys **must** be `bytes`
|
|
73
|
+
* Keys **must** be inserted in lexicographical order
|
|
74
|
+
* Map values **must** be positive integers less than 2^64
|
|
75
|
+
* Once built, maps and sets **cannot** be altered
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## Usage
|
|
80
|
+
|
|
81
|
+
### Building & opening
|
|
82
|
+
|
|
83
|
+
Above, we created a map with data stored in memory.
|
|
84
|
+
If you want to store your map in a file, you can also give a path instead:
|
|
85
|
+
|
|
86
|
+
```Python
|
|
87
|
+
ducer.Map.build(items, "path/to/my.map")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Building a map like this is a very memory-efficient.
|
|
91
|
+
You can also stream a map without loading all data into memory,
|
|
92
|
+
e.g., using the builtin mmap:
|
|
93
|
+
|
|
94
|
+
```Python
|
|
95
|
+
with open("path/to/my.map", "rb") as f:
|
|
96
|
+
mm = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
|
|
97
|
+
mm.madvise(mmap.MADV_RANDOM)
|
|
98
|
+
m = ducer.Map(mm)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Access
|
|
102
|
+
|
|
103
|
+
Other than being read-only, maps and sets behave the same as the builtin
|
|
104
|
+
Python `dict` and `set`.
|
|
105
|
+
Please open an issue if you find something that doesn't work.
|
|
106
|
+
For map `m: Map` and key `k: bytes`, the following all work as intended:
|
|
107
|
+
|
|
108
|
+
```Python
|
|
109
|
+
k in m # bool
|
|
110
|
+
m[k] # value except KeyError
|
|
111
|
+
m.get(k) # value or None
|
|
112
|
+
m.get(k, 0) # value or 0
|
|
113
|
+
len(m) # number of items
|
|
114
|
+
for k in m: # iterate over keys
|
|
115
|
+
pass
|
|
116
|
+
for v in m.values(): # iterate over values
|
|
117
|
+
pass
|
|
118
|
+
for k, v in m.items(): # iterate over items
|
|
119
|
+
pass
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### TODO advanced search & operations
|
ducer-0.2.1/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Ducer
|
|
2
|
+
|
|
3
|
+
This package provides Python bindings for the excellent Rust crate
|
|
4
|
+
[fst](https://github.com/BurntSushi/fst) by Andrew Gallant.
|
|
5
|
+
`Map` and `Set` classes allow building compact representations from sorted
|
|
6
|
+
Python iterables.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Performance
|
|
11
|
+
|
|
12
|
+
Ducer maps and sets can be built and queried at millions of keys per second.
|
|
13
|
+
Consider the following example:
|
|
14
|
+
|
|
15
|
+
```Python
|
|
16
|
+
n = 1_000_000_000
|
|
17
|
+
items = ((b"%09d" % i, n-i) for i in range(n))
|
|
18
|
+
data = ducer.Map.build(items, ":memory:")
|
|
19
|
+
print(len(data))
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Creating this map takes about 4 minutes on my humble laptop,
|
|
23
|
+
which translates to almost 4 Million items per second.
|
|
24
|
+
About 3 minutes is spent in Python just creating the item tuples.
|
|
25
|
+
This is a pathological scenario, the resulting output is just 464 bytes.
|
|
26
|
+
A real-world example with 1.1 Billion keys, where the msgpacked
|
|
27
|
+
key-value pairs occupy 21 GiB (without any kind of searchability),
|
|
28
|
+
results in a 4.6 GiB file.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## Limitations
|
|
33
|
+
|
|
34
|
+
Performance is rarely free,
|
|
35
|
+
so there are some limitations you should consider before proceeding:
|
|
36
|
+
|
|
37
|
+
* Keys **must** be `bytes`
|
|
38
|
+
* Keys **must** be inserted in lexicographical order
|
|
39
|
+
* Map values **must** be positive integers less than 2^64
|
|
40
|
+
* Once built, maps and sets **cannot** be altered
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
### Building & opening
|
|
47
|
+
|
|
48
|
+
Above, we created a map with data stored in memory.
|
|
49
|
+
If you want to store your map in a file, you can also give a path instead:
|
|
50
|
+
|
|
51
|
+
```Python
|
|
52
|
+
ducer.Map.build(items, "path/to/my.map")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Building a map like this is a very memory-efficient.
|
|
56
|
+
You can also stream a map without loading all data into memory,
|
|
57
|
+
e.g., using the builtin mmap:
|
|
58
|
+
|
|
59
|
+
```Python
|
|
60
|
+
with open("path/to/my.map", "rb") as f:
|
|
61
|
+
mm = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
|
|
62
|
+
mm.madvise(mmap.MADV_RANDOM)
|
|
63
|
+
m = ducer.Map(mm)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Access
|
|
67
|
+
|
|
68
|
+
Other than being read-only, maps and sets behave the same as the builtin
|
|
69
|
+
Python `dict` and `set`.
|
|
70
|
+
Please open an issue if you find something that doesn't work.
|
|
71
|
+
For map `m: Map` and key `k: bytes`, the following all work as intended:
|
|
72
|
+
|
|
73
|
+
```Python
|
|
74
|
+
k in m # bool
|
|
75
|
+
m[k] # value except KeyError
|
|
76
|
+
m.get(k) # value or None
|
|
77
|
+
m.get(k, 0) # value or 0
|
|
78
|
+
len(m) # number of items
|
|
79
|
+
for k in m: # iterate over keys
|
|
80
|
+
pass
|
|
81
|
+
for v in m.values(): # iterate over values
|
|
82
|
+
pass
|
|
83
|
+
for k, v in m.items(): # iterate over items
|
|
84
|
+
pass
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### TODO advanced search & operations
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ducer
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Fast and compact maps and sets with Billions of keys, based on finite-state-transducers.
|
|
5
|
+
Author-email: Joachim Folz <joachim.folz@dfki.de>
|
|
6
|
+
License: Copyright (c) 2024 Joachim Folz
|
|
7
|
+
|
|
8
|
+
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:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
11
|
+
|
|
12
|
+
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.
|
|
13
|
+
|
|
14
|
+
Project-URL: Homepage, https://github.com/jfolz/ducer
|
|
15
|
+
Project-URL: Documentation, https://github.com/jfolz/ducer/blob/main/README.md
|
|
16
|
+
Project-URL: Repository, https://github.com/jfolz/ducer
|
|
17
|
+
Project-URL: Issues, https://github.com/jfolz/ducer/issues
|
|
18
|
+
Project-URL: Changelog, https://github.com/jfolz/ducer/blob/main/CHANGELOG.md
|
|
19
|
+
Keywords: big data,compression,dataset,dict,finite-state-machines,finite-state-transducers,frozen,fst,map,serialization,set,streaming
|
|
20
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
21
|
+
Classifier: Intended Audience :: Developers
|
|
22
|
+
Classifier: Intended Audience :: Science/Research
|
|
23
|
+
Classifier: Intended Audience :: Information Technology
|
|
24
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
25
|
+
Classifier: Programming Language :: Rust
|
|
26
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
32
|
+
Requires-Python: >=3.8
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
License-File: LICENSE
|
|
35
|
+
|
|
36
|
+
# Ducer
|
|
37
|
+
|
|
38
|
+
This package provides Python bindings for the excellent Rust crate
|
|
39
|
+
[fst](https://github.com/BurntSushi/fst) by Andrew Gallant.
|
|
40
|
+
`Map` and `Set` classes allow building compact representations from sorted
|
|
41
|
+
Python iterables.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Performance
|
|
46
|
+
|
|
47
|
+
Ducer maps and sets can be built and queried at millions of keys per second.
|
|
48
|
+
Consider the following example:
|
|
49
|
+
|
|
50
|
+
```Python
|
|
51
|
+
n = 1_000_000_000
|
|
52
|
+
items = ((b"%09d" % i, n-i) for i in range(n))
|
|
53
|
+
data = ducer.Map.build(items, ":memory:")
|
|
54
|
+
print(len(data))
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Creating this map takes about 4 minutes on my humble laptop,
|
|
58
|
+
which translates to almost 4 Million items per second.
|
|
59
|
+
About 3 minutes is spent in Python just creating the item tuples.
|
|
60
|
+
This is a pathological scenario, the resulting output is just 464 bytes.
|
|
61
|
+
A real-world example with 1.1 Billion keys, where the msgpacked
|
|
62
|
+
key-value pairs occupy 21 GiB (without any kind of searchability),
|
|
63
|
+
results in a 4.6 GiB file.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## Limitations
|
|
68
|
+
|
|
69
|
+
Performance is rarely free,
|
|
70
|
+
so there are some limitations you should consider before proceeding:
|
|
71
|
+
|
|
72
|
+
* Keys **must** be `bytes`
|
|
73
|
+
* Keys **must** be inserted in lexicographical order
|
|
74
|
+
* Map values **must** be positive integers less than 2^64
|
|
75
|
+
* Once built, maps and sets **cannot** be altered
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## Usage
|
|
80
|
+
|
|
81
|
+
### Building & opening
|
|
82
|
+
|
|
83
|
+
Above, we created a map with data stored in memory.
|
|
84
|
+
If you want to store your map in a file, you can also give a path instead:
|
|
85
|
+
|
|
86
|
+
```Python
|
|
87
|
+
ducer.Map.build(items, "path/to/my.map")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Building a map like this is a very memory-efficient.
|
|
91
|
+
You can also stream a map without loading all data into memory,
|
|
92
|
+
e.g., using the builtin mmap:
|
|
93
|
+
|
|
94
|
+
```Python
|
|
95
|
+
with open("path/to/my.map", "rb") as f:
|
|
96
|
+
mm = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
|
|
97
|
+
mm.madvise(mmap.MADV_RANDOM)
|
|
98
|
+
m = ducer.Map(mm)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Access
|
|
102
|
+
|
|
103
|
+
Other than being read-only, maps and sets behave the same as the builtin
|
|
104
|
+
Python `dict` and `set`.
|
|
105
|
+
Please open an issue if you find something that doesn't work.
|
|
106
|
+
For map `m: Map` and key `k: bytes`, the following all work as intended:
|
|
107
|
+
|
|
108
|
+
```Python
|
|
109
|
+
k in m # bool
|
|
110
|
+
m[k] # value except KeyError
|
|
111
|
+
m.get(k) # value or None
|
|
112
|
+
m.get(k, 0) # value or 0
|
|
113
|
+
len(m) # number of items
|
|
114
|
+
for k in m: # iterate over keys
|
|
115
|
+
pass
|
|
116
|
+
for v in m.values(): # iterate over values
|
|
117
|
+
pass
|
|
118
|
+
for k, v in m.items(): # iterate over items
|
|
119
|
+
pass
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### TODO advanced search & operations
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.gitignore
|
|
2
|
+
CHANGELOG.md
|
|
3
|
+
LICENSE
|
|
4
|
+
MANIFEST.in
|
|
5
|
+
README.md
|
|
6
|
+
pyproject.toml
|
|
7
|
+
.github/workflows/build_wheels.yml
|
|
8
|
+
ducer/__init__.py
|
|
9
|
+
ducer.egg-info/PKG-INFO
|
|
10
|
+
ducer.egg-info/SOURCES.txt
|
|
11
|
+
ducer.egg-info/dependency_links.txt
|
|
12
|
+
ducer.egg-info/top_level.txt
|
|
13
|
+
rust/Cargo.lock
|
|
14
|
+
rust/Cargo.toml
|
|
15
|
+
rust/src/automaton.rs
|
|
16
|
+
rust/src/buffer.rs
|
|
17
|
+
rust/src/encoding.rs
|
|
18
|
+
rust/src/lib.rs
|
|
19
|
+
rust/src/map.rs
|
|
20
|
+
rust/src/set.rs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ducer
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ducer"
|
|
3
|
+
description = "Fast and compact maps and sets with Billions of keys, based on finite-state-transducers."
|
|
4
|
+
readme = { file = "README.md", content-type = "text/markdown" }
|
|
5
|
+
authors = [{ name = "Joachim Folz", email = "joachim.folz@dfki.de" }]
|
|
6
|
+
classifiers = [
|
|
7
|
+
"Development Status :: 5 - Production/Stable",
|
|
8
|
+
"Intended Audience :: Developers",
|
|
9
|
+
"Intended Audience :: Science/Research",
|
|
10
|
+
"Intended Audience :: Information Technology",
|
|
11
|
+
"License :: OSI Approved :: MIT License",
|
|
12
|
+
"Programming Language :: Rust",
|
|
13
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
14
|
+
"Programming Language :: Python :: 3.8",
|
|
15
|
+
"Programming Language :: Python :: 3.9",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
]
|
|
20
|
+
keywords = [
|
|
21
|
+
"big data",
|
|
22
|
+
"compression",
|
|
23
|
+
"dataset",
|
|
24
|
+
"dict",
|
|
25
|
+
"finite-state-machines",
|
|
26
|
+
"finite-state-transducers",
|
|
27
|
+
"frozen",
|
|
28
|
+
"fst",
|
|
29
|
+
"map",
|
|
30
|
+
"serialization",
|
|
31
|
+
"set",
|
|
32
|
+
"streaming",
|
|
33
|
+
]
|
|
34
|
+
requires-python = ">=3.8"
|
|
35
|
+
dynamic = ["version"]
|
|
36
|
+
license = { file = "LICENSE" }
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/jfolz/ducer"
|
|
40
|
+
Documentation = "https://github.com/jfolz/ducer/blob/main/README.md"
|
|
41
|
+
Repository = "https://github.com/jfolz/ducer"
|
|
42
|
+
Issues = "https://github.com/jfolz/ducer/issues"
|
|
43
|
+
Changelog = "https://github.com/jfolz/ducer/blob/main/CHANGELOG.md"
|
|
44
|
+
|
|
45
|
+
[tool.setuptools_scm]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools]
|
|
48
|
+
packages = ["ducer"]
|
|
49
|
+
|
|
50
|
+
[build-system]
|
|
51
|
+
requires = [
|
|
52
|
+
"setuptools>=71.1.0",
|
|
53
|
+
"setuptools-rust>=1.9.0",
|
|
54
|
+
"setuptools_scm>=8.1.0",
|
|
55
|
+
]
|
|
56
|
+
build-backend = "setuptools.build_meta"
|
|
57
|
+
|
|
58
|
+
[[tool.setuptools-rust.ext-modules]]
|
|
59
|
+
target = "ducer._fst"
|
|
60
|
+
path = "rust/Cargo.toml"
|
|
61
|
+
binding = "PyO3"
|
|
62
|
+
args = ["--profile", "prod"]
|
|
63
|
+
|
|
64
|
+
[tool.cibuildwheel]
|
|
65
|
+
skip = "*_i686 pp* *musllinux* *-win32"
|
|
66
|
+
build-verbosity = 1
|
|
67
|
+
#before-all = "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal"
|
|
68
|
+
#before-build = "cargo clean --manifest-path rust/Cargo.toml"
|
|
69
|
+
build-frontend = "build"
|
|
70
|
+
#test-requires = "pytest>=5.3.2 numpy Pillow>=7.0.0"
|
|
71
|
+
#test-command = "pytest {project}/test -vv"
|