-
Notifications
You must be signed in to change notification settings - Fork 0
Pull request for Ilya #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: old_version
Are you sure you want to change the base?
Changes from all commits
06ca026
d4f32a5
cbb63cb
7fb242a
a0cf98c
e1a1bfc
7a4abc2
ec0385c
fc7e0d6
cdcf6a5
fc648a9
415cf05
61516d9
1ecf9f3
71d7a58
773b42f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| .venv | ||
| __pycache__ | ||
| .idea | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| FROM python:3.12.3-slim | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| COPY requirements.txt . | ||
|
|
||
| RUN pip install --no-cache-dir fastapi[standard] && \ | ||
| pip install --no-cache-dir -r requirements.txt | ||
|
|
||
| COPY . . | ||
|
|
||
| WORKDIR /app/src | ||
|
|
||
| CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8003", "--reload"] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Тут и ниже везде, аналогично
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. они тоже имеют шанс на существование |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| annotated-types==0.7.0 | ||
| anyio==4.9.0 | ||
| asyncpg==0.30.0 | ||
| certifi==2025.4.26 | ||
| click==8.1.8 | ||
| colorama==0.4.6 | ||
| dnspython==2.7.0 | ||
| email_validator==2.2.0 | ||
| fastapi==0.115.12 | ||
| fastapi-cli==0.0.7 | ||
| greenlet==3.2.2 | ||
| h11==0.16.0 | ||
| httpcore==1.0.9 | ||
| httptools==0.6.4 | ||
| httpx==0.28.1 | ||
| idna==3.10 | ||
| Jinja2==3.1.6 | ||
| markdown-it-py==3.0.0 | ||
| MarkupSafe==3.0.2 | ||
| mdurl==0.1.2 | ||
| pydantic==2.11.5 | ||
| pydantic_core==2.33.2 | ||
| Pygments==2.19.1 | ||
| python-dotenv==1.1.0 | ||
| python-multipart==0.0.20 | ||
| PyYAML==6.0.2 | ||
| rich==14.0.0 | ||
| rich-toolkit==0.14.6 | ||
| shellingham==1.5.4 | ||
| sniffio==1.3.1 | ||
| SQLAlchemy==2.0.41 | ||
| starlette==0.46.2 | ||
| typer==0.15.4 | ||
| typing-inspection==0.4.1 | ||
| typing_extensions==4.13.2 | ||
| uvicorn==0.34.2 | ||
| watchfiles==1.0.5 | ||
| websockets==15.0.1 | ||
| psycopg2-binary | ||
| beautifulsoup4 | ||
| requests | ||
| SQLAlchemy |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Вынести бы в тулзы или скрипты сервиса. Не относится к коду самого микросервиса |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| from bs4 import BeautifulSoup | ||
| from database.schemas import CarCreate | ||
| import requests | ||
|
|
||
| def parse_ev_cars() -> list: | ||
| result = [] | ||
|
|
||
| for page_idx in range(0, 20): | ||
| url_page = f"https://ev-database.org/#group=vehicle-group&rs-pr=10000_100000&rs-er=0_1000&rs-ld=0_1000&rs-ac=2_23&rs-dcfc=0_300&rs-ub=10_200&rs-tw=0_2500&rs-ef=100_350&rs-sa=-1_5&rs-w=1000_3500&rs-c=0_5000&rs-y=2010_2030&s=1&p={page_idx}-50" | ||
|
|
||
| headers = { | ||
| 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' | ||
| } | ||
|
|
||
| try: | ||
| response = requests.get(url_page, headers=headers) | ||
| soup = BeautifulSoup(response.text, 'html.parser') | ||
|
|
||
| cars_list = soup.find_all('div', {'class': 'list-item', 'data-jplist-item': ''}) | ||
|
|
||
| for idx, car in enumerate(cars_list): | ||
| title_link = car.find('a', class_='title') | ||
| if title_link: | ||
| spans = title_link.find_all('span') | ||
| full_name = ' '.join(span.text.strip() for span in spans) | ||
| else: | ||
| print(f'Отсуствует название для {idx + 1} машины') | ||
|
|
||
| specs = car.find('div', class_='specs') | ||
|
|
||
| consumpting = specs.find('div', {'data-tooltip': "Efficiency under standardized conditions"}).find('span', class_='efficiency').text.strip() | ||
| battery_capacity = specs.find('div', {'data-tooltip': "Useable battery capacity."}).find('span', class_='battery_p').text.strip() | ||
| hidden_info = car.find('div', class_='hidden') | ||
| type_charger = hidden_info.find('span', attrs={'title': lambda x: x and 'plug' in x}).text.strip() | ||
|
|
||
| result.append(CarCreate(name=full_name, | ||
| battery_capacity=battery_capacity, | ||
| consumpting=consumpting, | ||
| type_charger=type_charger)) | ||
| except Exception as e: | ||
| print(f"Ошибка на странице {page_idx}: {e}") | ||
|
|
||
| print(f"Была собрана информация о {len(cars_list)} автомобилях") | ||
| return result | ||
|
|
||
| if __name__ == "__main__": | ||
| print(parse_ev_cars()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import os | ||
| from abc import ABC | ||
| from dataclasses import asdict, dataclass | ||
|
|
||
|
|
||
| class CfgBase(ABC): | ||
| dict: callable = asdict | ||
|
|
||
| class PostgresCfg(CfgBase): | ||
| url: str = os.getenv("DATABASE_URL") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| from database.schemas import CarCreate, CarGet | ||
| from database.models import Car | ||
|
|
||
| from sqlalchemy.ext.asyncio import AsyncSession | ||
| from sqlalchemy.future import select | ||
|
|
||
| async def add_car(db: AsyncSession, car: CarCreate) -> Car: | ||
| new_car = Car(name=car.name, | ||
| battery_capacity=car.battery_capacity, | ||
| consumpting=car.consumpting, | ||
| type_charger=car.type_charger) | ||
| db.add(new_car) | ||
| await db.flush() | ||
| return new_car | ||
|
|
||
| async def add_cars(db: AsyncSession, cars: list[CarCreate]) -> list[Car]: | ||
| car_objects = [ | ||
| Car(name=car.name, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Перенос строки |
||
| battery_capacity=car.battery_capacity, | ||
| consumpting=car.consumpting, | ||
| type_charger=car.type_charger | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Запятую после type_charger |
||
| ) | ||
| for car in cars | ||
| ] | ||
|
|
||
| db.add_all(car_objects) | ||
| await db.flush() | ||
| return car_objects | ||
|
|
||
| async def get_all_cars(db: AsyncSession) -> list[CarGet]: | ||
| stmt = select(Car) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. stmt?) |
||
|
|
||
| result = await db.execute(stmt) | ||
| return result.scalars().all() | ||
|
|
||
| async def get_car_by_name(db: AsyncSession, name: str) -> CarGet | None: | ||
| stmt = select(Car).where(Car.name == name) | ||
|
|
||
| result = await db.execute(stmt) | ||
| return result.scalar_one_or_none() | ||
|
|
||
| async def get_car(db: AsyncSession, id: int) -> CarGet | None: | ||
| stmt = select(Car).where(Car.id == id) | ||
|
|
||
| result = await db.execute(stmt) | ||
| return result.scalar_one_or_none() | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Кажется такое в каждом сервисе есть. Было бы неплохо в либу унести
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. справедливо, если будет время, то вынесу |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| from typing import Iterator, Any, AsyncGenerator | ||
| from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker | ||
| from config import PostgresCfg | ||
|
|
||
| engine = create_async_engine( | ||
| url=PostgresCfg.url, | ||
| future=True, | ||
| echo=False | ||
| ) | ||
|
|
||
| async_session = async_sessionmaker(engine, expire_on_commit=False, class_=AsyncSession) | ||
|
|
||
|
|
||
| async def get_db() -> AsyncGenerator[AsyncSession, None]: | ||
| session: AsyncSession = async_session() | ||
| try: | ||
| yield session | ||
| await session.commit() | ||
| except Exception as exc: | ||
| await session.rollback() | ||
| raise exc | ||
| finally: | ||
| await session.close() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import asyncio | ||
| from database.database import engine | ||
| from database.models import Base | ||
|
|
||
| async def init_models(): | ||
| async with engine.begin() as conn: | ||
| await conn.run_sync(Base.metadata.create_all) | ||
|
|
||
| if __name__ == "__main__": | ||
| asyncio.run(init_models()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| from sqlalchemy import String | ||
| from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column | ||
|
|
||
| class Base(DeclarativeBase): | ||
| pass | ||
|
|
||
| class Car(Base): | ||
| __tablename__ = "cars" | ||
|
|
||
| id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) | ||
| name: Mapped[str] = mapped_column(String, index=True, nullable=False) | ||
| battery_capacity: Mapped[str] = mapped_column(String, nullable=False) | ||
| consumpting: Mapped[str] = mapped_column(String, nullable=False) | ||
| type_charger: Mapped[str] = mapped_column(String, nullable=False) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| from pydantic import BaseModel | ||
|
|
||
| class CarCreate(BaseModel): | ||
| name: str | ||
| battery_capacity: str | ||
| consumpting: str | ||
| type_charger: str | ||
|
|
||
| class CarGet(BaseModel): | ||
| id: int | ||
| name: str | ||
| battery_capacity: str | ||
| consumpting: str | ||
| type_charger: str |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| from fastapi import FastAPI, Depends, status | ||
| from sqlalchemy.ext.asyncio import AsyncSession | ||
| from database.database import get_db | ||
| from EV_cars import parse_ev_cars | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Лишнее? |
||
| from database.init_db import init_models | ||
| from contextlib import asynccontextmanager | ||
| from database import cruds | ||
| from database.schemas import CarCreate, CarGet | ||
|
|
||
|
|
||
| @asynccontextmanager | ||
| async def lifespan(app: FastAPI): | ||
| try: | ||
| print("Starting up") | ||
| app.add_middleware( | ||
| allow_methods=["GET", "POST"], | ||
| allow_origins=["*"] | ||
| ) | ||
| await init_models() | ||
| yield | ||
| finally: | ||
| print("Shutting down...") | ||
|
|
||
| app = FastAPI(lifespan=lifespan, title="EV Route Car Service") | ||
|
|
||
|
|
||
| @app.post("/car", response_model=CarCreate, status_code=status.HTTP_201_CREATED) | ||
| async def add_car(car: CarCreate, db: AsyncSession = Depends(get_db)): | ||
| return await cruds.add_car(db, car) | ||
|
|
||
| @app.post("/cars", response_model=list[CarCreate], status_code=status.HTTP_201_CREATED) | ||
| async def add_cars(cars: list[CarCreate], db: AsyncSession = Depends(get_db)): | ||
| return await cruds.add_cars(db, cars) | ||
|
|
||
| @app.get("/car", response_model=CarGet) | ||
| async def get_car_by_name(name: str, db: AsyncSession = Depends(get_db)): | ||
| return await cruds.get_car_by_name(db, name) | ||
|
|
||
| @app.get("/car/{car_id}", response_model=CarGet) | ||
| async def get_car(car_id: int, db: AsyncSession = Depends(get_db)): | ||
| return await cruds.get_car(db, car_id) | ||
|
|
||
| @app.get("/cars", response_model=CarGet) | ||
| async def get_cars(db: AsyncSession = Depends(get_db)): | ||
| return await cruds.get_all_cars(db) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
пустую строчку в конце