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
18 changes: 0 additions & 18 deletions gateway/dependencies/auth.py

This file was deleted.

20 changes: 0 additions & 20 deletions gateway/utils.py

This file was deleted.

File renamed without changes.
3 changes: 3 additions & 0 deletions server/data_service/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.venv
__pycache__
.idea

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

пустую строчку в конце

Empty file added server/data_service/.gitkeep
Empty file.
14 changes: 14 additions & 0 deletions server/data_service/Dockerfile
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"]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут и ниже везде, аналогично

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

они тоже имеют шанс на существование

42 changes: 42 additions & 0 deletions server/data_service/requirements.txt
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
47 changes: 47 additions & 0 deletions server/data_service/src/EV_cars.py

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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())
10 changes: 10 additions & 0 deletions server/data_service/src/config.py
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")
Empty file.
47 changes: 47 additions & 0 deletions server/data_service/src/database/cruds.py
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,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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()

23 changes: 23 additions & 0 deletions server/data_service/src/database/database.py

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется такое в каждом сервисе есть. Было бы неплохо в либу унести

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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()
10 changes: 10 additions & 0 deletions server/data_service/src/database/init_db.py
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())
14 changes: 14 additions & 0 deletions server/data_service/src/database/models.py
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)
14 changes: 14 additions & 0 deletions server/data_service/src/database/schemas.py
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
45 changes: 45 additions & 0 deletions server/data_service/src/main.py
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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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)
Loading