Skip to content

Commit 875e607

Browse files
committed
Change: DBManager now works on sqlalchemy instead of sqlite3
1 parent 7ccf246 commit 875e607

4 files changed

Lines changed: 195 additions & 13 deletions

File tree

src/api/api.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ class URL(BaseModel):
99

1010
class PublicAPI:
1111
title = "API Server"
12-
version = "0.0.1"
12+
version = "0.1.0"
1313
description="ParkTrack server API built with FastAPI"
1414

1515
def __init__(self, db_manager):
1616
self.db_manager = db_manager
17-
self.db_manager.connect()
1817

1918
self.app = FastAPI(
2019
title=self.title,

src/db_manager/db_manager.py

Lines changed: 115 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,119 @@
1-
import sqlite3
1+
from sqlalchemy import create_engine, inspect
2+
from sqlalchemy.orm import sessionmaker, Session
3+
from sqlalchemy.exc import SQLAlchemyError
4+
import contextlib
5+
from typing import Generator, Optional
6+
import os
7+
8+
from .models import Base
29

310
class DBManager:
4-
def __init__(self, db_name):
5-
self.db_name = db_name
6-
self.conn = None
7-
self.cursor = None
8-
9-
def connect(self):
10-
self.conn = sqlite3.connect(self.db_name)
11-
self.cursor = self.conn.cursor()
11+
def __init__(self, database_url: Optional[str] = None):
12+
self.database_url = database_url or self._get_default_database_url()
13+
self.engine = None
14+
self.SessionLocal = None
15+
self._initialize_database()
16+
17+
def _get_default_database_url(self) -> str:
18+
"""Захардкодил URL базы данных по умолчанию"""
19+
# Для SQLite
20+
return "sqlite:///./parktrack.db"
21+
22+
# Для PostgreSQL:
23+
# return "postgresql://user:password@localhost/parktrack"
24+
25+
def _initialize_database(self):
26+
"""Инициализация движка и сессии"""
27+
try:
28+
# Для SQLite нужно special pragma для Foreign Keys
29+
connect_args = {}
30+
if self.database_url.startswith("sqlite"):
31+
connect_args = {"check_same_thread": False}
32+
33+
self.engine = create_engine(
34+
self.database_url,
35+
connect_args=connect_args,
36+
echo=True, # Для тестирования
37+
pool_pre_ping=True # Загадочно
38+
)
39+
40+
self.SessionLocal = sessionmaker(
41+
autocommit=False,
42+
autoflush=False,
43+
bind=self.engine
44+
)
45+
46+
if not self._check_tables_exist():
47+
print(f"Gotta setup database real quick hold on...")
48+
self._create_tables()
49+
50+
print(f"Database initialized successfully: {self.database_url}")
51+
52+
except Exception as e:
53+
print(f"Database initialization failed: {e}")
54+
raise
55+
56+
def _check_tables_exist(self) -> bool:
57+
try:
58+
inspector = inspect(self.engine)
59+
existing_tables = inspector.get_table_names()
60+
61+
# Получаем список таблиц, которые должны быть созданы
62+
required_tables = Base.metadata.tables.keys()
63+
64+
print(f"Existing tables: {existing_tables}")
65+
print(f"Required tables: {list(required_tables)}")
66+
67+
# Проверяем, что ВСЕ нужные таблицы существуют
68+
for table_name in required_tables:
69+
if table_name not in existing_tables:
70+
print(f"Table {table_name} not found")
71+
return False
72+
73+
return True
74+
75+
except Exception as e:
76+
print(f"Error checking tables: {e}")
77+
return False
78+
79+
def _create_tables(self):
80+
try:
81+
print("Creating database tables...")
82+
Base.metadata.create_all(bind=self.engine)
83+
print("Tables created successfully")
84+
except Exception as e:
85+
print(f"Error creating tables: {e}")
86+
raise
87+
88+
@contextlib.contextmanager
89+
def get_session(self) -> Generator[Session, None, None]:
90+
"""Контекстный менеджер для получения сессии"""
91+
session = self.SessionLocal()
92+
try:
93+
yield session
94+
session.commit()
95+
except Exception:
96+
session.rollback()
97+
raise
98+
finally:
99+
session.close()
12100

13101
def check_connection(self) -> bool:
14-
return True
102+
"""Проверить соединение с базой данных"""
103+
try:
104+
with self.get_session() as session:
105+
# Простой запрос для проверки соединения
106+
session.execute("SELECT 1")
107+
return True
108+
except SQLAlchemyError as e:
109+
print(f"Database connection check failed: {e}")
110+
return False
111+
112+
def get_db(self) -> Generator[Session, None, None]:
113+
return self.get_session()
114+
115+
def execute_raw_sql(self, query: str, params: dict = None):
116+
"""Выполнить сырой SQL запрос"""
117+
with self.get_session() as session:
118+
result = session.execute(query, params or {})
119+
return result

src/db_manager/models.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Numeric, ForeignKey
2+
from sqlalchemy.ext.declarative import declarative_base
3+
from sqlalchemy.orm import relationship
4+
from datetime import datetime, timezone
5+
6+
Base = declarative_base()
7+
8+
class Camera(Base):
9+
__tablename__ = 'cameras'
10+
11+
id = Column(Integer, primary_key=True, autoincrement=True)
12+
title = Column(String(120))
13+
is_active = Column(Boolean, default=False)
14+
pixels_height = Column(Integer)
15+
pixels_width = Column(Integer)
16+
created_at = Column(DateTime, default=timezone.utc)
17+
updated_at = Column(DateTime, default=datetime.now(timezone.utc), onupdate=timezone.utc)
18+
19+
parking_zones = relationship("ParkingZone", back_populates="camera")
20+
cars = relationship("Car", back_populates="camera")
21+
22+
def __repr__(self):
23+
return f"<Camera(id={self.id}, title='{self.title}', is_active={self.is_active})>"
24+
25+
class ParkingZone(Base):
26+
__tablename__ = 'parking_zones'
27+
28+
id = Column(Integer, primary_key=True, autoincrement=True)
29+
zone_type = Column(String(50))
30+
parking_lots_count = Column(Integer)
31+
camera_id = Column(Integer, ForeignKey('cameras.id'))
32+
33+
camera = relationship("Camera", back_populates="parking_zones")
34+
points = relationship("ParkingZonePoint", back_populates="parking_zone")
35+
36+
def __repr__(self):
37+
return f"<ParkingZone(id={self.id}, zone_type='{self.zone_type}', camera_id={self.camera_id})>"
38+
39+
class ParkingZonePoint(Base):
40+
__tablename__ = 'parking_zones_points'
41+
42+
id = Column(Integer, primary_key=True, autoincrement=True)
43+
parking_zone_id = Column(Integer, ForeignKey('parking_zones.id'))
44+
x_component = Column(Numeric(6, 5))
45+
y_component = Column(Numeric(6, 5))
46+
latitude = Column(Numeric(11, 8))
47+
longitude = Column(Numeric(11, 8))
48+
49+
parking_zone = relationship("ParkingZone", back_populates="points")
50+
51+
def __repr__(self):
52+
return f"<ParkingZonePoint(id={self.id}, zone_id={self.parking_zone_id}, x={self.x_component}, y={self.y_component})>"
53+
54+
class Car(Base):
55+
__tablename__ = 'cars'
56+
57+
id = Column(Integer, primary_key=True, autoincrement=True)
58+
confidence_rate = Column(Numeric(5, 4))
59+
camera_id = Column(Integer, ForeignKey('cameras.id'))
60+
61+
camera = relationship("Camera", back_populates="cars")
62+
points = relationship("CarPoint", back_populates="car")
63+
64+
def __repr__(self):
65+
return f"<Car(id={self.id}, confidence={self.confidence_rate}, camera_id={self.camera_id})>"
66+
67+
class CarPoint(Base):
68+
__tablename__ = 'car_points'
69+
70+
id = Column(Integer, primary_key=True, autoincrement=True)
71+
car_id = Column(Integer, ForeignKey('cars.id'))
72+
x_component = Column(Numeric(6, 5))
73+
y_component = Column(Numeric(6, 5))
74+
75+
car = relationship("Car", back_populates="points")
76+
77+
def __repr__(self):
78+
return f"<CarPoint(id={self.id}, car_id={self.car_id}, x={self.x_component}, y={self.y_component})>"

src/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from db_manager.db_manager import DBManager
33

44
def main():
5-
api_server = PublicAPI(db_manager=DBManager("mock"))
5+
api_server = PublicAPI(DBManager())
66
api_server.run(URL(host="127.0.0.1", port=8000))
77

88
if __name__ == "__main__":

0 commit comments

Comments
 (0)