SQLAlchemy Searchable
SQLAlchemy Searchable provides full-text search capabilities for declarative SQLAlchemy models, primarily leveraging PostgreSQL's `tsvector` type for robust search. The current version is 3.0.0, and the library maintains a regular release cadence, with major versions often aligning with SQLAlchemy and PostgreSQL version support changes.
Common errors
-
sqlalchemy.exc.ProgrammingError: type "tsvector" does not exist
cause This error typically occurs when using `sqlalchemy-searchable` with PostgreSQL without the `sqlalchemy_utils` package installed or without explicitly defining the `TSVectorType` column in your model, or if the PostgreSQL database itself is missing the required language configuration or `pg_trgm` extension.fixInstall `sqlalchemy-utils` (`pip install sqlalchemy-searchable[sqlalchemy_utils]`) and consider defining a `TSVectorType` column in your model (e.g., `search_vector = Column(TSVectorType('column1', 'column2'))`). Ensure your PostgreSQL database has the necessary extensions enabled (e.g., `CREATE EXTENSION pg_trgm;`) and relevant language dictionaries configured. -
AttributeError: 'Query' object has no attribute 'search'
cause This error means that the `.search()` method was not added to your SQLAlchemy query object. This usually happens if `make_searchable(Base.metadata)` was not called, or if it was called *after* your models were defined.fixVerify that `make_searchable(Base.metadata)` is called correctly and, crucially, that it is executed *before* any of your SQLAlchemy models that use `__searchable__` are defined. -
No module named 'sqlalchemy_searchable'
cause The `sqlalchemy-searchable` library is not installed in your current Python environment.fixInstall the package using pip: `pip install sqlalchemy-searchable`.
Warnings
- breaking Version 3.0.0 drops support for PostgreSQL 11, 12, and 13. Users on these older PostgreSQL versions must upgrade their database or stick to `sqlalchemy-searchable < 3.0.0`.
- breaking Version 2.0.0 dropped support for Python 3.6, 3.7 and SQLAlchemy 1.3. Your application must use Python 3.8+ and SQLAlchemy 1.4+ (or 2.x) to use `sqlalchemy-searchable >= 2.0.0`.
- gotcha `sqlalchemy-searchable`'s advanced full-text search capabilities, particularly with `tsvector` types and linguistic features, are most robust and performant when used with PostgreSQL. While it can work with other databases, their FTS support might be limited or rely on generic string matching.
- gotcha The `make_searchable(Base.metadata)` function must be called *before* defining your SQLAlchemy models. If called afterwards, the models will not be properly instrumented for search capabilities, and you might encounter `AttributeError`.
Install
-
pip install sqlalchemy-searchable -
pip install sqlalchemy-searchable[sqlalchemy_utils] -
pip install sqlalchemy-searchable[psycopg2-binary]
Imports
- make_searchable
from sqlalchemy_searchable import make_searchable
- SearchQueryMixin
from sqlalchemy_searchable import SearchQueryMixin
Quickstart
import os
from sqlalchemy import create_engine, Column, Integer, String, Text
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy_searchable import make_searchable
# from sqlalchemy_utils import TSVectorType # Often used for explicit search vector column type
# Database setup (using in-memory SQLite for simplicity)
# For PostgreSQL, use 'postgresql://user:password@host:port/database'
engine = create_engine(os.environ.get('SQLALCHEMY_DATABASE_URL', 'sqlite:///:memory:'))
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base()
# IMPORTANT: Call make_searchable BEFORE defining your models
make_searchable(Base.metadata)
class Document(Base):
__tablename__ = 'document'
# __searchable__ defines which columns contribute to the search vector
__searchable__ = ['title', 'content']
id = Column(Integer, primary_key=True)
title = Column(String(255))
content = Column(Text)
# For PostgreSQL, you might define an explicit TSVectorType:
# search_vector = Column(TSVectorType('title', 'content'))
def __repr__(self):
return f"<Document(id={self.id}, title='{self.title}')>"
Base.metadata.create_all(engine)
# Add some data
doc1 = Document(title="Python Programming Basics", content="Learn the fundamentals of Python, including variables, data types, and control flow.")
doc2 = Document(title="Advanced SQLAlchemy Techniques", content="Explore advanced features of SQLAlchemy like custom types, events, and performance tuning.")
doc3 = Document(title="Web Development with Flask", content="Build web applications efficiently using the Flask microframework and Jinja2 templates.")
session.add_all([doc1, doc2, doc3])
session.commit()
# Perform searches
print("Searching for 'Python':")
results_python = session.query(Document).search('Python').all()
for doc in results_python:
print(f"- {doc.title}")
print("\nSearching for 'web applications':")
results_web = session.query(Document).search('web applications').all()
for doc in results_web:
print(f"- {doc.title}")
session.close()