» Python: Building Full-Text Search API with ElasticSearch » 2. Index Documents » 2.3 Send Index Requests

Send Index Requests

Let's add the API for indexing books, then we can put in some test data for later use.

Move domain/model to books/domain/model.

Create books/domain/gateway/book_manager.py:

from abc import ABC, abstractmethod

from ..model import Book


class BookManager(ABC):
    @abstractmethod
    def index_book(self, b: Book) -> str:
        pass

Again, we‘re using 4 Layers Architecture Pattern. It will pay off when your project grows bigger and bigger. Read more here.

Add books/domain/gateway/__init__.py:

from .book_manager import BookManager

Create books/infrastructure/search/es.py:

from dataclasses import asdict

from elasticsearch import Elasticsearch

from ...domain.gateway import BookManager
from ...domain.model import Book


INDEX_BOOK = "book_idx"


class ElasticSearchEngine(BookManager):
    def __init__(self, address: str, page_size: int):
        self.page_size = page_size
        self.client = Elasticsearch(address)

    def index_book(self, b: Book) -> str:
        result = self.client.index(index=INDEX_BOOK, document=asdict(b))
        return result['_id']

By default, Elasticsearch allows you to index documents into an index that doesn‘t exist yet. When you index a document into a non-existent index, Elasticsearch will dynamically create the index for you with default settings. This can be convenient for certain use cases where you don‘t want to manage index creation explicitly.

Install yaml dependencies:

pip3 install PyYAML

Create books/infrastructure/config/config.py:

from dataclasses import dataclass
import yaml


@dataclass
class SearchConfig:
    address: str


@dataclass
class ApplicationConfig:
    page_size: int


@dataclass
class Config:
    app: ApplicationConfig
    search: SearchConfig


def parseConfig(filename: str) -> Config:
    with open(filename, 'r') as f:
        data = yaml.safe_load(f)
        return Config(
            ApplicationConfig(**data['app']),
            SearchConfig(**data['search'])
        )

Add books/infrastructure/config/__init__.py:

from .config import Config, parseConfig

Create config.yml:

app:
  page_size: 10
search:
  address: "http://localhost:9200"

Caution: Do not directly track your config.yml file with Git, as this could potentially leak sensitive data. If necessary, only track the template of the configuration file.
e.g.

app:
  page_size: 10
search:
  address: ""

Create books/application/executor/book_operator.py:

from ...domain.model import Book
from ...domain.gateway import BookManager


class BookOperator():

    def __init__(self, book_manager: BookManager):
        self.book_manager = book_manager

    def create_book(self, b: Book) -> str:
        return self.book_manager.index_book(b)

Remember to create __init__.py files for all the sub-folders.

Create books/application/wire_helper.py:

from ..domain.gateway import BookManager
from ..infrastructure.config import Config
from ..infrastructure.search import ElasticSearchEngine


class WireHelper:
    def __init__(self, engine: ElasticSearchEngine):
        self.engine = engine

    @classmethod
    def new(cls, c: Config):
        es = ElasticSearchEngine(c.search.address, c.app.page_size)
        return cls(es)

    def book_manager(self) -> BookManager:
        return self.engine

Create books/adapter/router.py:

import logging

from fastapi import FastAPI, HTTPException

from ..application.executor import BookOperator
from ..application import WireHelper
from ..domain.model import Book


class RestHandler:
    def __init__(self, logger: logging.Logger, book_operator: BookOperator):
        self._logger = logger
        self.book_operator = book_operator

    def create_book(self, b: Book):
        try:
            return self.book_operator.create_book(b)
        except Exception as e:
            self._logger.error(f"Failed to create: {e}")
            raise HTTPException(status_code=400, detail="Failed to create")


def make_router(app: FastAPI, wire_helper: WireHelper):
    rest_handler = RestHandler(
        logging.getLogger("lr-full-text"),
        BookOperator(wire_helper.book_manager())
    )

    @app.get("/")
    async def welcome():
        return {"status": "ok"}

    @app.post("/books")
    async def create_book(b: Book):
        book_id = rest_handler.create_book(b)
        return {"id": book_id}

Replace main.py with the following code:

from fastapi import FastAPI

from books.adapter import make_router
from books.application import WireHelper
from books.infrastructure.config import parseConfig

CONFIG_FILENAME = "config.yml"

c = parseConfig(CONFIG_FILENAME)
wire_helper = WireHelper.new(c)
app = FastAPI()
make_router(app, wire_helper)

Run the server again and try it with curl:

uvicorn web.main:app --reload

Example request:

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"The Da Vinci Code","author":"Dan Brown","published_at":"2003-03-18","content":"In the Louvre, a curator is found dead. Next to his body, an enigmatic message. It is the beginning of a race to discover the truth about the Holy Grail."}' \
  http://localhost:8000/books

Example response:

{"id":"Te9HCo8BPyexJHELQDO_"}

Put in more test data

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"Harry Potter and the Philosopher\u0027s Stone","author":"J.K. Rowling","published_at":"1997-06-26","content":"A young boy discovers he is a wizard and begins his education at Hogwarts School of Witchcraft and Wizardry, where he uncovers the mystery of the Philosopher‘s Stone."}' \
  http://localhost:8000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"To Kill a Mockingbird","author":"Harper Lee","published_at":"1960-07-11","content":"Set in the American South during the Great Depression, the novel explores themes of racial injustice and moral growth through the eyes of young Scout Finch."}' \
  http://localhost:8000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"The Lord of the Rings","author":"J.R.R. Tolkien","published_at":"1954-07-29","content":"A hobbit named Frodo Baggins embarks on a perilous journey to destroy a powerful ring and save Middle-earth from the Dark Lord Sauron."}' \
  http://localhost:8000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"The Catcher in the Rye","author":"J.D. Salinger","published_at":"1951-07-16","content":"Holden Caulfield narrates his experiences in New York City after being expelled from prep school, grappling with themes of alienation, identity, and innocence."}' \
  http://localhost:8000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"The Alchemist","author":"Paulo Coelho","published_at":"1988-01-01","content":"Santiago, a shepherd boy, travels from Spain to Egypt in search of a treasure buried near the Pyramids. Along the way, he learns about the importance of following one‘s dreams."}' \
  http://localhost:8000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"The Hunger Games","author":"Suzanne Collins","published_at":"2008-09-14","content":"In a dystopian future, teenagers are forced to participate in a televised death match called the Hunger Games. Katniss Everdeen volunteers to take her sister‘s place and becomes a symbol of rebellion."}' \
  http://localhost:8000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"1984","author":"George Orwell","published_at":"1949-06-08","content":"Winston Smith lives in a totalitarian society ruled by the Party led by Big Brother. He rebels against the oppressive regime but ultimately succumbs to its control."}' \
  http://localhost:8000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"The Girl with the Dragon Tattoo","author":"Stieg Larsson","published_at":"2005-08-01","content":"Journalist Mikael Blomkvist and hacker Lisbeth Salander investigate the disappearance of a young woman from a wealthy family, uncovering dark secrets and corruption."}' \
  http://localhost:8000/books

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"title":"Gone Girl","author":"Gillian Flynn","published_at":"2012-06-05","content":"On their fifth wedding anniversary, Nick Dunne‘s wife, Amy, disappears. As the media circus ensues and suspicions mount, Nick finds himself in a whirlwind of deception and betrayal."}' \
  http://localhost:8000/books
PrevNext