Skip to content
This repository was archived by the owner on Jul 3, 2025. It is now read-only.
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
91 changes: 27 additions & 64 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,27 @@
# Вакансия :: Программист Python

Разработка бизнес-системы с использованием веб-технологий. Автоматизация сервисов с большим количеством пользователей

## От вас

### Обязательно

- Знание синтаксиса языка Python
- Опыт разработки на Python не менее 1 года
- Базовые знания принципов работы Web
- Желание работать в команде и развиваться

### Приветствуется

- Навыки работы с Flask, Sanic, FastAPI
- Опыт работы с БД: PostgreSQL, MS SQL, MongoDB, ClickHouse
- Опыт разработки под ОС семейства GNU Linux, знание основных команд
- Работа с системами управления исходным кодом Git
- Знания базовых принципов разработки (тестирование, рефакторинг, Code Review, CI/CD)

### Будет круто, но не обязательно

- Знание английского языка на уровне чтения технической документации
- Участие в разработке Open Source проектов
- Наличие профиля на GitHub, Stack Overflow
- Наличие проектов которые можете показать нам
- Разработка с использованием TypeScript, знание современных frontend-библиотек и подходов к разработке

## У нас

- Полный рабочий день, гибкий обед и начало рабочего дня
- Полностью «белая» заработная плата с возможностью увеличения в процессе работы (зависит от отдачи сотрудника)
- Полис ДМС
- Дружелюбная команда с юмором, готовая поддержать и помочь
- Интересный проект и необычные задачи. Рутина тоже есть, но мы нацелены именно на продуктив
- Возможность одновременно участвовать в разных проектах и развивать другие компетенции (TS и все модное)
- Попробовать современные тренды и практики в разработке ПО
- Никаких опенспейсов и кубиклов, а комфортное пространство в центре Тюмени
- Готовы делиться опытом и знаниями, если вы готовы их получать

 

Если вакансия вас заинтересовала, но есть недопонимания и вопросы, свяжитесь с нами - обсудим, договоримся.
Большим плюсом будет выполнение тестового задания.
Если у вас есть опыт работы с 1С, то эта вакансия не для вас.

## Тестовое задание

Решение принимается в виде PR к текущему проекту.

Есть несколько рабочих сервисов, у каждого сервиса есть состояние работает/не работает/работает нестабильно.

Требуется написать API который:

1. Получает и сохраняет данные: имя, состояние, описание
2. Выводит список сервисов с актуальным состоянием
3. По имени сервиса выдает историю изменения состояния и все данные по каждому состоянию

Дополнительным плюсом будет

1. По указанному интервалу выдается информация о том сколько не работал сервис и считать SLA в процентах до 3-й запятой

Вывод всех данных должен быть в формате JSON
# Python TEST TASK for itpc.ru
Этот проект представляет собой API, которое позволяет выполнять следующие действия:

- Сохранение данных: API принимает данные, включая имя сервиса, его текущее состояние и описание, и сохраняет их в базе данных.
- Вывод списка сервисов с актуальным состоянием: API предоставляет эндпоинт для получения списка сервисов с их текущим состоянием.
- История изменения состояния: По имени сервиса API позволяет получать историю изменения состояния и всю доступную информацию по каждому состоянию сервиса.

