Skip to content

Commit

Permalink
Make structs subclassable
Browse files Browse the repository at this point in the history
  • Loading branch information
Sh1nku committed Jun 29, 2024
1 parent b5dcd5d commit abf0452
Show file tree
Hide file tree
Showing 16 changed files with 153 additions and 49 deletions.
85 changes: 71 additions & 14 deletions wrappers/python/README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
# Solrstice: A Solr 8+ Client for Rust and Python

Solrstice is a solr client library written in rust. With this wrapper you can use it in python.

Both asyncio and blocking clients are provided. All apis have type hints.
Documentation can be found at [sh1nku.github.io/solrstice/python](https://sh1nku.github.io/solrstice/python)

## Features

* Config API
* Collection API
* Alias API
* Select Documents
* Grouping Component Query
* DefTypes (lucene, dismax, edismax)
* Facet Counts (Query, Field, Pivot)
* Json Facet (Query, Stat, Terms, Nested)
* Grouping Component Query
* DefTypes (lucene, dismax, edismax)
* Facet Counts (Query, Field, Pivot)
* Json Facet (Query, Stat, Terms, Nested)
* Indexing Documents
* Deleting Documents

## Installation

```bash
pip install solrstice
```

## Basic Usage

### Async

```python
import asyncio

Expand All @@ -32,25 +40,28 @@ from solrstice.queries import DeleteQuery, SelectQuery, UpdateQuery
context = SolrServerContext(SolrSingleServerHost('localhost:8983'), SolrBasicAuth('solr', 'SolrRocks'))
client = AsyncSolrCloudClient(context)


async def main():
# Create config and collection
await client.upload_config('example_config', 'path/to/config')
await client.create_collection('example_collection', 'example_config', shards=1, replication_factor=1)

# Index a document
await client.index(UpdateQuery(), 'example_collection', [{'id': 'example_document', 'title': 'Example document'}])

# Search for the document
response = await client.select(SelectQuery(fq=['title:Example document']), 'example_collection')
docs = response.get_docs_response().get_docs()

# Delete the document
await client.delete(DeleteQuery(ids=['example_document']), 'example_collection')


asyncio.run(main())
```

### Blocking

```python
from solrstice.auth import SolrBasicAuth
from solrstice.clients import BlockingSolrCloudClient
Expand All @@ -77,15 +88,19 @@ client.delete(DeleteQuery(ids=['example_document']), 'example_collection')
```

## Grouping component

### Field grouping

```python
group_builder = GroupingComponent(fields=["age"], limit=10)
select_builder = SelectQuery(fq=["age:[* TO *]"], grouping=group_builder)
groups = await client.select(select_builder, "example_collection").get_groups()
age_group = groups["age"]
docs = age_group.get_field_result()
```

### Query grouping

```python
group_builder = GroupingComponent(queries=["age:[0 TO 59]", "age:[60 TO *]"], limit=10)
select_builder = SelectQuery(fq=["age:[* TO *]"], grouping=group_builder)
Expand All @@ -94,38 +109,50 @@ age_group = groups["age:[0 TO 59]"]
group = age_group.get_query_result()
docs = group.get_docs()
```

## Query parsers

### Lucene

```python
query_parser = LuceneQuery(df="population")
select_builder = SelectQuery(q="outdoors", def_type=query_parser)
await client.select(select_builder, "example_collection")
docs = response.get_docs_response().get_docs()
```

### Dismax

```python
query_parser = DismaxQuery(qf="interests^20", bq=["interests:cars^20"])
select_builder = SelectQuery(q="outdoors", def_type=query_parser)
await client.select(select_builder, "example_collection")
docs = response.get_docs_response().get_docs()
```

### Edismax

```python
query_parser = EdismaxQuery(qf="interests^20", bq=["interests:cars^20"])
select_builder = SelectQuery(q="outdoors", def_type=query_parser)
await client.select(select_builder, "example_collection")
docs = response.get_docs_response().get_docs()
```

## FacetSet Component

### Pivot facet

```python
select_builder = SelectQuery(facet_set=FacetSetComponent(pivots=PivotFacetComponent(["interests,age"])))
await client.select(select_builder, "example_collection")
facets = response.get_facet_set()
pivots = facets.get_pivots()
interests_age = pivot.get("interests,age")
```

### Field facet

```python
facet_set = FacetSetComponent(fields=FieldFacetComponent(fields=[FieldFacetEntry("age")]))
select_builder = SelectQuery(facet_set=facet_set)
Expand All @@ -134,28 +161,35 @@ facets = response.get_facet_set()
fields = facets.get_fields()
age = fields.get("age")
```

### Query facet

```python
select_builder = SelectQuery(facet_set=FacetSetComponent(queries=["age:[0 TO 59]"]))
response = await client.select(select_builder, name)
facets = response.get_facet_set()
queries = facets.get_queries()
query = queries.get("age:[0 TO 59]")
```

## Json Facet Component

### Query

```python
select_builder = SelectQuery(
json_facet=JsonFacetComponent(
facets={"below_60": JsonQueryFacet("age:[0 TO 59]")}
)
json_facet=JsonFacetComponent(
facets={"below_60": JsonQueryFacet("age:[0 TO 59]")}
)
)
response = await client.select(select_builder, "example_collection"")
facets = response.get_json_facets()
below_60 = facets.get_nested_facets().get("below_60")
assert below_60.get_count() == 4
```

### Stat

```python
select_builder = SelectQuery(
json_facet=JsonFacetComponent(
Expand All @@ -167,7 +201,9 @@ facets = response.get_json_facets()
total_people = facets.get_flat_facets().get("total_people")
assert total_people == 1000
```

### Terms

```python
select_builder = SelectQuery(
json_facet=JsonFacetComponent(facets={"age": JsonTermsFacet("age")})
Expand All @@ -177,7 +213,9 @@ facets = response.get_json_facets()
age_buckets = facets.get_nested_facets().get("age").get_buckets()
assert len(age_buckets) == 3
```

### Nested

```python
select_builder = SelectQuery(
json_facet=JsonFacetComponent(
Expand All @@ -199,22 +237,29 @@ total_people = (
)
assert total_people == 750.0
```

## Hosts

### Single Server

```python
context = SolrServerContext(SolrSingleServerHost('localhost:8983'), SolrBasicAuth('solr', 'SolrRocks'))
client = AsyncSolrCloudClient(context)
```

### Multiple servers

```python
# The client will randomly select a server to send requests to. It will wait 5 seconds for a response, before trying another server.
context = SolrServerContext(
SolrMultipleServerHost(["localhost:8983", "localhost:8984"], 5),
SolrBasicAuth('solr', 'SolrRocks'),
SolrMultipleServerHost(["localhost:8983", "localhost:8984"], 5),
SolrBasicAuth('solr', 'SolrRocks'),
)
client = AsyncSolrCloudClient(context)
```

### Zookeeper

```python
context = SolrServerContext(
await ZookeeperEnsembleHostConnector(["localhost:2181"], 30).connect(),
Expand All @@ -224,4 +269,16 @@ client = AsyncSolrCloudClient(context)
```

## Notes
* Multiprocessing does not work, and will block forever. Normal multithreading works fine.

* Multiprocessing does not work, and will block forever. Normal multithreading works fine.
* Pyo3, the Rust library for creating bindings does not allow overriding the `__init__` method on objects from
Rust. `__new__` has to be overridden instead.

For example, if you want to create a simpler way to create a client
```python
class SolrClient(AsyncSolrCloudClient):
def __new__(cls, host: str, auth: Optional[SolrAuth] = None):
context = SolrServerContext(SolrSingleServerHost(host), auth)
return super().__new__(cls, context=context)
client = SolrClient(config.solr_host, SolrBasicAuth("username", "password"))
```
8 changes: 6 additions & 2 deletions wrappers/python/src/clients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub fn clients(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
Ok(())
}

#[pyclass(name = "AsyncSolrCloudClient", module = "solrstice.clients")]
#[pyclass(name = "AsyncSolrCloudClient", module = "solrstice.clients", subclass)]
#[derive(Clone)]
pub struct AsyncSolrCloudClientWrapper(SolrServerContextWrapper);

Expand Down Expand Up @@ -148,7 +148,11 @@ impl AsyncSolrCloudClientWrapper {
}
}

