» Python:使用ElasticSearch构建全文检索API » 2. 索引文档 » 2.3 发送 Index 请求

发送 Index 请求

让我们添加索引图书的 API,然后放入一些测试数据供后续使用。

移动 domain/modelbooks/domain/model

创建 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

此处也是在使用4层架构。项目越大,该架构的好处越明显。阅读更多

添加 books/domain/gateway/__init__.py:

from .book_manager import BookManager

创建 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']

默认情况下,Elasticsearch 允许你将文档索引到尚不存在的索引中。 当你将文档索引到不存在的索引时,Elasticsearch 将会使用默认设置动态地创建索引。这在开发者不想显式地创建索引时会很方便。

安装 yaml 依赖:

pip3 install PyYAML

创建 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'])
        )

添加 books/infrastructure/config/__init__.py:

from .config import Config, parseConfig

创建 config.yml:

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

警醒:
不要直接 git 提交 config.yml。可能会导致敏感数据泄露。如果非要提交的话,建议只提交配置格式模板。
比如:

app:
  page_size: 10
search:
  address: ""

创建 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)

记得给所有相关子目录创建 __init__.py 文件。

创建 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

创建 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}

用以下代码替换 main.py 中内容:

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)

再次运行 server,然后使用 curl 进行测试:

uvicorn web.main:app --reload

样例请求:

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

样例响应:

{"id":"Te9HCo8BPyexJHELQDO_"}

放入测试数据

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
上页下页