## Ресурсы
- [FastAPI](https://fastapi.tiangolo.com/)
- [SQLalchemy](https://www.sqlalchemy.org/)
- [Pydantic](https://docs.pydantic.dev/latest/)
- [PostgreSQL](https://www.postgresql.org/)


## Документация
Установка зависимостей:
```
pip install -r reuirements.txt
```
Запуск через main.py, либо через консоль:
```
uvicorn main:app --reload
```


После запуска документация доступна по адресу http://127.0.0.1:8000/docs/
Реализовано через OpenAPI(Swagger)
Binary file added __pycache__/database.cpython-310.pyc
Binary file not shown.
Binary file added __pycache__/main.cpython-310.pyc
Binary file not shown.
Binary file added __pycache__/settings.cpython-310.pyc
Binary file not shown.
58 changes: 58 additions & 0 deletions database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from sqlalchemy.exc import OperationalError, ResourceClosedError
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from schemas.base import Base
from settings import settings
import asyncpg
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import sessionmaker

# SQLALCHEMY_URL = settings.SQLALCHEMY_URL

SQLALCHEMY_URL = "postgresql+asyncpg://postgres:postgres@localhost:5432/postgres10"
engine = create_async_engine(SQLALCHEMY_URL, echo=False, pool_pre_ping=True)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)



class get_db:
def __init__(self):
pass

async def create_tables(self) -> None:
try:
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
except OperationalError as error:
pass

async def drop_tables(self) -> None:
try:
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
except OperationalError as error:
pass

async def insert(self, stmt: Base) -> None:
try:
async with async_session() as session:
async with session.begin():
session.add(stmt)
await session.commit()
except OperationalError as error:
pass

async def execute(self, stmt: Base) -> list | None:
try:
async with async_session() as session:
async with session.begin():
result = await session.execute(stmt)
try:
result = [u._asdict() for u in result.all()]
except ResourceClosedError:
return None
return result if len(result) > 0 else None

except OperationalError as error:
pass
34 changes: 34 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

import routers
from database import get_db
from settings import settings


app = FastAPI()


app.add_middleware(
CORSMiddleware,
allow_origins=['*'],
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
)

app.include_router(routers.router)


@app.on_event('startup')
async def startup_event():
database = get_db()
if settings.DROP_DATABASE:
await database.drop_tables()
await database.create_tables()


if __name__ == '__main__':

uvicorn.run("main:app", host='127.0.0.1', port=8000, reload=True)
Empty file added models/__init__.py
Empty file.
Binary file added models/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file added models/__pycache__/activity.cpython-310.pyc
Binary file not shown.
27 changes: 27 additions & 0 deletions models/activity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from datetime import datetime
from enum import Enum
from uuid import UUID, uuid4

from pydantic import BaseModel, Field


class ServiceStatus(str, Enum):
ACTIVE = 'ACTIVE'
UNSTABLE = 'UNSTABLE'
STOPPED = 'STOPPED'



class CurrentActivity(BaseModel):
service_name: str = Field(example='Service_name')
status: ServiceStatus = Field(default=ServiceStatus.ACTIVE)
description: str = Field(example='Server is Active ')


class ServicesList(CurrentActivity):
status_map: dict


class ServicesLog(CurrentActivity):
log_uuid: UUID = Field(default_factory=uuid4)
created_date: datetime
27 changes: 27 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Python TEST TASK for itpc.ru
Этот проект представляет собой API, которое позволяет выполнять следующие действия:

- Сохранение данных: API принимает данные, включая имя сервиса, его текущее состояние и описание, и сохраняет их в базе данных.
- Вывод списка сервисов с актуальным состоянием: API предоставляет эндпоинт для получения списка сервисов с их текущим состоянием.
- История изменения состояния: По имени сервиса API позволяет получать историю изменения состояния и всю доступную информацию по каждому состоянию сервиса.

## Ресурсы
- [FastAPI](https://fastapi.tiangolo.com/)
- [SQLalchemy](https://www.sqlalchemy.org/)
- [Pydantic](https://docs.pydantic.dev/latest/)
- [PostgreSQL](https://www.postgresql.org/)


## Документация
Установка зависимостей:
```
pip install -r reuirements.txt
```
Запуск через main.py, либо через консоль:
```
uvicorn main:app --reload
```


После запуска документация доступна по адресу http://127.0.0.1:8000/docs/
Реализовано через OpenAPI(Swagger)
35 changes: 35 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
annotated-types==0.6.0
anyio==3.7.1
asyncpg==0.28.0
certifi==2023.7.22
click==8.1.7
colorama==0.4.6
dnspython==2.4.2
email-validator==2.1.0.post1
exceptiongroup==1.1.3
fastapi==0.104.0
greenlet==3.0.1
h11==0.14.0
httpcore==0.18.0
httptools==0.6.1
httpx==0.25.0
idna==3.4
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
orjson==3.9.10
pydantic==2.4.2
pydantic-extra-types==2.1.0
pydantic-settings==2.0.3
pydantic_core==2.10.1
python-dotenv==1.0.0
python-multipart==0.0.6
PyYAML==6.0.1
sniffio==1.3.0
SQLAlchemy==2.0.22
starlette==0.27.0
typing_extensions==4.8.0
ujson==5.8.0
uvicorn==0.23.2
watchfiles==0.21.0
websockets==12.0
7 changes: 7 additions & 0 deletions routers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from fastapi import APIRouter

from routers import log_router

router = APIRouter()

router.include_router(log_router.router)
Binary file added routers/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file added routers/__pycache__/log_router.cpython-310.pyc
Binary file not shown.
33 changes: 33 additions & 0 deletions routers/log_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from typing import List
from fastapi import APIRouter, Depends

from models.activity import CurrentActivity, ServicesList, ServicesLog
from services.log_service import LogService
from services.service_service import ServicesService

router = APIRouter(prefix='/logs', tags=['Logs'])

"""Фиксируем активность сервера"""
@router.post('/save_logs/')
async def log_activity(
data: CurrentActivity,
activity_service: LogService = Depends(),
):

return await activity_service.log_activity(data)

"""Список всех серверов"""
@router.get('/list_services/', response_model=List[ServicesList] | None)
async def list_services(
service_service: ServicesService = Depends(),
):

return await service_service.list_services()

"""Логи сервера"""
@router.get('/list_logs/', response_model=List[ServicesLog])
async def list_logs(
service_name: str,
activity_service: LogService = Depends(),
):
return await activity_service.list_logs(service_name)
2 changes: 2 additions & 0 deletions schemas/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from schemas.services_logs_schemas import ServiceLogDB
from schemas.services_schemas import ServiceDB
Binary file added schemas/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file added schemas/__pycache__/base.cpython-310.pyc
Binary file not shown.
Binary file not shown.
Binary file added schemas/__pycache__/services_schemas.cpython-310.pyc
Binary file not shown.
23 changes: 23 additions & 0 deletions schemas/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from datetime import datetime

from sqlalchemy import BIGINT, TIMESTAMP, String, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.sql.schema import Column


class Base(DeclarativeBase):
type_annotation_map = {
int: BIGINT,
datetime: TIMESTAMP(timezone=False),
str: String(),
}

created_date: Mapped[datetime] = mapped_column(server_default=func.now(), index=True)
update_date: Mapped[datetime] = mapped_column(server_default=func.now(), onupdate=func.now())


def to_sql(pidantic_schemas):
"""Convert Pydantic schemas to column names"""
keys = pidantic_schemas.__fields__.keys()
keys = [Column(key) for key in keys]
return keys
15 changes: 15 additions & 0 deletions schemas/services_logs_schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from uuid import uuid4

from sqlalchemy import UUID
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.sql.schema import ForeignKey

from schemas.base import Base


class ServiceLogDB(Base):
__tablename__ = 'services_logs'
log_uuid: Mapped[UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid4)
service_name = mapped_column(ForeignKey('services.service_name'), nullable=True)
status: Mapped[str] = mapped_column(nullable=False)
description: Mapped[str] = mapped_column(nullable=False, default='')
12 changes: 12 additions & 0 deletions schemas/services_schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column

from schemas.base import Base


class ServiceDB(Base):
__tablename__ = 'services'
service_name: Mapped[str] = mapped_column(primary_key=True)
status: Mapped[str] = mapped_column(index=True)
description: Mapped[str] = mapped_column(nullable=True)
status_map = mapped_column(JSONB, default={})
Empty file added services/__init__.py
Empty file.
Binary file added services/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file added services/__pycache__/log_service.cpython-310.pyc
Binary file not shown.
Binary file not shown.
Loading