#[pyclass(name = "BlockingSolrCloudClient", module = "solrstice.clients")]
#[pyclass(
name = "BlockingSolrCloudClient",
module = "solrstice.clients",
subclass
)]
#[derive(Clone)]
pub struct BlockingSolrCloudClientWrapper(SolrServerContextWrapper);

Expand Down
10 changes: 5 additions & 5 deletions wrappers/python/src/hosts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub fn hosts(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
Ok(())
}

#[pyclass(name = "SolrHost", subclass, module = "solrstice.hosts")]
#[pyclass(name = "SolrHost", module = "solrstice.hosts", subclass)]
#[derive(Clone)]
pub struct SolrHostWrapper {
pub solr_host: Arc<dyn SolrHost + Send + Sync>,
Expand All @@ -34,7 +34,7 @@ impl SolrHost for SolrHostWrapper {
}
}

#[pyclass(name = "SolrSingleServerHost", extends = SolrHostWrapper, module= "solrstice.hosts")]
#[pyclass(name = "SolrSingleServerHost", extends = SolrHostWrapper, module= "solrstice.hosts", subclass)]
#[derive(Clone)]
pub struct SolrSingleServerHostWrapper;

Expand All @@ -51,7 +51,7 @@ impl SolrSingleServerHostWrapper {
}
}

