Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CI

on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
workflow_dispatch:

jobs:
lint:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ruff

- name: Run ruff linter
run: ruff check .

- name: Run ruff formatter check
run: ruff format --check .
54 changes: 33 additions & 21 deletions git/src/main.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
import json
import os

def load_books(filename='library.json'):

def load_books(filename="library.json"):
"""
Загрузка списка книг из JSON-файла.
Возвращает список книг (каждая книга - это словарь).
"""
if not os.path.isfile(filename):
return []
with open(filename, 'r', encoding='utf-8') as file:
with open(filename, "r", encoding="utf-8") as file:
try:
return json.load(file)
except json.JSONDecodeError:
return []

def save_books(books, filename='library.json'):

def save_books(books, filename="library.json"):
"""
Сохранение списка книг в JSON-файл.
"""
with open(filename, 'w', encoding='utf-8') as file:
with open(filename, "w", encoding="utf-8") as file:
json.dump(books, file, ensure_ascii=False, indent=4)


def list_books(books):
"""
Возвращает строку со списком всех книг.
Expand All @@ -29,29 +32,30 @@ def list_books(books):
return "Библиотека пуста."
result_lines = []
for idx, book in enumerate(books, start=1):
result_lines.append(f"{idx}. {book['title']} | {book['author']} | {book['year']}")
result_lines.append(
f"{idx}. {book['title']} | {book['author']} | {book['year']}"
)
return "\n".join(result_lines)


def add_book(books, title, author, year):
"""
Принимает текущий список книг и данные о новой книге.
Возвращает новый список, в котором добавлена новая книга.
"""
new_book = {
'title': title,
'author': author,
'year': year
}
new_book = {"title": title, "author": author, "year": year}
# Создаём НОВЫЙ список, добавляя new_book
return books + [new_book]


def remove_book(books, title):
"""
Принимает текущий список книг и название книги для удаления.
Возвращает новый список без книги, у которой совпадает название.
"""
# Фильтруем список: оставляем только те книги, у которых название не совпадает с переданным
return [book for book in books if book['title'].lower() != title.lower()]
return [book for book in books if book["title"].lower() != title.lower()]


def search_books(books, keyword):
"""
Expand All @@ -60,13 +64,16 @@ def search_books(books, keyword):
"""
keyword_lower = keyword.lower()
return [
book for book in books
if keyword_lower in book['title'].lower() or keyword_lower in book['author'].lower()
book
for book in books
if keyword_lower in book["title"].lower()
or keyword_lower in book["author"].lower()
]


def main():
"""
Точка входа в программу: здесь мы загружаем книги,
Точка входа в программу: здесь мы загружаем книги,
показываем меню и обрабатываем ввод пользователя.
"""
books = load_books() # Загрузили список книг из JSON
Expand All @@ -81,11 +88,11 @@ def main():

choice = input("Выберите действие (1-5): ").strip()

if choice == '1':
if choice == "1":
print("\nСписок книг:")
print(list_books(books))

elif choice == '2':
elif choice == "2":
print("\nДобавление новой книги:")
title = input("Введите название: ").strip()
author = input("Введите автора: ").strip()
Expand All @@ -97,9 +104,11 @@ def main():
save_books(books) # Сразу сохраняем в файл
print("Книга добавлена!")

elif choice == '3':
elif choice == "3":
print("\nУдаление книги:")
title_to_remove = input("Введите название книги, которую хотите удалить: ").strip()
title_to_remove = input(
"Введите название книги, которую хотите удалить: "
).strip()

new_books = remove_book(books, title_to_remove)
if len(new_books) < len(books):
Expand All @@ -109,22 +118,25 @@ def main():
else:
print("Книга с таким названием не найдена.")

elif choice == '4':
elif choice == "4":
print("\nПоиск книг:")
keyword = input("Введите ключевое слово для поиска (в названии или авторе): ").strip()
keyword = input(
"Введите ключевое слово для поиска (в названии или авторе): "
).strip()
found_books = search_books(books, keyword)
if found_books:
print("\nНайденные книги:")
print(list_books(found_books))
else:
print("Ничего не найдено.")

elif choice == '5':
elif choice == "5":
print("Выход из программы.")
break

else:
print("Некорректный ввод. Попробуйте ещё раз.")


if __name__ == "__main__":
main()
14 changes: 8 additions & 6 deletions simple_backend/orders.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@

class Order:
TAX_RATE = 0.08 # 8% налог
SERVICE_CHARGE = 0.05 # 5% сервисный сбор

def __init__(self, customer):
self.customer = customer
self.dishes = []

def add_dish(self, dish):
if isinstance(dish, Dish):
self.dishes.append(dish)
else:
raise ValueError("Можно добавлять только объекты класса Dish.")

def remove_dish(self, dish):
if dish in self.dishes:
self.dishes.remove(dish)
Expand All @@ -22,7 +21,6 @@ def remove_dish(self, dish):
def calculate_total(self):
return sum(dish.price for dish in self.dishes)


def final_total(self):
total_after_discount = self.apply_discount()
total_with_tax = total_after_discount * (1 + Order.TAX_RATE)
Expand Down Expand Up @@ -53,7 +51,8 @@ def __str__(self):
customer_list = ", ".join([customer.name for customer in self.customers])
dish_list = "\n".join([str(dish) for dish in self.dishes])
return f"Group Order for {customer_list}:\n{dish_list}\nTotal: ${self.final_total():.2f}"



class Dish:
def __init__(self, name, price, category):
self.name = name
Expand All @@ -63,6 +62,7 @@ def __init__(self, name, price, category):
def __str__(self):
return f"Dish: {self.name}, Category: {self.category}, Price: ${self.price:.2f}"


class Customer:
def __init__(self, name, membership="Regular"):
self.name = name
Expand All @@ -75,6 +75,8 @@ def get_discount(self):

def __str__(self):
return f"Customer: {self.name}, Membership: {self.membership}"


# Пример использования

# Создаем блюда
Expand All @@ -101,4 +103,4 @@ def __str__(self):
group_order.add_dish(coffee)

print(group_order) # Вывод информации о групповом заказе
print(f"Split Bill: ${group_order.split_bill():.2f} per person") # Стоимость на каждого
print(f"Split Bill: ${group_order.split_bill():.2f} per person") # Стоимость на каждого
80 changes: 80 additions & 0 deletions simple_backend/src/task_tracker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Task Tracker API

API для управления задачами на FastAPI.

## Как запустить

1. Активировать виртуальное окружение:
```
venv\Scripts\activate
```

2. Установить зависимости:
```
pip install -r requirements.txt
```

3. Запустить сервер:
```
uvicorn main:app --reload
```

4. Открыть http://127.0.0.1:8000/docs

## Что умеет API

- GET /tasks - получить все задачи
- POST /tasks - создать задачу
- PUT /tasks/{id} - обновить задачу
- DELETE /tasks/{id} - удалить задачу

## Про хранение данных

### Хранение в памяти (сейчас)

Что такое хранение состояния - это когда сервер запоминает информацию между запросами. У нас список задач хранится в переменной tasks_db.

Проблемы с хранением в памяти:
- При перезапуске сервера все задачи пропадают
- Нельзя запустить несколько серверов одновременно
- Если данных много, может не хватить памяти
- При сбое все данные теряются

### Хранение в файле

Когда мы переделали на хранение в файле, стало лучше:
- Задачи не пропадают при перезапуске
- Можно сделать резервную копию файла
- Легко посмотреть данные в файле

Но проблемы остались:
- Файл все равно лежит на одном сервере
- Если запустить несколько серверов, у каждого будет свой файл

### Другие способы хранения

База данных - хорошо масштабируется, но сложно настроить
Облачные сервисы - доступно из любого места, но нужен интернет
Redis - очень быстро, но дорого по памяти
Файлы - просто, но не масштабируется

### Stateless сервер

Когда мы используем внешний сервис (jsonbin.io), сервер становится stateless:
- Сервер не хранит данные у себя
- Все данные в облаке
- Можно запустить много серверов

### Состояние гонки

Это когда несколько запросов одновременно меняют одни и те же данные, и получается путаница.

Проблемы в нашем API:
- Два человека могут создать задачи с одинаковым ID
- Один может удалить задачу пока другой ее читает
- При одновременном обновлении изменения могут потеряться

Как исправить:
- Использовать блокировки
- Использовать базу данных с транзакциями
- Обрабатывать запросы по очереди
35 changes: 35 additions & 0 deletions simple_backend/src/task_tracker/base_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from abc import ABC, abstractmethod
import requests
from typing import Any, Dict, Optional

class BaseHTTPClient(ABC):
"""Базовый класс для HTTP клиентов с общими функциями"""

def __init__(self, base_url: str):
self.base_url = base_url

def _make_request(self, method: str, endpoint: str, **kwargs) -> Optional[Dict[Any, Any]]:

"""Общий метод для выполнения HTTP запросов"""
url = f"{self.base_url}{endpoint}"

try:
response = requests.request(method, url, **kwargs)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"Ошибка HTTP запроса: {e}")
return None
except Exception as e:
print(f"Неожиданная ошибка: {e}")
return None

@abstractmethod
def load_tasks(self):
"""Абстрактный метод для загрузки данных"""
pass

@abstractmethod
def save_tasks(self, data):
"""Абстрактный метод для сохранения данных"""
pass
Loading