Aller au contenu principal

Conception de schéma

Le schéma définit la structure des documents dans votre index. Il spécifie les champs existants, leur indexation et leur stockage.

Types de champs​

TypeDescriptionIndexéStocké
TEXTTexte libre, tokeniséOuiOptionnel
IDIdentifiant non tokeniséOuiOptionnel
KEYWORDMots-clés séparés par espace/virguleOuiOptionnel
STOREDStocké uniquement, non searchableNonOui
NUMERICEntier ou flottantOuiOptionnel
DATETIMEDates et heuresOuiOptionnel
BOOLEANBooléenOuiOptionnel
NGRAMN-grammes de caractèresOuiOptionnel
NGRAMWORDSN-grammes de motsOuiOptionnel
VectorFieldVecteur d'embeddingPersonnaliséOptionnel

Créer un schéma​

from whoosh.fields import Schema, TEXT, ID, KEYWORD, STORED, NUMERIC

schema = Schema(
title=TEXT(stored=True),
path=ID(stored=True, unique=True),
content=TEXT,
tags=KEYWORD(lowercase=True),
published=NUMERIC(int, stored=True),
is_published=BOOLEAN,
icon=STORED
)

Options des champs​

TEXT​

content = TEXT(
stored=False, # Stocker le texte original ?
unique=False, # Utiliser pour remplacer des documents ?
phrase=True, # Indexer les positions pour recherche de phrases
analyzer=None, # Analyseur personnalisé
field_boost=1.0 # Boost pour le scoring
)

ID​

path = ID(
stored=True, # Stocker le chemin
unique=True # Utiliser pour remplacement de documents
)

KEYWORD​

tags = KEYWORD(
stored=False,
lowercase=True, # Minusculiser automatiquement
commas=True, # Séparer par virgules
scorable=True # Stocker la longueur pour scoring
)

SchemaBuilder​

Whoosh-NG v4.0.0.dev0 (en développement) introduit SchemaBuilder pour une API fluide :

from whoosh.fields import SchemaBuilder, TEXT, ID, NUMERIC

schema = (
SchemaBuilder()
.field("title", TEXT(stored=True))
.field("path", ID(stored=True, unique=True))
.field("content", TEXT)
.field("rating", NUMERIC(float, stored=True))
.build()
)

Champs dynamiques​

Utilisez des patterns glob pour associer des types :

# Tout champ finissant par "_date" est un DATETIME
schema.add("*_date", DATETIME(stored=True), glob=True)

# Tout champ finissant par "_id" est un ID
schema.add("*_id", ID(stored=True), glob=True)

Modifier le schéma​

Ajoutez ou supprimez des champs après création :

writer = ix.writer()

# Ajouter un champ
writer.add_field("description", TEXT(stored=True))

# Supprimer un champ
writer.remove_field("legacy_field")

writer.commit()

Note: Supprimer un champ ne fait que le retirer du schéma. Les données ne sont libérées qu'à l'optimisation.

Modèles de recherche​

Whoosh-NG peut mapper automatiquement des modèles Python (dataclasses, Pydantic, SQLAlchemy, SQLModel, msgspec) vers un Schema Whoosh via ModelIndex.

Niveau 1 : Auto-mapping​

from dataclasses import dataclass
from whoosh_modern.models import ModelIndex

@dataclass
class Book:
title: str
count: int
tag: str | None = None

idx = ModelIndex(Book)
schema = idx.schema

ModelIndex inspecte les annotations de type et les mappe vers des champs Whoosh :

Type PythonChamp Whoosh
strTEXT
int / floatNUMERIC
boolBOOLEAN
datetime / dateDATETIME
DecimalNUMERIC(int, decimal_places=2)
EnumKEYWORD
bytesKEYWORD (stockage hexadécimal)
list[str]KEYWORD
Optional[T]type mappé ou STORED

Les champs ID sont auto-détectés : SearchOptions(id=True) explicite > nom id/ID/_id > premier champ str.

Niveau 2 : Options explicites​

Utilisez SearchField pour remplacer les valeurs par défaut :

from whoosh_modern.models import SearchField, SearchOptions

class Book:
title: str = SearchField(fulltext=True, stored=True)
count: int = SearchField(sortable=True)
tags: list[str] = SearchField(multi=True)

Niveau 3 : Types annotés​

Utilisez Annotated pour attacher des métadonnées directement aux annotations :

from typing import Annotated
from whoosh_modern.models import SearchField

class Book:
title: Annotated[str, SearchField(fulltext=True, stored=True)]

Intégrations​

Dataclass​

from dataclasses import dataclass
from whoosh_modern.models import ModelIndex

@dataclass
class Article:
title: str
body: str
published: datetime.datetime

idx = ModelIndex(Article)

Pydantic v2​

from pydantic import BaseModel
from whoosh_modern.models import register_model

class Article(BaseModel):
title: str
body: str
published: datetime.datetime

# Métadonnées de recherche par champ via json_schema_extra
model_config = {"json_schema_extra": {"search": {"fulltext": True}}}

idx = register_model(Article)

SQLAlchemy​

from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import DeclarativeBase
from whoosh_modern.models import register_model

class Base(DeclarativeBase):
pass

class Article(Base):
__tablename__ = "articles"
id = Column(Integer, primary_key=True)
title = Column(String, info={"search": {"fulltext": True, "stored": True}})
published = Column(DateTime, info={"search": {"sortable": True}})

idx = register_model(Article)

SQLModel​

from sqlmodel import SQLModel, Field
from whoosh_modern.models import register_model

class Article(SQLModel, table=True):
id: int = Field(primary_key=True)
title: str = Field(sa_column_kwargs={"info": {"search": {"fulltext": True}}})
published: datetime.datetime

idx = register_model(Article)

msgspec​

import msgspec
from whoosh_modern.models import register_model

class Article(msgspec.Struct):
title: str = msgspec.field(metadata={"search": {"fulltext": True}})
published: datetime.datetime

idx = register_model(Article)

Conversion d'instances​

doc = idx.to_whoosh_document(book_instance)
writer.add_document(**doc)

to_whoosh_document gère :

  • dataclass : itération via dataclasses.fields()
  • Pydantic/SQLModel : itération via model_fields
  • SQLAlchemy : itération via __mapper__.columns
  • Valeurs Enum converties en .value
  • bytes convertis en chaîne hexadécimale

Bonnes pratiques​

  1. Minimal : N'indexez que ce que vous cherchez
  2. STORED avec parcimonie : Augmente la taille de l'index
  3. Champs uniques : Utilisez unique=True pour les identifiants
  4. Boost de champ : Boostez les champs importants au niveau schéma
  5. TEXT options : Désactivez phrase si vous n'avez pas besoin de recherche de phrase
  6. Champ ID : Laissez ModelIndex auto-détecter ou marquez explicitement avec SearchOptions(id=True)