mirror of
https://github.com/wowlikon/LibraryAPI.git
synced 2025-12-11 21:30:46 +00:00
Добавление жанров, дополнение описания
This commit is contained in:
56
.gitignore
vendored
56
.gitignore
vendored
@@ -3,9 +3,6 @@ __pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
@@ -27,12 +24,6 @@ share/python-wheels/
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
@@ -51,36 +42,6 @@ coverage.xml
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
@@ -94,13 +55,6 @@ ipython_config.py
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
# .env
|
||||
.venv
|
||||
@@ -110,16 +64,9 @@ ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
@@ -134,8 +81,5 @@ dmypy.json
|
||||
# VS code
|
||||
.vscode
|
||||
|
||||
# JUPITER
|
||||
*.ipynb
|
||||
|
||||
# Postgres data
|
||||
data/
|
||||
|
||||
110
README.md
110
README.md
@@ -1,97 +1,105 @@
|
||||
# LibraryAPI
|
||||
|
||||
This project is a test web application built using FastAPI, a modern web framework for creating APIs in Python. It showcases the use of Pydantic for data validation, SQLModel for database interactions, Alembic for migration management, PostgreSQL as the database system, and Docker Compose for easy deployment.
|
||||
Это проект приложения на FastAPI - современном веб фреймворке для создания API на Python. Я использую Pydantic для валидации данных, SQLModel для взаимодействия с базой данных, Alembic для управления миграциями, PostgreSQL как систему базы данных и Docker Compose для легкого развертывания.
|
||||
|
||||
### **Key Components:**
|
||||
### **Ключевые элементы:**
|
||||
|
||||
1. FastAPI: Provides high performance and simplicity for developing RESTful APIs, supporting asynchronous operations and automatic documentation generation.
|
||||
2. Pydantic: Used for data validation and serialization, allowing easy definition of data schemas.
|
||||
3. SQLModel: Combines SQLAlchemy and Pydantic, enabling database operations with Python classes.
|
||||
4. Alembic: A tool for managing database migrations, making it easy to track and apply changes to the database schema.
|
||||
5. PostgreSQL: A reliable relational database used for data storage.
|
||||
6. Docker Compose: Simplifies the deployment of the application and its dependencies in containers.
|
||||
1. FastAPI: Предоставляет высокопроизводительность и простоту для разработки RESTful API, поддерживает асинхронные операции и автоматическую генерацию документации.
|
||||
2. Pydantic: Используется для валидации данных и сериализации, позволяет легко определить схемы данных.
|
||||
3. SQLModel: Объединяет SQLAlchemy и Pydantic, включая операции с базой данных с помощью классов Python.
|
||||
4. Alembic: Инструмент для управления миграциями базы данных, упрощающий отслеживание и применение изменений в схеме базы данных.
|
||||
5. PostgreSQL: Надежная реляционная база данных для хранения данных.
|
||||
6. Docker Compose: Упрощает развертывание приложения и его зависимостей в контейнерах.
|
||||
|
||||
|
||||
### **Installation Instructions**
|
||||
### **Инструкция по установке**
|
||||
|
||||
For development:
|
||||
|
||||
1. Clone the repository:
|
||||
1. Клонируйте репозиторий:
|
||||
```bash
|
||||
git clone https://github.com/wowlikon/libraryapi.git
|
||||
```
|
||||
|
||||
2. Navigate to the project directory:
|
||||
2. Перейдите в каталог проекта:
|
||||
```bash
|
||||
cd libraryapi
|
||||
```
|
||||
|
||||
3. Configure environment variables:
|
||||
3. Настройте переменные окружения:
|
||||
```bash
|
||||
edit .env
|
||||
```
|
||||
|
||||
4. Build the Docker containers:
|
||||
4. Соберите контейнеры Docker:
|
||||
```bash
|
||||
docker compose build
|
||||
```
|
||||
|
||||
5. Run the application:
|
||||
5. Запустите приложение:
|
||||
```bash
|
||||
docker compose up api
|
||||
```
|
||||
|
||||
For make new migrations:
|
||||
Для создания новых миграций:
|
||||
```bash
|
||||
docker compose run --rm -T api alembic revision --autogenerate -m "Migration name"
|
||||
```
|
||||
|
||||
For run tests:
|
||||
Для запуска тестов:
|
||||
```bash
|
||||
docker compose up test
|
||||
```
|
||||
|
||||
### **API Endpoints**
|
||||
### **Эндпоинты API**
|
||||
|
||||
**Authors**
|
||||
| Method | Endpoint | Description |
|
||||
|--------|-----------------------|------------------------------------------------|
|
||||
| POST | `/authors` | Create a new author |
|
||||
| GET | `/authors` | Retrieve a list of all authors |
|
||||
| GET | `/authors/{id}` | Retrieve a specific author by ID with books |
|
||||
| PUT | `/authors/{id}` | Update a specific author by ID |
|
||||
| DELETE | `/authors/{id}` | Delete a specific author by ID |
|
||||
| GET | `/authors/{id}/books` | Retrieve a list of books for a specific author |
|
||||
**Авторы**
|
||||
| Метод | Эндпоинты | Описание |
|
||||
|--------|-----------------------|---------------------------------------------|
|
||||
| POST | `/authors` | Создать нового автора |
|
||||
| GET | `/authors` | Получить список всех авторов |
|
||||
| GET | `/authors/{id}` | Получить конкретного автора по ID с книгами |
|
||||
| PUT | `/authors/{id}` | Обновить конкретного автора по ID |
|
||||
| DELETE | `/authors/{id}` | Удалить конкретного автора по ID |
|
||||
| GET | `/authors/{id}/books` | Получить список книг для конкретного автора |
|
||||
|
||||
**Books**
|
||||
| Method | Endpoint | Description |
|
||||
|--------|-----------------------|------------------------------------------------|
|
||||
| POST | `/books` | Create a new book |
|
||||
| GET | `/books` | Retrieve a list of all books |
|
||||
| GET | `/book/{id}` | Retrieve a specific book by ID with authors |
|
||||
| PUT | `/books/{id}` | Update a specific book by ID |
|
||||
| DELETE | `/books/{id}` | Delete a specific book by ID |
|
||||
| GET | `/books/{id}/authors` | Retrieve a list of authors for a specific book |
|
||||
**Книги**
|
||||
| Метод | Эндпоинты | Описание |
|
||||
|--------|-----------------------|----------------------------------------------|
|
||||
| POST | `/books` | Создать новую книгу |
|
||||
| GET | `/books` | Получить список всех книг |
|
||||
| GET | `/book/{id}` | Получить конкретную книгу по ID с авторами |
|
||||
| PUT | `/books/{id}` | Обновить конкретную книгу по ID |
|
||||
| DELETE | `/books/{id}` | Удалить конкретную книгу по ID |
|
||||
| GET | `/books/{id}/authors` | Получить список авторов для конкретной книги |
|
||||
|
||||
**Relationships**
|
||||
| Method | Endpoint | Description |
|
||||
**Жанры**
|
||||
| Метод | Эндпоинты | Описание |
|
||||
|--------|-----------------------|----------------------------------------------|
|
||||
| POST | `/genres` | Создать новый жанр |
|
||||
| GET | `/genres` | Получить список всех жанров |
|
||||
| GET | `/genres/{id}` | Получить конкретный жанр по ID |
|
||||
| PUT | `/genres/{id}` | Обновить конкретный жанр по ID |
|
||||
| DELETE | `/genres/{id}` | Удалить конкретный жанр по ID |
|
||||
| GET | `/books/{id}/genres` | Получить список жанров для конкретной книги |
|
||||
|
||||
**Связи**
|
||||
| Метод | Эндпоинты | Описание |
|
||||
|--------|------------------------------|-----------------------------------------|
|
||||
| GET | `/relationships/author-book` | Retrieve a list of all relationships |
|
||||
| POST | `/relationships/author-book` | Add author-book relationship |
|
||||
| DELETE | `/relationships/author-book` | Remove author-book relationship |
|
||||
| GET | `/relationships/author-book` | Получить список всех связей автор-книга |
|
||||
| POST | `/relationships/author-book` | Добавить связь автор-книга |
|
||||
| DELETE | `/relationships/author-book` | Удалить связь автор-книга |
|
||||
|
||||
|
||||
### **Technologies Used**
|
||||
### **Используемые технологии**
|
||||
|
||||
- **FastAPI**: A modern web framework for building APIs with Python, known for its speed and ease of use.
|
||||
- **Pydantic**: A data validation and settings management library that uses Python type annotations.
|
||||
- **SQLModel**: A library for interacting with databases using Python classes, combining the features of SQLAlchemy and Pydantic.
|
||||
- **Alembic**: A lightweight database migration tool for use with SQLAlchemy.
|
||||
- **PostgreSQL**: A powerful, open-source relational database management system.
|
||||
- **Docker**: A platform for developing, shipping, and running applications in containers.
|
||||
- **Docker Compose**: A tool for defining and running multi-container Docker applications.
|
||||
- **FastAPI**: Современный web фреймворк для построения API с использованием Python, известный своей скоростью и простотой использования.
|
||||
- **Pydantic**: Библиотека для валидации данных и управления настройками, использующая аннотации типов Python.
|
||||
- **SQLModel**: Библиотека для взаимодействия с базами данных с использованием классов Python, объединяющая функции SQLAlchemy и Pydantic.
|
||||
- **Alembic**: Легковесный инструмент для миграции базы данных на основе SQLAlchemy.
|
||||
- **PostgreSQL**: Сильная, открытая реляционная система управления базами данных.
|
||||
- **Docker**: Платформа для разработки, распространения и запуска приложений в контейнерах.
|
||||
- **Docker Compose**: Инструмент для определения и запуска многоконтейнерных приложений Docker.
|
||||
|
||||
|
||||
### **TODO List**
|
||||
|
||||
- Geners table and endpoints
|
||||
- Добавление жанров
|
||||
|
||||
0
alembic.ini
Executable file → Normal file
0
alembic.ini
Executable file → Normal file
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
db:
|
||||
container_name: db
|
||||
image: postgres
|
||||
image: postgres:17
|
||||
expose:
|
||||
- 5432
|
||||
volumes:
|
||||
|
||||
@@ -11,18 +11,20 @@ from .routers.misc import get_info
|
||||
app = get_app()
|
||||
alembic_cfg = Config("alembic.ini")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
print("[+] Initializing...")
|
||||
|
||||
# Initialize the database
|
||||
# Настройка базы данных
|
||||
with engine.begin() as connection:
|
||||
alembic_cfg.attributes['connection'] = connection
|
||||
alembic_cfg.attributes["connection"] = connection
|
||||
command.upgrade(alembic_cfg, "head")
|
||||
|
||||
print("[+] Starting...")
|
||||
yield # Here FastAPI will start handling requests;
|
||||
yield # Обработка запросов
|
||||
print("[+] Application shutdown")
|
||||
|
||||
# Include routers
|
||||
|
||||
# Подключение маршрутов
|
||||
app.include_router(api_router)
|
||||
|
||||
@@ -2,13 +2,15 @@ from fastapi import APIRouter
|
||||
|
||||
from .authors import router as authors_router
|
||||
from .books import router as books_router
|
||||
from .genres import router as genres_router
|
||||
from .relationships import router as relationships_router
|
||||
from .misc import router as misc_router
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
# Including all routers
|
||||
# Подключение всех маршрутов
|
||||
api_router.include_router(authors_router)
|
||||
api_router.include_router(books_router)
|
||||
api_router.include_router(genres_router)
|
||||
api_router.include_router(relationships_router)
|
||||
api_router.include_router(misc_router)
|
||||
|
||||
@@ -10,29 +10,32 @@ from httpx import get
|
||||
|
||||
from library_service.settings import get_app
|
||||
|
||||
# Templates initialization
|
||||
# Загрузка шаблонов
|
||||
templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates")
|
||||
|
||||
router = APIRouter(tags=["misc"])
|
||||
|
||||
# Formatted information about the application
|
||||
|
||||
# Форматированная информация о приложении
|
||||
def get_info(app) -> Dict:
|
||||
return {
|
||||
"status": "ok",
|
||||
"app_info": {
|
||||
"title": app.title,
|
||||
"version": app.version,
|
||||
"description": app.description,
|
||||
},
|
||||
"server_time": datetime.now().isoformat(),
|
||||
}
|
||||
"status": "ok",
|
||||
"app_info": {
|
||||
"title": app.title,
|
||||
"version": app.version,
|
||||
"description": app.description,
|
||||
},
|
||||
"server_time": datetime.now().isoformat(),
|
||||
}
|
||||
|
||||
# Root endpoint
|
||||
|
||||
# Эндпоинт главной страницы
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
async def root(request: Request, app=Depends(get_app)):
|
||||
return templates.TemplateResponse(request, "index.html", get_info(app))
|
||||
|
||||
# API Information endpoint
|
||||
|
||||
# Эндпоинт информации об API
|
||||
@router.get("/api/info")
|
||||
async def api_info(app=Depends(get_app)):
|
||||
return JSONResponse(content=get_info(app))
|
||||
|
||||
@@ -51,9 +51,57 @@ def remove_author_from_book(author_id: int, book_id: int, session: Session = Dep
|
||||
return {"message": "Relationship removed successfully"}
|
||||
|
||||
# Get relationships
|
||||
@router.get("/relationships/author-book", response_model=List[AuthorBookLink])
|
||||
@router.get("/relationships/genre-book", response_model=List[GenreBookLink])
|
||||
def get_relationships(session: Session = Depends(get_session)):
|
||||
relationships = session.exec(select(AuthorBookLink)).all()
|
||||
relationships = session.exec(select(GenreBookLink)).all()
|
||||
return relationships
|
||||
|
||||
# Add author to book
|
||||
@router.post("/relationships/genre-book", response_model=GenreBookLink)
|
||||
def add_genre_to_book(genre_id: int, book_id: int, session: Session = Depends(get_session)):
|
||||
genre = session.get(Genre, genre_id)
|
||||
if not genre:
|
||||
raise HTTPException(status_code=404, detail="Genre not found")
|
||||
|
||||
book = session.get(Book, book_id)
|
||||
if not book:
|
||||
raise HTTPException(status_code=404, detail="Book not found")
|
||||
|
||||
existing_link = session.exec(
|
||||
select(GenreBookLink)
|
||||
.where(GenreBookLink.genre_id == genre_id)
|
||||
.where(GenreBookLink.book_id == book_id)
|
||||
).first()
|
||||
|
||||
if existing_link:
|
||||
raise HTTPException(status_code=400, detail="Relationship already exists")
|
||||
|
||||
link = GenreBookLink(genre_id=genre_id, book_id=book_id)
|
||||
session.add(link)
|
||||
session.commit()
|
||||
session.refresh(link)
|
||||
return link
|
||||
|
||||
# Remove author from book
|
||||
@router.delete("/relationships/genre-book", response_model=Dict[str, str])
|
||||
def remove_genre_from_book(genre_id: int, book_id: int, session: Session = Depends(get_session)):
|
||||
link = session.exec(
|
||||
select(GenreBookLink)
|
||||
.where(GenreBookLink.genre_id == genre_id)
|
||||
.where(GenreBookLink.book_id == book_id)
|
||||
).first()
|
||||
|
||||
if not link:
|
||||
raise HTTPException(status_code=404, detail="Relationship not found")
|
||||
|
||||
session.delete(link)
|
||||
session.commit()
|
||||
return {"message": "Relationship removed successfully"}
|
||||
|
||||
# Get relationships
|
||||
@router.get("/relationships/genre-book", response_model=List[GenreBookLink])
|
||||
def get__genre_relationships(session: Session = Depends(get_session)):
|
||||
relationships = session.exec(select(GenreBookLink)).all()
|
||||
return relationships
|
||||
|
||||
# Get author's books
|
||||
|
||||
0
migrations/README
Executable file → Normal file
0
migrations/README
Executable file → Normal file
0
migrations/env.py
Executable file → Normal file
0
migrations/env.py
Executable file → Normal file
0
migrations/script.py.mako
Executable file → Normal file
0
migrations/script.py.mako
Executable file → Normal file
@@ -1,7 +1,7 @@
|
||||
[tool.poetry]
|
||||
name = "library-service"
|
||||
version = "0.1.1"
|
||||
description = "This is a sample API for managing authors and books."
|
||||
name = "LibraryAPI"
|
||||
version = "0.1.2"
|
||||
description = "Это простое API для управления авторами и книгами."
|
||||
authors = ["wowlikon"]
|
||||
readme = "README.md"
|
||||
packages = [{ include = "library_service" }]
|
||||
|
||||
@@ -9,10 +9,14 @@ from tests.test_misc import setup_database
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
def make_relationship(author_id, book_id):
|
||||
def make_authorbook_relationship(author_id, book_id):
|
||||
response = client.post("/relationships/author-book", params={"author_id": author_id, "book_id": book_id})
|
||||
assert response.status_code == 200, "Invalid response status"
|
||||
|
||||
def make_genrebook_relationship(author_id, book_id):
|
||||
response = client.post("/relationships/genre-book", params={"genre_id": author_id, "book_id": book_id})
|
||||
assert response.status_code == 200, "Invalid response status"
|
||||
|
||||
def test_prepare_data(setup_database):
|
||||
response = client.post("/books", json={"title": "Test Book 1", "description": "Test Description 1"})
|
||||
response = client.post("/books", json={"title": "Test Book 2", "description": "Test Description 2"})
|
||||
@@ -22,11 +26,11 @@ def test_prepare_data(setup_database):
|
||||
response = client.post("/authors", json={"name": "Test Author 2"})
|
||||
response = client.post("/authors", json={"name": "Test Author 3"})
|
||||
|
||||
make_relationship(1, 1)
|
||||
make_relationship(2, 1)
|
||||
make_relationship(1, 2)
|
||||
make_relationship(2, 3)
|
||||
make_relationship(3, 3)
|
||||
make_authorbook_relationship(1, 1)
|
||||
make_authorbook_relationship(2, 1)
|
||||
make_authorbook_relationship(1, 2)
|
||||
make_authorbook_relationship(2, 3)
|
||||
make_authorbook_relationship(3, 3)
|
||||
|
||||
response = client.get("/relationships/author-book")
|
||||
assert response.status_code == 200, "Invalid response status"
|
||||
|
||||
Reference in New Issue
Block a user