Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pydantic v2 Meilisearch #32

Merged
merged 6 commits into from
Jul 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions dbs/meilisearch/.env.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Master key must be at least 16 bytes, composed of valid UTF-8 characters
MEILI_MASTER_KEY = ""
MEILI_VERSION = "v1.1.1"
MEILI_VERSION = "v1.2.0"
MEILI_PORT = 7700
MEILI_URL = "localhost"
MEILI_SERVICE = "meilisearch"
API_PORT = 8003

# Container image tag
TAG = "0.1.0"
TAG = "0.2.0"

# Docker project namespace (defaults to the current folder name if not set)
COMPOSE_PROJECT_NAME = meili_wine
10 changes: 6 additions & 4 deletions dbs/meilisearch/api/config.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from pydantic import BaseSettings
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
extra="allow",
)

meili_service: str
meili_master_key: str
meili_port: int
meili_url: str
tag: str

class Config:
env_file = ".env"
3 changes: 2 additions & 1 deletion dbs/meilisearch/api/routers/rest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from meilisearch_python_async import Client
from fastapi import APIRouter, HTTPException, Query, Request
from meilisearch_python_async import Client

from schemas.retriever import (
FullTextSearch,
TopWinesByCountry,
Expand Down
8 changes: 5 additions & 3 deletions dbs/meilisearch/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
meilisearch-python-async==1.2.0
pydantic[dotenv]>=1.10.7, <2.0.0
fastapi>=0.95.0, <1.0.0
meilisearch-python-async~=1.4.0
pydantic~=2.0.0
pydantic-settings~=2.0.0
python-dotenv>=1.0.0
fastapi~=0.100.0
httpx>=0.24.0
aiohttp>=3.8.4
uvicorn>=0.21.0, <1.0.0
Expand Down
39 changes: 21 additions & 18 deletions dbs/meilisearch/schemas/retriever.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
from pydantic import BaseModel
from pydantic import BaseModel, ConfigDict


class FullTextSearch(BaseModel):
id: int
country: str
title: str
description: str | None
points: int
price: float | str | None
variety: str | None
winery: str | None

class Config:
schema_extra = {
model_config = ConfigDict(
json_schema_extra={
"example": {
"id": 3845,
"country": "Italy",
Expand All @@ -24,9 +15,23 @@ class Config:
"winery": "Castellinuzza e Piuca",
}
}
)

id: int
country: str
title: str
description: str | None
points: int
price: float | str | None
variety: str | None
winery: str | None


class TopWinesByCountry(BaseModel):
model_config = ConfigDict(
validate_assignment=True,
)

id: int
country: str
title: str
Expand All @@ -36,11 +41,12 @@ class TopWinesByCountry(BaseModel):
variety: str | None
winery: str | None

class Config:
validate_assignment = True


class TopWinesByProvince(BaseModel):
model_config = ConfigDict(
validate_assignment=True,
)

id: int
country: str
province: str
Expand All @@ -50,6 +56,3 @@ class TopWinesByProvince(BaseModel):
price: float | str | None = "Not available"
variety: str | None
winery: str | None

class Config:
validate_assignment = True
78 changes: 48 additions & 30 deletions dbs/meilisearch/schemas/wine.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,13 @@
from pydantic import BaseModel, root_validator
from pydantic import BaseModel, ConfigDict, Field, model_validator


class Wine(BaseModel):
id: int
points: int
title: str
description: str | None
price: float | None
variety: str | None
winery: str | None
vineyard: str | None
country: str | None
province: str | None
region_1: str | None
region_2: str | None
taster_name: str | None
taster_twitter_handle: str | None

class Config:
allow_population_by_field_name = True
validate_assignment = True
schema_extra = {
model_config = ConfigDict(
populate_by_name=True,
validate_assignment=True,
extra="allow",
str_strip_whitespace=True,
json_schema_extra={
"example": {
"id": 45100,
"points": 85,
Expand All @@ -37,20 +24,51 @@ class Config:
"taster_name": "Michael Schachner",
"taster_twitter_handle": "@wineschach",
}
}
},
)

@root_validator(pre=True)
def _get_vineyard(cls, values):
"Rename designation to vineyard"
vineyard = values.pop("designation", None)
if vineyard:
values["vineyard"] = vineyard.strip()
return values
id: int
points: int
title: str
description: str | None
price: float | None
variety: str | None
winery: str | None
vineyard: str | None = Field(..., alias="designation")
country: str | None
province: str | None
region_1: str | None
region_2: str | None
taster_name: str | None
taster_twitter_handle: str | None

@root_validator
@model_validator(mode="before")
def _fill_country_unknowns(cls, values):
"Fill in missing country values with 'Unknown', as we always want this field to be queryable"
country = values.get("country")
if not country:
if country is None or country == "null":
values["country"] = "Unknown"
return values


if __name__ == "__main__":
data = {
"id": 45100,
"points": 85,
"title": "Balduzzi 2012 Reserva Merlot (Maule Valley)",
"description": "Ripe in color and aromas, this chunky wine delivers heavy baked-berry and raisin aromas in front of a jammy, extracted palate. Raisin and cooked berry flavors finish plump, with earthy notes.",
"price": 10, # Test if field is cast to float
"variety": "Merlot",
"winery": "Balduzzi",
"designation": "Reserva", # Test if field is renamed
"country": "null", # Test unknown country
"province": " Maule Valley ", # Test if field is stripped
"region_1": "null",
"region_2": "null",
"taster_name": "Michael Schachner",
"taster_twitter_handle": "@wineschach",
}
from pprint import pprint

wine = Wine(**data)
pprint(wine.model_dump(), sort_dicts=False)
6 changes: 2 additions & 4 deletions dbs/meilisearch/scripts/bulk_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from meilisearch_python_async import Client
from meilisearch_python_async.index import Index
from meilisearch_python_async.models.settings import MeilisearchSettings
from pydantic.main import ModelMetaclass

sys.path.insert(1, os.path.realpath(Path(__file__).resolve().parents[1]))
from api.config import Settings
Expand Down Expand Up @@ -62,15 +61,14 @@ def get_json_data(data_dir: Path, filename: str) -> list[JsonBlob]:

def validate(
data: list[JsonBlob],
model: ModelMetaclass,
exclude_none: bool = False,
) -> list[JsonBlob]:
validated_data = [model(**item).dict(exclude_none=exclude_none) for item in data]
validated_data = [Wine(**item).model_dump(exclude_none=exclude_none) for item in data]
return validated_data


def process_chunks(data: list[JsonBlob]) -> tuple[list[JsonBlob], str]:
validated_data = validate(data, Wine, exclude_none=True)
validated_data = validate(data, exclude_none=True)
return validated_data


Expand Down