completr 0.2.0__tar.gz → 0.2.2__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.
- {completr-0.2.0 → completr-0.2.2}/Cargo.lock +3 -3
- {completr-0.2.0 → completr-0.2.2}/Cargo.toml +1 -1
- {completr-0.2.0 → completr-0.2.2}/PKG-INFO +1 -1
- {completr-0.2.0 → completr-0.2.2}/crates/completr/Cargo.toml +1 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/client.rs +6 -1
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/database.rs +11 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/store.rs +153 -2
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/Cargo.toml +3 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/python/completr/__init__.pyi +1 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/python/completr/completr.pyi +8 -1
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/src/lib.rs +15 -8
- completr-0.2.2/crates/completr-py/tests/test_allocators.py +20 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/tests/test_database.py +13 -0
- {completr-0.2.0 → completr-0.2.2}/LICENSE +0 -0
- {completr-0.2.0 → completr-0.2.2}/README.md +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/README.md +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/blocked.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/codec.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/dict.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/document.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/elias_fano.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/engine.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/fuzzy.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/highlight.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/hybrid.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/inbox.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/index.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/lib.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/postings.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/search.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/segment/merge.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/segment/titles.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/segment/words.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/segment.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/staged.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/text.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/trie.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr/src/vectors.rs +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/examples/layers.py +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/examples/live_updates.py +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/examples/quickstart.py +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/python/completr/__init__.py +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/python/completr/_async.py +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/python/completr/_async.pyi +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/python/completr/_errors.py +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/python/completr/_types.py +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/python/completr/py.typed +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/tests/test_api.py +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/tests/test_client.py +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/tests/test_completr.py +0 -0
- {completr-0.2.0 → completr-0.2.2}/crates/completr-py/tests/test_vectors.py +0 -0
- {completr-0.2.0 → completr-0.2.2}/pyproject.toml +0 -0
|
@@ -735,7 +735,7 @@ dependencies = [
|
|
|
735
735
|
|
|
736
736
|
[[package]]
|
|
737
737
|
name = "completr"
|
|
738
|
-
version = "0.2.
|
|
738
|
+
version = "0.2.2"
|
|
739
739
|
dependencies = [
|
|
740
740
|
"arc-swap",
|
|
741
741
|
"async-trait",
|
|
@@ -765,7 +765,7 @@ dependencies = [
|
|
|
765
765
|
|
|
766
766
|
[[package]]
|
|
767
767
|
name = "completr-cli"
|
|
768
|
-
version = "0.2.
|
|
768
|
+
version = "0.2.2"
|
|
769
769
|
dependencies = [
|
|
770
770
|
"clap",
|
|
771
771
|
"completr",
|
|
@@ -776,7 +776,7 @@ dependencies = [
|
|
|
776
776
|
|
|
777
777
|
[[package]]
|
|
778
778
|
name = "completr-py"
|
|
779
|
-
version = "0.2.
|
|
779
|
+
version = "0.2.2"
|
|
780
780
|
dependencies = [
|
|
781
781
|
"completr",
|
|
782
782
|
"mimalloc",
|
|
@@ -53,6 +53,7 @@ aws-credentials = ["aws", "dep:aws-config", "dep:aws-credential-types", "dep:asy
|
|
|
53
53
|
[dev-dependencies]
|
|
54
54
|
turbovec = "1.0.0"
|
|
55
55
|
tempfile = "3"
|
|
56
|
+
async-trait = "0.1"
|
|
56
57
|
xxhash-rust = { version = "0.8", features = ["xxh3"] }
|
|
57
58
|
serde_json = { version = "1", features = ["float_roundtrip"] }
|
|
58
59
|
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
|
@@ -28,6 +28,8 @@ pub struct ConnectOptions {
|
|
|
28
28
|
pub cache_dir: Option<PathBuf>,
|
|
29
29
|
/// Storage options, such as credentials or a region, passed to the object store.
|
|
30
30
|
pub storage: Vec<(String, String)>,
|
|
31
|
+
/// Tags on every object written, e.g. to select bucket lifecycle rules.
|
|
32
|
+
pub tags: Vec<(String, String)>,
|
|
31
33
|
pub build: BuildOptions,
|
|
32
34
|
pub index: IndexOptions,
|
|
33
35
|
}
|
|
@@ -38,6 +40,7 @@ impl Default for ConnectOptions {
|
|
|
38
40
|
sync_every: Some(Duration::from_secs(5)),
|
|
39
41
|
cache_dir: None,
|
|
40
42
|
storage: Vec::new(),
|
|
43
|
+
tags: Vec::new(),
|
|
41
44
|
build: BuildOptions::default(),
|
|
42
45
|
index: IndexOptions::default(),
|
|
43
46
|
}
|
|
@@ -48,6 +51,7 @@ crate::setters!(ConnectOptions {
|
|
|
48
51
|
sync_every: Option<Duration>,
|
|
49
52
|
cache_dir: Option<PathBuf>,
|
|
50
53
|
storage: Vec<(String, String)>,
|
|
54
|
+
tags: Vec<(String, String)>,
|
|
51
55
|
build: BuildOptions,
|
|
52
56
|
index: IndexOptions,
|
|
53
57
|
});
|
|
@@ -218,7 +222,8 @@ const LEGACY_SETTINGS: &str = "collection/";
|
|
|
218
222
|
pub async fn connect(url: &str, options: ConnectOptions) -> Result<Client, Error> {
|
|
219
223
|
let mut database = Database::open(url, options.storage)
|
|
220
224
|
.await?
|
|
221
|
-
.with_build_options(options.build)
|
|
225
|
+
.with_build_options(options.build)
|
|
226
|
+
.with_tags(options.tags);
|
|
222
227
|
if let Some(dir) = &options.cache_dir {
|
|
223
228
|
database = database.with_cache_dir(dir)?;
|
|
224
229
|
}
|
|
@@ -196,6 +196,17 @@ impl Database {
|
|
|
196
196
|
Ok(Self::new(Store::open_with(url, options).await?))
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/// Tags every object this database writes, e.g. to select bucket lifecycle rules.
|
|
200
|
+
pub fn with_tags<K: AsRef<str>, V: AsRef<str>>(
|
|
201
|
+
self,
|
|
202
|
+
tags: impl IntoIterator<Item = (K, V)>,
|
|
203
|
+
) -> Self {
|
|
204
|
+
Self {
|
|
205
|
+
store: self.store.with_tags(tags),
|
|
206
|
+
..self
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
199
210
|
/// Caches downloaded segments in `dir` and memory-maps them.
|
|
200
211
|
pub fn with_cache_dir(self, dir: impl AsRef<std::path::Path>) -> Result<Self, Error> {
|
|
201
212
|
Ok(Self {
|
|
@@ -7,7 +7,7 @@ use futures::{StreamExt, TryStreamExt};
|
|
|
7
7
|
use object_store::local::LocalFileSystem;
|
|
8
8
|
use object_store::memory::InMemory;
|
|
9
9
|
use object_store::path::Path;
|
|
10
|
-
use object_store::{ObjectStore, ObjectStoreExt, PutMode, PutOptions, PutPayload};
|
|
10
|
+
use object_store::{ObjectStore, ObjectStoreExt, PutMode, PutOptions, PutPayload, TagSet};
|
|
11
11
|
use url::Url;
|
|
12
12
|
|
|
13
13
|
use crate::{Error, Segment};
|
|
@@ -29,6 +29,8 @@ pub struct Store {
|
|
|
29
29
|
/// Where remote segments are downloaded to and memory-mapped from, per store URL.
|
|
30
30
|
cache: Option<PathBuf>,
|
|
31
31
|
namespace: String,
|
|
32
|
+
/// Tags on every object written, e.g. to select bucket lifecycle rules.
|
|
33
|
+
tags: TagSet,
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
impl Store {
|
|
@@ -80,6 +82,7 @@ impl Store {
|
|
|
80
82
|
local_root: None,
|
|
81
83
|
cache: None,
|
|
82
84
|
namespace,
|
|
85
|
+
tags: TagSet::default(),
|
|
83
86
|
})
|
|
84
87
|
}
|
|
85
88
|
|
|
@@ -93,6 +96,7 @@ impl Store {
|
|
|
93
96
|
local_root: Some(root),
|
|
94
97
|
cache: None,
|
|
95
98
|
namespace: String::new(),
|
|
99
|
+
tags: TagSet::default(),
|
|
96
100
|
})
|
|
97
101
|
}
|
|
98
102
|
|
|
@@ -115,9 +119,31 @@ impl Store {
|
|
|
115
119
|
.to_vec())
|
|
116
120
|
}
|
|
117
121
|
|
|
122
|
+
/// Tags every object this store writes from now on; S3 and Azure keep them, other stores ignore them.
|
|
123
|
+
pub fn with_tags<K: AsRef<str>, V: AsRef<str>>(
|
|
124
|
+
mut self,
|
|
125
|
+
tags: impl IntoIterator<Item = (K, V)>,
|
|
126
|
+
) -> Self {
|
|
127
|
+
let mut set = TagSet::default();
|
|
128
|
+
for (key, value) in tags {
|
|
129
|
+
set.push(key.as_ref(), value.as_ref());
|
|
130
|
+
}
|
|
131
|
+
self.tags = set;
|
|
132
|
+
self
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// The tags written with every object, URL-encoded as S3 takes them.
|
|
136
|
+
pub fn tags(&self) -> &str {
|
|
137
|
+
self.tags.encoded()
|
|
138
|
+
}
|
|
139
|
+
|
|
118
140
|
pub async fn put(&self, key: &str, data: Vec<u8>) -> Result<(), Error> {
|
|
141
|
+
let options = PutOptions {
|
|
142
|
+
tags: self.tags.clone(),
|
|
143
|
+
..PutOptions::default()
|
|
144
|
+
};
|
|
119
145
|
self.inner
|
|
120
|
-
.
|
|
146
|
+
.put_opts(&self.path(key), PutPayload::from(data), options)
|
|
121
147
|
.await?;
|
|
122
148
|
Ok(())
|
|
123
149
|
}
|
|
@@ -127,6 +153,7 @@ impl Store {
|
|
|
127
153
|
pub async fn put_if_absent(&self, key: &str, data: Vec<u8>) -> Result<bool, Error> {
|
|
128
154
|
let options = PutOptions {
|
|
129
155
|
mode: PutMode::Create,
|
|
156
|
+
tags: self.tags.clone(),
|
|
130
157
|
..PutOptions::default()
|
|
131
158
|
};
|
|
132
159
|
match self
|
|
@@ -431,6 +458,13 @@ impl BlockingStore {
|
|
|
431
458
|
self.0.with_cache_dir(dir).map(Self)
|
|
432
459
|
}
|
|
433
460
|
|
|
461
|
+
pub fn with_tags<K: AsRef<str>, V: AsRef<str>>(
|
|
462
|
+
self,
|
|
463
|
+
tags: impl IntoIterator<Item = (K, V)>,
|
|
464
|
+
) -> Self {
|
|
465
|
+
Self(self.0.with_tags(tags))
|
|
466
|
+
}
|
|
467
|
+
|
|
434
468
|
pub fn prune_cache<'a>(&self, keep: impl IntoIterator<Item = &'a str>) -> Result<usize, Error> {
|
|
435
469
|
self.0.prune_cache(keep)
|
|
436
470
|
}
|
|
@@ -557,6 +591,123 @@ mod tests {
|
|
|
557
591
|
use super::*;
|
|
558
592
|
use crate::Document;
|
|
559
593
|
|
|
594
|
+
/// An in-memory store that records the tags each write carries.
|
|
595
|
+
#[derive(Debug, Default)]
|
|
596
|
+
struct Recording {
|
|
597
|
+
inner: InMemory,
|
|
598
|
+
tags: std::sync::Mutex<Vec<String>>,
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
impl std::fmt::Display for Recording {
|
|
602
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
603
|
+
write!(f, "Recording")
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
#[async_trait::async_trait]
|
|
608
|
+
impl ObjectStore for Recording {
|
|
609
|
+
async fn put_opts(
|
|
610
|
+
&self,
|
|
611
|
+
location: &Path,
|
|
612
|
+
payload: PutPayload,
|
|
613
|
+
opts: PutOptions,
|
|
614
|
+
) -> object_store::Result<object_store::PutResult> {
|
|
615
|
+
self.tags
|
|
616
|
+
.lock()
|
|
617
|
+
.unwrap()
|
|
618
|
+
.push(opts.tags.encoded().to_owned());
|
|
619
|
+
self.inner.put_opts(location, payload, opts).await
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
async fn put_multipart_opts(
|
|
623
|
+
&self,
|
|
624
|
+
location: &Path,
|
|
625
|
+
opts: object_store::PutMultipartOptions,
|
|
626
|
+
) -> object_store::Result<Box<dyn object_store::MultipartUpload>> {
|
|
627
|
+
self.inner.put_multipart_opts(location, opts).await
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
async fn get_opts(
|
|
631
|
+
&self,
|
|
632
|
+
location: &Path,
|
|
633
|
+
options: object_store::GetOptions,
|
|
634
|
+
) -> object_store::Result<object_store::GetResult> {
|
|
635
|
+
self.inner.get_opts(location, options).await
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
fn delete_stream(
|
|
639
|
+
&self,
|
|
640
|
+
locations: futures::stream::BoxStream<'static, object_store::Result<Path>>,
|
|
641
|
+
) -> futures::stream::BoxStream<'static, object_store::Result<Path>> {
|
|
642
|
+
self.inner.delete_stream(locations)
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
fn list(
|
|
646
|
+
&self,
|
|
647
|
+
prefix: Option<&Path>,
|
|
648
|
+
) -> futures::stream::BoxStream<'static, object_store::Result<object_store::ObjectMeta>>
|
|
649
|
+
{
|
|
650
|
+
self.inner.list(prefix)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async fn list_with_delimiter(
|
|
654
|
+
&self,
|
|
655
|
+
prefix: Option<&Path>,
|
|
656
|
+
) -> object_store::Result<object_store::ListResult> {
|
|
657
|
+
self.inner.list_with_delimiter(prefix).await
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
async fn copy_opts(
|
|
661
|
+
&self,
|
|
662
|
+
from: &Path,
|
|
663
|
+
to: &Path,
|
|
664
|
+
options: object_store::CopyOptions,
|
|
665
|
+
) -> object_store::Result<()> {
|
|
666
|
+
self.inner.copy_opts(from, to, options).await
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
#[tokio::test]
|
|
671
|
+
async fn every_write_carries_the_tags() {
|
|
672
|
+
let recording = Arc::new(Recording::default());
|
|
673
|
+
let store = Store {
|
|
674
|
+
inner: recording.clone(),
|
|
675
|
+
prefix: Path::default(),
|
|
676
|
+
local_root: None,
|
|
677
|
+
cache: None,
|
|
678
|
+
namespace: "recording".into(),
|
|
679
|
+
tags: TagSet::default(),
|
|
680
|
+
}
|
|
681
|
+
.with_tags([
|
|
682
|
+
("LifecycleRule", "KeepForever"),
|
|
683
|
+
("team", "search & ranking"),
|
|
684
|
+
]);
|
|
685
|
+
assert_eq!(
|
|
686
|
+
store.tags(),
|
|
687
|
+
"LifecycleRule=KeepForever&team=search+%26+ranking"
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
let database = crate::Database::new(store);
|
|
691
|
+
let mut txn = database.begin().await.unwrap();
|
|
692
|
+
txn.append_documents("songs", [Document::new(1, "Rust", 0.5)], [])
|
|
693
|
+
.unwrap();
|
|
694
|
+
txn.commit().await.unwrap();
|
|
695
|
+
let mut changes = crate::ChangeSet::new();
|
|
696
|
+
changes.upsert("songs", [Document::new(2, "Go", 0.5)]);
|
|
697
|
+
database.submit(changes).await.unwrap();
|
|
698
|
+
database
|
|
699
|
+
.acquire_lease("ingestor", "me", std::time::Duration::from_secs(60))
|
|
700
|
+
.await
|
|
701
|
+
.unwrap();
|
|
702
|
+
|
|
703
|
+
let written = recording.tags.lock().unwrap().clone();
|
|
704
|
+
// A segment, a version, a change set and a lease.
|
|
705
|
+
assert!(written.len() >= 4, "{written:?}");
|
|
706
|
+
assert!(written
|
|
707
|
+
.iter()
|
|
708
|
+
.all(|t| t == "LifecycleRule=KeepForever&team=search+%26+ranking"));
|
|
709
|
+
}
|
|
710
|
+
|
|
560
711
|
#[test]
|
|
561
712
|
fn local_and_memory_round_trip() {
|
|
562
713
|
let dir = tempfile::tempdir().unwrap();
|
|
@@ -18,4 +18,7 @@ pyo3 = { version = "0.29", features = ["extension-module", "abi3-py311"] }
|
|
|
18
18
|
pyo3-log = "0.13"
|
|
19
19
|
tracing = { version = "0.1", default-features = false, features = ["log"] }
|
|
20
20
|
completr_rs = { package = "completr", path = "../completr", features = ["aws-credentials", "gcp", "azure"] }
|
|
21
|
+
|
|
22
|
+
# Not on macOS, where every mimalloc in a process shares fixed thread slots and pyarrow ships one.
|
|
23
|
+
[target.'cfg(target_os = "linux")'.dependencies]
|
|
21
24
|
mimalloc = { version = "0.1", default-features = false }
|
|
@@ -311,7 +311,12 @@ class Engine:
|
|
|
311
311
|
|
|
312
312
|
class Store:
|
|
313
313
|
def __init__(
|
|
314
|
-
self,
|
|
314
|
+
self,
|
|
315
|
+
url: str,
|
|
316
|
+
options: Mapping[str, str] | None = None,
|
|
317
|
+
cache_dir: str | PathLike[str] | None = None,
|
|
318
|
+
*,
|
|
319
|
+
tags: Mapping[str, str] | None = None,
|
|
315
320
|
) -> None: ...
|
|
316
321
|
def prune_cache(self, keep: Sequence[str]) -> int: ...
|
|
317
322
|
def get(self, key: str) -> bytes: ...
|
|
@@ -394,6 +399,7 @@ class Database:
|
|
|
394
399
|
options: Mapping[str, str] | None = None,
|
|
395
400
|
cache_dir: str | PathLike[str] | None = None,
|
|
396
401
|
*,
|
|
402
|
+
tags: Mapping[str, str] | None = None,
|
|
397
403
|
min_word_chars: int = 3,
|
|
398
404
|
max_edit_distance: int = 2,
|
|
399
405
|
fuzzy_prefix_chars: int = 7,
|
|
@@ -564,6 +570,7 @@ class Client:
|
|
|
564
570
|
sync_every: float | None = 5.0,
|
|
565
571
|
cache_dir: str | PathLike[str] | None = None,
|
|
566
572
|
options: Mapping[str, str] | None = None,
|
|
573
|
+
tags: Mapping[str, str] | None = None,
|
|
567
574
|
min_word_chars: int = 3,
|
|
568
575
|
max_edit_distance: int = 2,
|
|
569
576
|
fuzzy_prefix_chars: int = 7,
|
|
@@ -10,7 +10,8 @@ use pyo3::prelude::*;
|
|
|
10
10
|
use pyo3::sync::PyOnceLock;
|
|
11
11
|
use pyo3::types::{PyBytes, PyDict, PyList, PyString, PyType};
|
|
12
12
|
|
|
13
|
-
// Returns freed memory to the system, which
|
|
13
|
+
// Returns freed memory to the system, which glibc keeps after a build.
|
|
14
|
+
#[cfg(target_os = "linux")]
|
|
14
15
|
#[global_allocator]
|
|
15
16
|
static ALLOCATOR: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
|
16
17
|
|
|
@@ -1542,17 +1543,19 @@ struct Store(completr_rs::BlockingStore);
|
|
|
1542
1543
|
#[pymethods]
|
|
1543
1544
|
impl Store {
|
|
1544
1545
|
#[new]
|
|
1545
|
-
#[pyo3(signature = (url, options = None, cache_dir = None))]
|
|
1546
|
+
#[pyo3(signature = (url, options = None, cache_dir = None, *, tags = None))]
|
|
1546
1547
|
fn new(
|
|
1547
1548
|
py: Python<'_>,
|
|
1548
1549
|
url: &str,
|
|
1549
1550
|
options: Option<HashMap<String, String>>,
|
|
1550
1551
|
cache_dir: Option<PathBuf>,
|
|
1552
|
+
tags: Option<HashMap<String, String>>,
|
|
1551
1553
|
) -> PyResult<Self> {
|
|
1552
1554
|
let options = options.unwrap_or_default();
|
|
1553
1555
|
let store = py
|
|
1554
1556
|
.detach(|| completr_rs::BlockingStore::open_with(url, options))
|
|
1555
|
-
.map_err(to_py_err)
|
|
1557
|
+
.map_err(to_py_err)?
|
|
1558
|
+
.with_tags(tags.unwrap_or_default());
|
|
1556
1559
|
let store = match cache_dir {
|
|
1557
1560
|
Some(dir) => store.with_cache_dir(dir).map_err(to_py_err)?,
|
|
1558
1561
|
None => store,
|
|
@@ -1636,11 +1639,11 @@ impl Database {
|
|
|
1636
1639
|
|
|
1637
1640
|
#[pymethods]
|
|
1638
1641
|
impl Database {
|
|
1639
|
-
/// `cache_dir` keeps downloaded segments on local disk, memory-mapped
|
|
1640
|
-
/// apply to segments this database builds from documents.
|
|
1642
|
+
/// `cache_dir` keeps downloaded segments on local disk, memory-mapped; `tags` go on every object
|
|
1643
|
+
/// written. The other keyword settings apply to segments this database builds from documents.
|
|
1641
1644
|
#[new]
|
|
1642
1645
|
#[pyo3(signature = (
|
|
1643
|
-
url, options = None, cache_dir = None, *,
|
|
1646
|
+
url, options = None, cache_dir = None, *, tags = None,
|
|
1644
1647
|
min_word_chars = 3, max_edit_distance = 2, fuzzy_prefix_chars = 7, vector_bits = 4, compact_keys = false, build_threads = 1,
|
|
1645
1648
|
))]
|
|
1646
1649
|
#[allow(clippy::too_many_arguments)]
|
|
@@ -1649,6 +1652,7 @@ impl Database {
|
|
|
1649
1652
|
url: &str,
|
|
1650
1653
|
options: Option<HashMap<String, String>>,
|
|
1651
1654
|
cache_dir: Option<PathBuf>,
|
|
1655
|
+
tags: Option<HashMap<String, String>>,
|
|
1652
1656
|
min_word_chars: u8,
|
|
1653
1657
|
max_edit_distance: u8,
|
|
1654
1658
|
fuzzy_prefix_chars: u8,
|
|
@@ -1659,7 +1663,8 @@ impl Database {
|
|
|
1659
1663
|
let options = options.unwrap_or_default();
|
|
1660
1664
|
let database = py
|
|
1661
1665
|
.detach(|| completr_rs::block_on(completr_rs::Database::open(url, options)))
|
|
1662
|
-
.map_err(to_py_err)
|
|
1666
|
+
.map_err(to_py_err)?
|
|
1667
|
+
.with_tags(tags.unwrap_or_default());
|
|
1663
1668
|
let database = match cache_dir {
|
|
1664
1669
|
Some(dir) => database.with_cache_dir(dir).map_err(to_py_err)?,
|
|
1665
1670
|
None => database,
|
|
@@ -2518,7 +2523,7 @@ impl Client {
|
|
|
2518
2523
|
/// `None` syncs only on `sync()`. The keyword settings apply to segments this client builds.
|
|
2519
2524
|
#[new]
|
|
2520
2525
|
#[pyo3(signature = (
|
|
2521
|
-
url = "memory://", *, sync_every = Some(5.0), cache_dir = None, options = None,
|
|
2526
|
+
url = "memory://", *, sync_every = Some(5.0), cache_dir = None, options = None, tags = None,
|
|
2522
2527
|
min_word_chars = 3, max_edit_distance = 2, fuzzy_prefix_chars = 7, vector_bits = 4, compact_keys = false, build_threads = 1,
|
|
2523
2528
|
))]
|
|
2524
2529
|
#[allow(clippy::too_many_arguments)]
|
|
@@ -2528,6 +2533,7 @@ impl Client {
|
|
|
2528
2533
|
sync_every: Option<f64>,
|
|
2529
2534
|
cache_dir: Option<PathBuf>,
|
|
2530
2535
|
options: Option<HashMap<String, String>>,
|
|
2536
|
+
tags: Option<HashMap<String, String>>,
|
|
2531
2537
|
min_word_chars: u8,
|
|
2532
2538
|
max_edit_distance: u8,
|
|
2533
2539
|
fuzzy_prefix_chars: u8,
|
|
@@ -2547,6 +2553,7 @@ impl Client {
|
|
|
2547
2553
|
.sync_every(sync_every.map(|s| Duration::from_secs_f64(s.max(0.001))))
|
|
2548
2554
|
.cache_dir(cache_dir)
|
|
2549
2555
|
.storage(options.unwrap_or_default().into_iter().collect())
|
|
2556
|
+
.tags(tags.unwrap_or_default().into_iter().collect())
|
|
2550
2557
|
.build(build);
|
|
2551
2558
|
let client = py
|
|
2552
2559
|
.detach(|| completr_rs::block_on(completr_rs::connect(url, connect)))
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
import completr
|
|
4
|
+
|
|
5
|
+
pyarrow = pytest.importorskip("pyarrow")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_completes_alongside_pyarrow_allocations(tmp_path):
|
|
9
|
+
pool = pyarrow.default_memory_pool()
|
|
10
|
+
table = pyarrow.table({"text": [f"skill {i}" for i in range(10_000)]})
|
|
11
|
+
assert pool.bytes_allocated() > 0
|
|
12
|
+
db = completr.connect(str(tmp_path / "db"))
|
|
13
|
+
with db.namespace("en-US").begin() as txn:
|
|
14
|
+
txn.overwrite("owner", [{"id": f"s{i}", "text": f"Skill {i}", "popularity": 0.5} for i in range(2_000)])
|
|
15
|
+
namespace = completr.connect(str(tmp_path / "db")).engine(sync_every=None).namespace("en-US")
|
|
16
|
+
for _ in range(200):
|
|
17
|
+
hits = namespace.complete("skill 1", ["owner"])
|
|
18
|
+
assert [str(h.id) for h in hits]
|
|
19
|
+
table = table.slice(1)
|
|
20
|
+
assert len(table) > 0
|
|
@@ -3,6 +3,8 @@ import time
|
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
|
+
import completr
|
|
7
|
+
|
|
6
8
|
from completr import ConflictError
|
|
7
9
|
from completr import ChangeSet, Database, Engine, Replica, Store, Ingestor
|
|
8
10
|
|
|
@@ -184,3 +186,14 @@ def test_threaded_queries_during_updates(database):
|
|
|
184
186
|
assert len({id(index) for index, _, _ in samples}) > 1
|
|
185
187
|
for index, query, result in samples:
|
|
186
188
|
assert [(h.id, h.score, h.kind) for h in index.complete(query, 10)] == result
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_tags_are_accepted_everywhere_and_ignored_by_local_stores(tmp_path):
|
|
192
|
+
tags = {"LifecycleRule": "KeepForever"}
|
|
193
|
+
db = completr.connect(str(tmp_path / "db"), tags=tags)
|
|
194
|
+
with db.begin() as txn:
|
|
195
|
+
txn.append("songs", DOCS[:2])
|
|
196
|
+
assert db.index_names() == ["songs"]
|
|
197
|
+
completr.Store(str(tmp_path / "store"), tags=tags).put("a", b"x")
|
|
198
|
+
client = completr.Client(str(tmp_path / "client"), sync_every=None, tags=tags)
|
|
199
|
+
client.create_collection("songs").add(DOCS[:2])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|