#[pyclass(name = "SolrMultipleServerHost", extends = SolrHostWrapper, module= "solrstice.hosts")]
#[pyclass(name = "SolrMultipleServerHost", extends = SolrHostWrapper, module= "solrstice.hosts", subclass)]
#[derive(Clone)]
pub struct SolrMultipleServerHostWrapper;

Expand All @@ -71,11 +71,11 @@ impl SolrMultipleServerHostWrapper {
}
}

#[pyclass(name = "ZookeeperEnsembleHost", extends = SolrHostWrapper, module= "solrstice.hosts")]
#[pyclass(name = "ZookeeperEnsembleHost", extends = SolrHostWrapper, module= "solrstice.hosts", subclass)]
#[derive(Clone)]
pub struct ZookeeperEnsembleHostWrapper;

#[pyclass(name = "ZookeeperEnsembleHostConnector")]
#[pyclass(name = "ZookeeperEnsembleHostConnector", subclass)]
#[derive(Clone)]
pub struct ZookeeperEnsembleHostConnectorWrapper(ZookeeperEnsembleHostConnector);

Expand Down
4 changes: 2 additions & 2 deletions wrappers/python/src/models/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub fn auth(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
Ok(())
}

#[pyclass(name = "SolrAuth", subclass, module = "solrstice.auth")]
#[pyclass(name = "SolrAuth", module = "solrstice.auth", subclass)]
#[derive(Clone)]
pub struct SolrAuthWrapper {
pub solr_auth: Arc<dyn SolrAuth + Send + Sync>,
Expand All @@ -21,7 +21,7 @@ impl SolrAuth for SolrAuthWrapper {
}
}

#[pyclass(name = "SolrBasicAuth", extends=SolrAuthWrapper, module = "solrstice.auth")]
#[pyclass(name = "SolrBasicAuth", extends=SolrAuthWrapper, module = "solrstice.auth", subclass)]
#[derive(Clone)]
pub struct SolrBasicAuthWrapper {}

Expand Down
2 changes: 1 addition & 1 deletion wrappers/python/src/models/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::models::auth::SolrAuthWrapper;
use pyo3::prelude::*;
use solrstice::models::context::{SolrServerContext, SolrServerContextBuilder};

#[pyclass(name = "SolrServerContext", subclass, module = "solrstice.hosts")]
#[pyclass(name = "SolrServerContext", module = "solrstice.hosts", subclass)]
#[derive(Clone)]
pub struct SolrServerContextWrapper(SolrServerContext);

Expand Down
14 changes: 11 additions & 3 deletions wrappers/python/src/models/facet_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub fn facet_set(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
}

#[derive(Clone, Debug, PartialEq, Default)]
#[pyclass(name = "SolrFacetSetResult", module = "solrstice.facet_set")]
#[pyclass(name = "SolrFacetSetResult", module = "solrstice.facet_set", subclass)]
pub struct SolrFacetSetResultWrapper(SolrFacetSetResult);

#[pymethods]
Expand Down Expand Up @@ -75,7 +75,11 @@ impl From<&SolrFacetSetResult> for SolrFacetSetResultWrapper {
}

#[derive(Clone, Debug, PartialEq)]
#[pyclass(name = "SolrPivotFacetResult", module = "solrstice.facet_set")]
#[pyclass(
name = "SolrPivotFacetResult",
module = "solrstice.facet_set",
subclass
)]
pub struct SolrPivotFacetResultWrapper(SolrPivotFacetResult);

#[pymethods]
Expand Down Expand Up @@ -128,7 +132,11 @@ impl From<&SolrPivotFacetResult> for SolrPivotFacetResultWrapper {
}

#[derive(Clone, Debug, PartialEq)]
#[pyclass(name = "SolrFieldFacetResult", module = "solrstice.facet_set")]
#[pyclass(
name = "SolrFieldFacetResult",
module = "solrstice.facet_set",
subclass
)]
pub struct SolrFieldFacetResultWrapper(SolrFieldFacetResult);

#[pymethods]
Expand Down
4 changes: 2 additions & 2 deletions wrappers/python/src/models/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub fn group(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
}

#[derive(Clone)]
#[pyclass(name = "SolrGroupResult", module = "solrstice.group")]
#[pyclass(name = "SolrGroupResult", module = "solrstice.group", subclass)]
pub struct SolrGroupResultWrapper(SolrGroupResult);

#[pymethods]
Expand Down Expand Up @@ -62,7 +62,7 @@ impl From<SolrGroupResultWrapper> for SolrGroupResult {
}

#[derive(Clone)]
#[pyclass(name = "SolrGroupFieldResult", module = "solrstice.group")]
#[pyclass(name = "SolrGroupFieldResult", module = "solrstice.group", subclass)]
pub struct SolrGroupFieldResultWrapper(SolrGroupFieldResult);

#[pymethods]
Expand Down
Loading

0 comments on commit abf0452

Please sign in to comment.