Compare commits
57 Commits
Author | SHA1 | Date |
---|---|---|
Bertrand Benjamin | b8eef461f8 | |
Bertrand Benjamin | a953631d19 | |
Bertrand Benjamin | ccb59975f7 | |
Bertrand Benjamin | 0ebc24ff29 | |
Bertrand Benjamin | 07595f1fd8 | |
Bertrand Benjamin | f3302e2132 | |
Bertrand Benjamin | 3b98a881e7 | |
Bertrand Benjamin | 356db507eb | |
Bertrand Benjamin | 066990d109 | |
Bertrand Benjamin | 94c942d055 | |
Bertrand Benjamin | 12b3220170 | |
Bertrand Benjamin | 6eec1f83bb | |
Bertrand Benjamin | 36e90a004e | |
Bertrand Benjamin | a95ce91b29 | |
Bertrand Benjamin | b8a769b96d | |
Bertrand Benjamin | 6f5b479426 | |
Bertrand Benjamin | 5cf062c7a0 | |
Bertrand Benjamin | c541d0063f | |
Bertrand Benjamin | c7eb8e44d2 | |
Bertrand Benjamin | febe686688 | |
Bertrand Benjamin | ccf1655cf4 | |
Bertrand Benjamin | 4a16444835 | |
Bertrand Benjamin | 9ec183c3a5 | |
Bertrand Benjamin | e5a50e0be8 | |
Bertrand Benjamin | fd567c292d | |
Bertrand Benjamin | 4e13d0e32f | |
Bertrand Benjamin | de9a4bc4be | |
Bertrand Benjamin | 2dc1cf6fb8 | |
Bertrand Benjamin | 7d908775a9 | |
Bertrand Benjamin | dfd0bb2b81 | |
Bertrand Benjamin | f73ad3a34d | |
Bertrand Benjamin | fe92433311 | |
Bertrand Benjamin | 6fbe238e59 | |
Bertrand Benjamin | 566ba8e2f5 | |
Bertrand Benjamin | 2444bf38a1 | |
Bertrand Benjamin | 6f486a6f3c | |
Bertrand Benjamin | 5735d344c5 | |
Bertrand Benjamin | b3bb1f0cc8 | |
Bertrand Benjamin | 21821c275e | |
Bertrand Benjamin | 8911f8ddeb | |
Bertrand Benjamin | 4e8addd6cb | |
Bertrand Benjamin | ba750989d8 | |
Bertrand Benjamin | 309e9627e7 | |
Bertrand Benjamin | b4df4d6c09 | |
Bertrand Benjamin | 723092c38f | |
Bertrand Benjamin | 77b26f1180 | |
Bertrand Benjamin | b50227a658 | |
Bertrand Benjamin | 4f4fc53253 | |
Bertrand Benjamin | 67b9a1e8a0 | |
Bertrand Benjamin | a3c44321bf | |
Bertrand Benjamin | e496d86828 | |
Bertrand Benjamin | c4fcb6a0ef | |
Bertrand Benjamin | a2a0269f39 | |
Bertrand Benjamin | 64c28427c9 | |
Bertrand Benjamin | 997e194b6d | |
Bertrand Benjamin | 6a44ca033f | |
Bertrand Benjamin | a7aeb12844 |
|
@ -122,3 +122,6 @@ dmypy.json
|
|||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# temporary database
|
||||
sqlite.db
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.0.1
|
||||
hooks:
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
- id: mixed-line-ending
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.10.1
|
||||
hooks:
|
||||
- id: isort
|
||||
args: ["--profile", "black"]
|
23
README.md
23
README.md
|
@ -1,8 +1,19 @@
|
|||
# Encore une autre façon d'enregistrer et d'analyser mes notes
|
||||
# Recopytex
|
||||
|
||||
Cette fois ci, on utilise:
|
||||
One more rewrite of Opytex. This time, it more a learning project than a operational project.
|
||||
|
||||
- Des fichiers csv pour stocker les notes
|
||||
- Des fichiers yaml pour les infos sur les élèves
|
||||
- Des notebooks pour l'analyse
|
||||
- Papermill pour produire les notesbooks à partir de template
|
||||
I am following the book *Architecture Patterns with Python* by Harry J.W. Percival and Bob Gregory. Then the project will follow TTD method using DDD.
|
||||
|
||||
## Backend API
|
||||
|
||||
It uses **fastapi**, **sqlalchemy**.
|
||||
|
||||
### Installing
|
||||
|
||||
poetry install
|
||||
|
||||
### Fire up
|
||||
|
||||
uvicorn backend.api.main:app --reload
|
||||
|
||||
## Frontend
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
from sqlalchemy import Column, ForeignKey, MetaData, String, Table
|
||||
from sqlalchemy.orm import backref, registry, relationship
|
||||
|
||||
from backend.model.assessment import Assessment
|
||||
from backend.model.student import Student
|
||||
from backend.model.tribe import Tribe
|
||||
|
||||
metadata = MetaData()
|
||||
mapper_registry = registry()
|
||||
|
||||
tribes_table = Table(
|
||||
"tribes",
|
||||
metadata,
|
||||
Column("name", String(255), primary_key=True),
|
||||
Column("level", String(255)),
|
||||
)
|
||||
|
||||
assessments_table = Table(
|
||||
"assessments",
|
||||
metadata,
|
||||
Column("id", String(255), primary_key=True),
|
||||
Column("name", String(255)),
|
||||
Column("tribe_name", String(255), ForeignKey("tribes.name")),
|
||||
)
|
||||
|
||||
students_table = Table(
|
||||
"students",
|
||||
metadata,
|
||||
Column("id", String(255), primary_key=True),
|
||||
Column("name", String(255)),
|
||||
Column("tribe_name", String(255), ForeignKey("tribes.name")),
|
||||
)
|
||||
|
||||
|
||||
def start_mappers():
|
||||
tribes_mapper = mapper_registry.map_imperatively(
|
||||
Tribe,
|
||||
tribes_table,
|
||||
properties={
|
||||
"students": relationship(
|
||||
Student, backref="tribes", order_by=students_table.c.id
|
||||
),
|
||||
"assessments": relationship(
|
||||
Assessment, backref="tribes", order_by=assessments_table.c.id
|
||||
),
|
||||
},
|
||||
)
|
||||
students_mapper = mapper_registry.map_imperatively(Student, students_table)
|
||||
assessments_mapper = mapper_registry.map_imperatively(Assessment, assessments_table)
|
|
@ -0,0 +1,33 @@
|
|||
import sqlite3
|
||||
|
||||
|
||||
def create_tribe_table(conn) -> None:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS tribes(
|
||||
name VARCHAR PRIMARY KEY UNIQUE,
|
||||
level VARCHAR
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def create_student_table(conn) -> None:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS students(
|
||||
id VARCHAR(500) PRIMARY KEY UNIQUE,
|
||||
name VARCHAR,
|
||||
tribe_name VARCHAR
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def create_db(conn) -> None:
|
||||
create_tribe_table(conn)
|
||||
create_student_table(conn)
|
|
@ -0,0 +1,194 @@
|
|||
import sqlite3
|
||||
|
||||
from fastapi import FastAPI, status
|
||||
from fastapi.responses import JSONResponse, RedirectResponse, Response
|
||||
|
||||
from backend.adapters.sqlite import create_db
|
||||
from backend.api.model import StudentModel, TribeModel
|
||||
from backend.model.student import Student
|
||||
from backend.model.tribe import Tribe
|
||||
from backend.repository.student_sqlite_repository import StudentSQLiteRepository
|
||||
from backend.repository.tribe_sqlite_repository import (
|
||||
TribeRepositoryError,
|
||||
TribeSQLiteRepository,
|
||||
)
|
||||
from backend.service import services
|
||||
from backend.service.services import StudentDoesExist, TribeDoesNotExist, TribeExists
|
||||
|
||||
# from sqlalchemy import create_engine
|
||||
# from sqlalchemy.orm import clear_mappers, sessionmaker
|
||||
# import backend.adapters.orm as orm
|
||||
# from backend.repository.tribe_sqlalchemy_repository import TribeSQLAlchemyRepository
|
||||
|
||||
# orm.start_mappers()
|
||||
# engine = create_engine("sqlite:///:memory:")
|
||||
# orm.metadata.create_all(engine)
|
||||
# session = sessionmaker(bind=engine)()
|
||||
# tribe_repo = TribeSQLAlchemyRepository(session)
|
||||
|
||||
conn = sqlite3.connect("sqlite.db")
|
||||
create_db(conn)
|
||||
|
||||
tribe_repo = TribeSQLiteRepository(conn)
|
||||
student_repo = StudentSQLiteRepository(conn)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/tribes", response_class=RedirectResponse, status_code=status.HTTP_302_FOUND)
|
||||
async def post_tribe(item: TribeModel):
|
||||
try:
|
||||
tribe = services.add_tribe(
|
||||
name=item.name, level=item.level, tribe_repo=tribe_repo, conn=conn
|
||||
)
|
||||
except TribeExists:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
content=f"The tribe {item.name} already exists",
|
||||
)
|
||||
|
||||
return f"/tribes/{tribe.name}"
|
||||
|
||||
|
||||
@app.put(
|
||||
"/tribes/{name}", response_class=RedirectResponse, status_code=status.HTTP_302_FOUND
|
||||
)
|
||||
async def put_tribe(name: str, item: TribeModel):
|
||||
try:
|
||||
tribe = services.update_tribe(
|
||||
name=item.name, level=item.level, tribe_repo=tribe_repo, conn=conn
|
||||
)
|
||||
except TribeDoesNotExist:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
content=f"The tribe {name} does not exists",
|
||||
)
|
||||
|
||||
return f"/tribes/{tribe.name}"
|
||||
|
||||
|
||||
@app.delete("/tribes/{name}")
|
||||
async def delete_tribe(name: str):
|
||||
try:
|
||||
services.delete_tribe(name=name, tribe_repo=tribe_repo, conn=conn)
|
||||
except TribeDoesNotExist:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
content=f"The tribe {name} does not exists",
|
||||
)
|
||||
|
||||
return Response(
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
|
||||
|
||||
@app.get("/tribes", response_model=list[TribeModel])
|
||||
async def list_tribes():
|
||||
tribes = tribe_repo.list()
|
||||
|
||||
return [t.to_dict() for t in tribes]
|
||||
|
||||
|
||||
@app.get("/tribes/{name}", response_model=TribeModel)
|
||||
async def get_tribe(name: str):
|
||||
tribe = tribe_repo.get(name)
|
||||
|
||||
return tribe.to_dict()
|
||||
|
||||
|
||||
@app.post(
|
||||
"/students", response_class=RedirectResponse, status_code=status.HTTP_302_FOUND
|
||||
)
|
||||
async def post_student(item: StudentModel):
|
||||
if item.id is not None:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
content=f"You can't post a student with an id. It is already registrered. Use PUT to modify it.",
|
||||
)
|
||||
|
||||
try:
|
||||
student = services.add_student(
|
||||
name=item.name,
|
||||
tribe=item.tribe_name,
|
||||
tribe_repo=tribe_repo,
|
||||
student_repo=student_repo,
|
||||
conn=conn,
|
||||
)
|
||||
except TribeDoesNotExist:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
content=f"The tribe {item.tribe_name} does not exists. You can't add a student in it.",
|
||||
)
|
||||
|
||||
return f"/students/{student.id}"
|
||||
|
||||
|
||||
@app.get("/students/{id}", status_code=status.HTTP_200_OK, response_model=StudentModel)
|
||||
async def get_student(id: str):
|
||||
tribes = tribe_repo.list()
|
||||
student = student_repo.get(id, tribes)
|
||||
return student.to_dict()
|
||||
|
||||
|
||||
@app.get("/students", status_code=status.HTTP_200_OK, response_model=list[StudentModel])
|
||||
async def list_students():
|
||||
tribes = tribe_repo.list()
|
||||
students = student_repo.list(tribes)
|
||||
|
||||
return [t.to_dict() for t in students]
|
||||
|
||||
|
||||
@app.put(
|
||||
"/students/{student_id}",
|
||||
response_class=RedirectResponse,
|
||||
status_code=status.HTTP_302_FOUND,
|
||||
)
|
||||
async def put_student(student_id, item: StudentModel):
|
||||
if student_id != item.id:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=f"Url and student id are the same",
|
||||
)
|
||||
|
||||
try:
|
||||
student = services.update_student(
|
||||
id=item.id,
|
||||
name=item.name,
|
||||
tribe=item.tribe_name,
|
||||
tribe_repo=tribe_repo,
|
||||
student_repo=student_repo,
|
||||
conn=conn,
|
||||
)
|
||||
except TribeDoesNotExist:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
content=f"The tribe {tribe_name} does not exists. You can't add a student in it.",
|
||||
)
|
||||
except StudentDoesExist:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
content=f"The student {item.name} ({item.id=}) does not exists. You can't modify it.",
|
||||
)
|
||||
|
||||
return f"/students/{student.id}"
|
||||
|
||||
|
||||
@app.delete(
|
||||
"/students/{student_id}",
|
||||
)
|
||||
async def delete_student(student_id):
|
||||
try:
|
||||
student = services.delete_student(
|
||||
id=student_id,
|
||||
student_repo=student_repo,
|
||||
conn=conn,
|
||||
)
|
||||
except StudentDoesExist:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
content=f"The student ({student_id=}) does not exists. You can't delete it.",
|
||||
)
|
||||
|
||||
return Response(
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
|
@ -0,0 +1,16 @@
|
|||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class StudentModel(BaseModel):
|
||||
name: str
|
||||
tribe_name: str
|
||||
id: Optional[str]
|
||||
|
||||
|
||||
class TribeModel(BaseModel):
|
||||
name: str
|
||||
level: str
|
||||
students: list[StudentModel] | None = []
|
||||
assessments: list | None = []
|
|
@ -0,0 +1,16 @@
|
|||
import os
|
||||
import sqlite3
|
||||
|
||||
from backend.adapters.sqlite import create_db
|
||||
|
||||
|
||||
def sqlite_conn(sqlite_file: str = ":memory"):
|
||||
conn = sqlite3.connect(sqlite_file)
|
||||
create_db(conn)
|
||||
return conn
|
||||
|
||||
|
||||
def get_api_url():
|
||||
host = os.environ.get("API_HOST", "localhost")
|
||||
port = 8000 if host == "localhost" else 80
|
||||
return f"http://{host}:{port}"
|
|
@ -0,0 +1,69 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import date
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from backend.model.tribe import Tribe
|
||||
|
||||
|
||||
@dataclass
|
||||
class Assessment:
|
||||
name: str
|
||||
tribe: Tribe
|
||||
term: int
|
||||
exercises: list[Exercise] = field(default_factory=list)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.tribe.register_assessment(self)
|
||||
|
||||
def register_exercise(self, exercise: Exercise):
|
||||
self.exercises.append(exercise)
|
||||
|
||||
@property
|
||||
def questions(self):
|
||||
return sum([exercise.questions for exercise in self.exercises])
|
||||
|
||||
|
||||
@dataclass
|
||||
class Exercise:
|
||||
name: str
|
||||
assessment: Assessment
|
||||
date: Optional[date]
|
||||
questions: list[Question] = field(default_factory=list)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.assessment.register_exercise(self)
|
||||
|
||||
def register_question(self, question: Question):
|
||||
self.questions.append(question)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Domain:
|
||||
name: str
|
||||
description: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Skill:
|
||||
name: str
|
||||
description: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Question:
|
||||
name: str
|
||||
exercise: Exercise
|
||||
description: str
|
||||
skill: Skill
|
||||
domain: Domain
|
||||
is_leveled: bool
|
||||
scale: int
|
||||
|
||||
def __post_init__(
|
||||
self,
|
||||
) -> None:
|
||||
|
||||
self.exercise.register_question(self)
|
|
@ -0,0 +1,33 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from backend.model.tribe import Tribe
|
||||
|
||||
|
||||
@dataclass
|
||||
class Student:
|
||||
name: str
|
||||
tribe: Tribe
|
||||
id: str = field(default_factory=lambda: str(uuid4()))
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.tribe.register_student(self)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, Student):
|
||||
return self.id == other.id
|
||||
return False
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.id)
|
||||
|
||||
def to_tuple(self) -> tuple:
|
||||
return (self.id, self.name, self.tribe.name)
|
||||
|
||||
def to_dict(self, full_tribe=False) -> dict:
|
||||
return {"id": self.id, "name": self.name, "tribe_name": self.tribe.name}
|
|
@ -0,0 +1,55 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from backend.model.assessment import Assessment
|
||||
from backend.model.student import Student
|
||||
|
||||
|
||||
class TribeError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Tribe:
|
||||
name: str
|
||||
level: str
|
||||
students: list[Student] = field(default_factory=list)
|
||||
assessments: list[Assessment] = field(default_factory=list)
|
||||
|
||||
def register_assessment(self, assessment: Assessment) -> None:
|
||||
self.assessments.append(assessment)
|
||||
|
||||
@property
|
||||
def students_id(self) -> list[str]:
|
||||
return [s.id for s in self.students]
|
||||
|
||||
def register_student(self, student: Student) -> None:
|
||||
"""Register a student
|
||||
|
||||
If the student is already registered, it is modified.
|
||||
"""
|
||||
try:
|
||||
old_student = next(filter(lambda s: s.id == student.id, self.students))
|
||||
except StopIteration:
|
||||
pass
|
||||
else:
|
||||
self.students.remove(old_student)
|
||||
|
||||
self.students.append(student)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, Tribe):
|
||||
return self.name == other.name
|
||||
return False
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.name)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {"name": self.name, "level": self.level}
|
||||
|
||||
def to_tuple(self) -> tuple:
|
||||
return (self.name, self.level)
|
|
@ -0,0 +1,23 @@
|
|||
import abc
|
||||
|
||||
|
||||
class AbstractRepository(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
def add(self, element):
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def update(self, reference, element):
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def list(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def get(self, reference):
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete(self, element):
|
||||
raise NotImplementedError
|
|
@ -0,0 +1,105 @@
|
|||
from backend.model.student import Student
|
||||
from backend.model.tribe import Tribe
|
||||
from backend.repository.abstract_repository import AbstractRepository
|
||||
|
||||
|
||||
class StudentRepositoryError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class StudentSQLiteRepository(AbstractRepository):
|
||||
def __init__(self, conn) -> None:
|
||||
self.conn = conn
|
||||
|
||||
def add(self, student: Student) -> None:
|
||||
self.conn.execute(
|
||||
"""
|
||||
INSERT INTO students(id, name, tribe_name) VALUES (?, ?, ?)
|
||||
""",
|
||||
(
|
||||
student.id,
|
||||
student.name,
|
||||
student.tribe.name,
|
||||
),
|
||||
)
|
||||
|
||||
def update(self, student: Student) -> None:
|
||||
search_student = self.conn.execute(
|
||||
"""
|
||||
SELECT id FROM students WHERE id=:id
|
||||
""",
|
||||
{"id": student.id},
|
||||
).fetchone()
|
||||
|
||||
if search_student is None:
|
||||
raise StudentRepositoryError(f"The student ({student.id=}) does not exists")
|
||||
|
||||
self.conn.execute(
|
||||
"""
|
||||
UPDATE students SET name=:newname, tribe_name=:newtribe WHERE id=:id
|
||||
""",
|
||||
{
|
||||
"newname": student.name,
|
||||
"newtribe": student.tribe.name,
|
||||
"id": student.id,
|
||||
},
|
||||
)
|
||||
|
||||
def _rebuild_student(self, row: tuple, tribes: list[Tribe]) -> Student:
|
||||
print(row)
|
||||
print([t.name for t in tribes])
|
||||
tribe = next(filter(lambda t: t.name == row[2], tribes))
|
||||
return Student(id=row[0], name=row[1], tribe=tribe)
|
||||
|
||||
def get(self, id: str, tribes: list[Tribe]) -> Student:
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT id, name, tribe_name FROM students WHERE id=?
|
||||
""",
|
||||
(id,),
|
||||
)
|
||||
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return self._rebuild_student(row, tribes)
|
||||
|
||||
raise ValueError(f"The student ({id=}) does not exists")
|
||||
|
||||
def list(self, tribes: list[Tribe]) -> list[Student]:
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT * FROM students
|
||||
"""
|
||||
)
|
||||
|
||||
rows = cursor.fetchall()
|
||||
return [self._rebuild_student(r, tribes) for r in rows]
|
||||
|
||||
def list_id(self):
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT id FROM students
|
||||
"""
|
||||
)
|
||||
|
||||
rows = cursor.fetchall()
|
||||
return [r[0] for r in rows]
|
||||
|
||||
def delete(self, id: str) -> None:
|
||||
students_id = self.list_id()
|
||||
if id not in students_id:
|
||||
raise StudentRepositoryError(
|
||||
f"The student {id} doesn't exists. Can't delete it."
|
||||
)
|
||||
|
||||
self.conn.execute(
|
||||
"""
|
||||
DELETE FROM students WHERE id=:id
|
||||
""",
|
||||
{
|
||||
"id": id,
|
||||
},
|
||||
)
|
|
@ -0,0 +1,23 @@
|
|||
from backend.model.tribe import Tribe
|
||||
from backend.repository.abstract_repository import AbstractRepository
|
||||
|
||||
|
||||
class TribeSQLAlchemyRepository(AbstractRepository):
|
||||
def __init__(self, session) -> None:
|
||||
self.session = session
|
||||
|
||||
def add(self, tribe: Tribe) -> None:
|
||||
self.session.add(tribe)
|
||||
|
||||
def update(self, name: str, tribe: Tribe) -> None:
|
||||
self.session.query(Tribe).filter_by(name=name).update(tribe.to_dict())
|
||||
|
||||
def get(self, name: str) -> Tribe:
|
||||
return self.session.query(Tribe).filter_by(name=name).one()
|
||||
|
||||
def list(self) -> list[Tribe]:
|
||||
return self.session.query(Tribe).all()
|
||||
|
||||
def delete(self, tribe: Tribe) -> None:
|
||||
the_tribe = self.get(tribe.name)
|
||||
self.session.delete(the_tribe)
|
|
@ -0,0 +1,89 @@
|
|||
from backend.model.tribe import Tribe
|
||||
from backend.repository.abstract_repository import AbstractRepository
|
||||
|
||||
|
||||
class TribeRepositoryError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TribeSQLiteRepository(AbstractRepository):
|
||||
def __init__(self, conn) -> None:
|
||||
self.conn = conn
|
||||
|
||||
def add(self, tribe: Tribe) -> None:
|
||||
tribes = self.list()
|
||||
if tribe.name in map(lambda x: x.name, tribes):
|
||||
raise TribeRepositoryError(
|
||||
f"The tribe {tribe.name} already exists. Can't add it"
|
||||
)
|
||||
|
||||
self.conn.execute(
|
||||
"""
|
||||
INSERT INTO tribes(name, level) VALUES (?, ?)
|
||||
""",
|
||||
(
|
||||
tribe.name,
|
||||
tribe.level,
|
||||
),
|
||||
)
|
||||
|
||||
def update(self, name: str, tribe: Tribe) -> None:
|
||||
tribes = self.list()
|
||||
|
||||
if name not in map(lambda x: x.name, tribes):
|
||||
raise TribeRepositoryError(
|
||||
f"The tribe {name} doesn't exists. Can't update it"
|
||||
)
|
||||
|
||||
self.conn.execute(
|
||||
"""
|
||||
UPDATE tribes SET name=:newname, level=:newlevel WHERE name=:name
|
||||
""",
|
||||
{
|
||||
"newname": tribe.name,
|
||||
"newlevel": tribe.level,
|
||||
"name": name,
|
||||
},
|
||||
)
|
||||
|
||||
def get(self, name: str) -> Tribe:
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT * FROM tribes WHERE name=?
|
||||
""",
|
||||
(name,),
|
||||
)
|
||||
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return Tribe(*row)
|
||||
|
||||
raise TribeRepositoryError(f"The tribe {name} does not exists")
|
||||
|
||||
def list(self) -> list[Tribe]:
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT * FROM tribes
|
||||
"""
|
||||
)
|
||||
|
||||
rows = cursor.fetchall()
|
||||
return [Tribe(*r) for r in rows]
|
||||
|
||||
def delete(self, name: str) -> None:
|
||||
tribes = self.list()
|
||||
if name not in map(lambda x: x.name, tribes):
|
||||
raise TribeRepositoryError(
|
||||
f"The tribe {name} doesn't exists. Can't delete it."
|
||||
)
|
||||
|
||||
self.conn.execute(
|
||||
"""
|
||||
DELETE FROM tribes WHERE name=:name
|
||||
""",
|
||||
{
|
||||
"name": name,
|
||||
},
|
||||
)
|
|
@ -0,0 +1,116 @@
|
|||
from backend.model.student import Student
|
||||
from backend.model.tribe import Tribe
|
||||
from backend.repository.abstract_repository import AbstractRepository
|
||||
from backend.repository.student_sqlite_repository import StudentRepositoryError
|
||||
from backend.repository.tribe_sqlite_repository import TribeRepositoryError
|
||||
|
||||
|
||||
class TribeExists(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TribeDoesNotExist(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class StudentExists(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class StudentDoesExist(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def add_tribe(name: str, level: str, tribe_repo: AbstractRepository, conn) -> Tribe:
|
||||
tribe = Tribe(name=name, level=level)
|
||||
try:
|
||||
tribe_repo.add(tribe)
|
||||
except TribeRepositoryError:
|
||||
raise TribeExists(f"The tribe {tribe.name} already exists")
|
||||
conn.commit()
|
||||
return tribe
|
||||
|
||||
|
||||
def update_tribe(name: str, level: str, tribe_repo: AbstractRepository, conn) -> Tribe:
|
||||
tribe = Tribe(name=name, level=level)
|
||||
try:
|
||||
tribe_repo.update(name=name, tribe=tribe)
|
||||
except TribeRepositoryError:
|
||||
raise TribeDoesNotExist(f"The tribe {name} does not exists you can't update it")
|
||||
conn.commit()
|
||||
return tribe
|
||||
|
||||
|
||||
def delete_tribe(name: str, tribe_repo: AbstractRepository, conn) -> None:
|
||||
try:
|
||||
tribe_repo.delete(name=name)
|
||||
except TribeRepositoryError:
|
||||
raise TribeDoesNotExist(f"The tribe {name} does not exists you can't delete it")
|
||||
|
||||
conn.commit()
|
||||
|
||||
|
||||
def add_student(
|
||||
name: str,
|
||||
tribe: str,
|
||||
student_repo: AbstractRepository,
|
||||
tribe_repo: AbstractRepository,
|
||||
conn,
|
||||
) -> Student:
|
||||
|
||||
try:
|
||||
_tribe = tribe_repo.get(tribe)
|
||||
except TribeRepositoryError:
|
||||
raise TribeDoesNotExist(
|
||||
f"The tribe {tribe} does not exists. Can't add a student in it"
|
||||
)
|
||||
|
||||
student = Student(name=name, tribe=_tribe)
|
||||
|
||||
try:
|
||||
student_repo.add(student)
|
||||
except StudentRepositoryError:
|
||||
raise StudentExists(f"The student {student.name} already exists. Can't add it.")
|
||||
|
||||
conn.commit()
|
||||
return student
|
||||
|
||||
|
||||
def update_student(
|
||||
id: str,
|
||||
name: str,
|
||||
tribe: str,
|
||||
student_repo: AbstractRepository,
|
||||
tribe_repo: AbstractRepository,
|
||||
conn,
|
||||
) -> Student:
|
||||
try:
|
||||
_tribe = tribe_repo.get(tribe)
|
||||
except TribeRepositoryError:
|
||||
raise TribeDoesNotExist(
|
||||
f"The tribe {tribe} does not exists. Can't update a student with it"
|
||||
)
|
||||
|
||||
student = Student(id=id, name=name, tribe=_tribe)
|
||||
|
||||
try:
|
||||
student_repo.update(student)
|
||||
except StudentRepositoryError:
|
||||
raise StudentDoesExist(
|
||||
f"The student {student.name} ({student.id=}) does not exists. Can't update it."
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
return student
|
||||
|
||||
|
||||
def delete_student(
|
||||
id: str,
|
||||
student_repo: AbstractRepository,
|
||||
conn,
|
||||
) -> Student:
|
||||
try:
|
||||
student_repo.delete(id=id)
|
||||
except StudentRepositoryError:
|
||||
raise StudentDoesExist("The student with id {id} does not exists")
|
||||
conn.commit()
|
|
@ -0,0 +1,872 @@
|
|||
# This file is automatically @generated by Poetry and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "3.6.2"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
files = [
|
||||
{file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"},
|
||||
{file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
|
||||
[package.extras]
|
||||
doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||
test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
|
||||
trio = ["trio (>=0.16,<0.22)"]
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "22.1.0"
|
||||
description = "Classes Without Boilerplate"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
|
||||
{file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
|
||||
docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
|
||||
tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
|
||||
tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2022.12.7"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"},
|
||||
{file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfgv"
|
||||
version = "3.3.1"
|
||||
description = "Validate configuration and produce human readable error messages."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
files = [
|
||||
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
|
||||
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "2.1.1"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.0"
|
||||
files = [
|
||||
{file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
|
||||
{file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
unicode-backport = ["unicodedata2"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.3"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.0.1"
|
||||
description = "Code coverage measurement for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "coverage-7.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b3695c4f4750bca943b3e1f74ad4be8d29e4aeab927d50772c41359107bd5d5c"},
|
||||
{file = "coverage-7.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa6a5a224b7f4cfb226f4fc55a57e8537fcc096f42219128c2c74c0e7d0953e1"},
|
||||
{file = "coverage-7.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74f70cd92669394eaf8d7756d1b195c8032cf7bbbdfce3bc489d4e15b3b8cf73"},
|
||||
{file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b66bb21a23680dee0be66557dc6b02a3152ddb55edf9f6723fa4a93368f7158d"},
|
||||
{file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87717959d4d0ee9db08a0f1d80d21eb585aafe30f9b0a54ecf779a69cb015f6"},
|
||||
{file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:854f22fa361d1ff914c7efa347398374cc7d567bdafa48ac3aa22334650dfba2"},
|
||||
{file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1e414dc32ee5c3f36544ea466b6f52f28a7af788653744b8570d0bf12ff34bc0"},
|
||||
{file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6c5ad996c6fa4d8ed669cfa1e8551348729d008a2caf81489ab9ea67cfbc7498"},
|
||||
{file = "coverage-7.0.1-cp310-cp310-win32.whl", hash = "sha256:691571f31ace1837838b7e421d3a09a8c00b4aac32efacb4fc9bd0a5c647d25a"},
|
||||
{file = "coverage-7.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:89caf4425fe88889e2973a8e9a3f6f5f9bbe5dd411d7d521e86428c08a873a4a"},
|
||||
{file = "coverage-7.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63d56165a7c76265468d7e0c5548215a5ba515fc2cba5232d17df97bffa10f6c"},
|
||||
{file = "coverage-7.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f943a3b2bc520102dd3e0bb465e1286e12c9a54f58accd71b9e65324d9c7c01"},
|
||||
{file = "coverage-7.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:830525361249dc4cd013652b0efad645a385707a5ae49350c894b67d23fbb07c"},
|
||||
{file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd1b9c5adc066db699ccf7fa839189a649afcdd9e02cb5dc9d24e67e7922737d"},
|
||||
{file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00c14720b8b3b6c23b487e70bd406abafc976ddc50490f645166f111c419c39"},
|
||||
{file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d55d840e1b8c0002fce66443e124e8581f30f9ead2e54fbf6709fb593181f2c"},
|
||||
{file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66b18c3cf8bbab0cce0d7b9e4262dc830e93588986865a8c78ab2ae324b3ed56"},
|
||||
{file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:12a5aa77783d49e05439fbe6e6b427484f8a0f9f456b46a51d8aac022cfd024d"},
|
||||
{file = "coverage-7.0.1-cp311-cp311-win32.whl", hash = "sha256:b77015d1cb8fe941be1222a5a8b4e3fbca88180cfa7e2d4a4e58aeabadef0ab7"},
|
||||
{file = "coverage-7.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb992c47cb1e5bd6a01e97182400bcc2ba2077080a17fcd7be23aaa6e572e390"},
|
||||
{file = "coverage-7.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e78e9dcbf4f3853d3ae18a8f9272111242531535ec9e1009fa8ec4a2b74557dc"},
|
||||
{file = "coverage-7.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60bef2e2416f15fdc05772bf87db06c6a6f9870d1db08fdd019fbec98ae24a9"},
|
||||
{file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9823e4789ab70f3ec88724bba1a203f2856331986cd893dedbe3e23a6cfc1e4e"},
|
||||
{file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9158f8fb06747ac17bd237930c4372336edc85b6e13bdc778e60f9d685c3ca37"},
|
||||
{file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:486ee81fa694b4b796fc5617e376326a088f7b9729c74d9defa211813f3861e4"},
|
||||
{file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1285648428a6101b5f41a18991c84f1c3959cee359e51b8375c5882fc364a13f"},
|
||||
{file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2c44fcfb3781b41409d0f060a4ed748537557de9362a8a9282182fafb7a76ab4"},
|
||||
{file = "coverage-7.0.1-cp37-cp37m-win32.whl", hash = "sha256:d6814854c02cbcd9c873c0f3286a02e3ac1250625cca822ca6bc1018c5b19f1c"},
|
||||
{file = "coverage-7.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f66460f17c9319ea4f91c165d46840314f0a7c004720b20be58594d162a441d8"},
|
||||
{file = "coverage-7.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b373c9345c584bb4b5f5b8840df7f4ab48c4cbb7934b58d52c57020d911b856"},
|
||||
{file = "coverage-7.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d3022c3007d3267a880b5adcf18c2a9bf1fc64469b394a804886b401959b8742"},
|
||||
{file = "coverage-7.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92651580bd46519067e36493acb394ea0607b55b45bd81dd4e26379ed1871f55"},
|
||||
{file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cfc595d2af13856505631be072835c59f1acf30028d1c860b435c5fc9c15b69"},
|
||||
{file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b4b3a4d9915b2be879aff6299c0a6129f3d08a775d5a061f503cf79571f73e4"},
|
||||
{file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b6f22bb64cc39bcb883e5910f99a27b200fdc14cdd79df8696fa96b0005c9444"},
|
||||
{file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72d1507f152abacea81f65fee38e4ef3ac3c02ff8bc16f21d935fd3a8a4ad910"},
|
||||
{file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a79137fc99815fff6a852c233628e735ec15903cfd16da0f229d9c4d45926ab"},
|
||||
{file = "coverage-7.0.1-cp38-cp38-win32.whl", hash = "sha256:b3763e7fcade2ff6c8e62340af9277f54336920489ceb6a8cd6cc96da52fcc62"},
|
||||
{file = "coverage-7.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:09f6b5a8415b6b3e136d5fec62b552972187265cb705097bf030eb9d4ffb9b60"},
|
||||
{file = "coverage-7.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:978258fec36c154b5e250d356c59af7d4c3ba02bef4b99cda90b6029441d797d"},
|
||||
{file = "coverage-7.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19ec666533f0f70a0993f88b8273057b96c07b9d26457b41863ccd021a043b9a"},
|
||||
{file = "coverage-7.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfded268092a84605f1cc19e5c737f9ce630a8900a3589e9289622db161967e9"},
|
||||
{file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bcfb1d8ac94af886b54e18a88b393f6a73d5959bb31e46644a02453c36e475"},
|
||||
{file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b4a923cc7566bbc7ae2dfd0ba5a039b61d19c740f1373791f2ebd11caea59"},
|
||||
{file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aec2d1515d9d39ff270059fd3afbb3b44e6ec5758af73caf18991807138c7118"},
|
||||
{file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c20cfebcc149a4c212f6491a5f9ff56f41829cd4f607b5be71bb2d530ef243b1"},
|
||||
{file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fd556ff16a57a070ce4f31c635953cc44e25244f91a0378c6e9bdfd40fdb249f"},
|
||||
{file = "coverage-7.0.1-cp39-cp39-win32.whl", hash = "sha256:b9ea158775c7c2d3e54530a92da79496fb3fb577c876eec761c23e028f1e216c"},
|
||||
{file = "coverage-7.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:d1991f1dd95eba69d2cd7708ff6c2bbd2426160ffc73c2b81f617a053ebcb1a8"},
|
||||
{file = "coverage-7.0.1-pp37.pp38.pp39-none-any.whl", hash = "sha256:3dd4ee135e08037f458425b8842d24a95a0961831a33f89685ff86b77d378f89"},
|
||||
{file = "coverage-7.0.1.tar.gz", hash = "sha256:a4a574a19eeb67575a5328a5760bbbb737faa685616586a9f9da4281f940109c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
|
||||
|
||||
[package.extras]
|
||||
toml = ["tomli"]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.3.6"
|
||||
description = "Distribution utilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"},
|
||||
{file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.0.4"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"},
|
||||
{file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "faker"
|
||||
version = "15.3.4"
|
||||
description = "Faker is a Python package that generates fake data for you."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "Faker-15.3.4-py3-none-any.whl", hash = "sha256:c2a2ff9dd8dfd991109b517ab98d5cb465e857acb45f6b643a0e284a9eb2cc76"},
|
||||
{file = "Faker-15.3.4.tar.gz", hash = "sha256:2d5443724f640ce07658ca8ca8bbd40d26b58914e63eec6549727869aa67e2cc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
python-dateutil = ">=2.4"
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.88.0"
|
||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "fastapi-0.88.0-py3-none-any.whl", hash = "sha256:263b718bb384422fe3d042ffc9a0c8dece5e034ab6586ff034f6b4b1667c3eee"},
|
||||
{file = "fastapi-0.88.0.tar.gz", hash = "sha256:915bf304180a0e7c5605ec81097b7d4cd8826ff87a02bb198e336fb9f3b5ff02"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0"
|
||||
starlette = "0.22.0"
|
||||
|
||||
[package.extras]
|
||||
all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||
dev = ["pre-commit (>=2.17.0,<3.0.0)", "ruff (==0.0.138)", "uvicorn[standard] (>=0.12.0,<0.19.0)"]
|
||||
doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer[all] (>=0.6.1,<0.7.0)"]
|
||||
test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.10.0)", "coverage[toml] (>=6.5.0,<7.0)", "databases[sqlite] (>=0.3.2,<0.7.0)", "email-validator (>=1.1.1,<2.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.23.0,<0.24.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.982)", "orjson (>=3.2.1,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=7.1.3,<8.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "ruff (==0.0.138)", "sqlalchemy (>=1.3.18,<=1.4.41)", "types-orjson (==3.6.2)", "types-ujson (==5.5.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.8.2"
|
||||
description = "A platform independent file lock."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "filelock-3.8.2-py3-none-any.whl", hash = "sha256:8df285554452285f79c035efb0c861eb33a4bcfa5b7a137016e32e6a90f9792c"},
|
||||
{file = "filelock-3.8.2.tar.gz", hash = "sha256:7565f628ea56bfcd8e54e42bdc55da899c85c1abfe1b5bcfd147e9188cebb3b2"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2022.9.29)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
|
||||
testing = ["covdefaults (>=2.2.2)", "coverage (>=6.5)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "2.0.1"
|
||||
description = "Lightweight in-process concurrent programming"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
|
||||
files = [
|
||||
{file = "greenlet-2.0.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:9ed358312e63bf683b9ef22c8e442ef6c5c02973f0c2a939ec1d7b50c974015c"},
|
||||
{file = "greenlet-2.0.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4f09b0010e55bec3239278f642a8a506b91034f03a4fb28289a7d448a67f1515"},
|
||||
{file = "greenlet-2.0.1-cp27-cp27m-win32.whl", hash = "sha256:1407fe45246632d0ffb7a3f4a520ba4e6051fc2cbd61ba1f806900c27f47706a"},
|
||||
{file = "greenlet-2.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:3001d00eba6bbf084ae60ec7f4bb8ed375748f53aeaefaf2a37d9f0370558524"},
|
||||
{file = "greenlet-2.0.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d566b82e92ff2e09dd6342df7e0eb4ff6275a3f08db284888dcd98134dbd4243"},
|
||||
{file = "greenlet-2.0.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:0722c9be0797f544a3ed212569ca3fe3d9d1a1b13942d10dd6f0e8601e484d26"},
|
||||
{file = "greenlet-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d37990425b4687ade27810e3b1a1c37825d242ebc275066cfee8cb6b8829ccd"},
|
||||
{file = "greenlet-2.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be35822f35f99dcc48152c9839d0171a06186f2d71ef76dc57fa556cc9bf6b45"},
|
||||
{file = "greenlet-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c140e7eb5ce47249668056edf3b7e9900c6a2e22fb0eaf0513f18a1b2c14e1da"},
|
||||
{file = "greenlet-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d21681f09e297a5adaa73060737e3aa1279a13ecdcfcc6ef66c292cb25125b2d"},
|
||||
{file = "greenlet-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fb412b7db83fe56847df9c47b6fe3f13911b06339c2aa02dcc09dce8bbf582cd"},
|
||||
{file = "greenlet-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6a08799e9e88052221adca55741bf106ec7ea0710bca635c208b751f0d5b617"},
|
||||
{file = "greenlet-2.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e112e03d37987d7b90c1e98ba5e1b59e1645226d78d73282f45b326f7bddcb9"},
|
||||
{file = "greenlet-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56961cfca7da2fdd178f95ca407fa330c64f33289e1804b592a77d5593d9bd94"},
|
||||
{file = "greenlet-2.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13ba6e8e326e2116c954074c994da14954982ba2795aebb881c07ac5d093a58a"},
|
||||
{file = "greenlet-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bf633a50cc93ed17e494015897361010fc08700d92676c87931d3ea464123ce"},
|
||||
{file = "greenlet-2.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9f2c221eecb7ead00b8e3ddb913c67f75cba078fd1d326053225a3f59d850d72"},
|
||||
{file = "greenlet-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:13ebf93c343dd8bd010cd98e617cb4c1c1f352a0cf2524c82d3814154116aa82"},
|
||||
{file = "greenlet-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:6f61d71bbc9b4a3de768371b210d906726535d6ca43506737682caa754b956cd"},
|
||||
{file = "greenlet-2.0.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:2d0bac0385d2b43a7bd1d651621a4e0f1380abc63d6fb1012213a401cbd5bf8f"},
|
||||
{file = "greenlet-2.0.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:f6327b6907b4cb72f650a5b7b1be23a2aab395017aa6f1adb13069d66360eb3f"},
|
||||
{file = "greenlet-2.0.1-cp35-cp35m-win32.whl", hash = "sha256:81b0ea3715bf6a848d6f7149d25bf018fd24554a4be01fcbbe3fdc78e890b955"},
|
||||
{file = "greenlet-2.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:38255a3f1e8942573b067510f9611fc9e38196077b0c8eb7a8c795e105f9ce77"},
|
||||
{file = "greenlet-2.0.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:04957dc96669be041e0c260964cfef4c77287f07c40452e61abe19d647505581"},
|
||||
{file = "greenlet-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:4aeaebcd91d9fee9aa768c1b39cb12214b30bf36d2b7370505a9f2165fedd8d9"},
|
||||
{file = "greenlet-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974a39bdb8c90a85982cdb78a103a32e0b1be986d411303064b28a80611f6e51"},
|
||||
{file = "greenlet-2.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dca09dedf1bd8684767bc736cc20c97c29bc0c04c413e3276e0962cd7aeb148"},
|
||||
{file = "greenlet-2.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c0757db9bd08470ff8277791795e70d0bf035a011a528ee9a5ce9454b6cba2"},
|
||||
{file = "greenlet-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5067920de254f1a2dee8d3d9d7e4e03718e8fd2d2d9db962c8c9fa781ae82a39"},
|
||||
{file = "greenlet-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5a8e05057fab2a365c81abc696cb753da7549d20266e8511eb6c9d9f72fe3e92"},
|
||||
{file = "greenlet-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:3d75b8d013086b08e801fbbb896f7d5c9e6ccd44f13a9241d2bf7c0df9eda928"},
|
||||
{file = "greenlet-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:097e3dae69321e9100202fc62977f687454cd0ea147d0fd5a766e57450c569fd"},
|
||||
{file = "greenlet-2.0.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:cb242fc2cda5a307a7698c93173d3627a2a90d00507bccf5bc228851e8304963"},
|
||||
{file = "greenlet-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:72b00a8e7c25dcea5946692a2485b1a0c0661ed93ecfedfa9b6687bd89a24ef5"},
|
||||
{file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b0ff9878333823226d270417f24f4d06f235cb3e54d1103b71ea537a6a86ce"},
|
||||
{file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be9e0fb2ada7e5124f5282d6381903183ecc73ea019568d6d63d33f25b2a9000"},
|
||||
{file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b493db84d124805865adc587532ebad30efa68f79ad68f11b336e0a51ec86c2"},
|
||||
{file = "greenlet-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0459d94f73265744fee4c2d5ec44c6f34aa8a31017e6e9de770f7bcf29710be9"},
|
||||
{file = "greenlet-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a20d33124935d27b80e6fdacbd34205732660e0a1d35d8b10b3328179a2b51a1"},
|
||||
{file = "greenlet-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:ea688d11707d30e212e0110a1aac7f7f3f542a259235d396f88be68b649e47d1"},
|
||||
{file = "greenlet-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:afe07421c969e259e9403c3bb658968702bc3b78ec0b6fde3ae1e73440529c23"},
|
||||
{file = "greenlet-2.0.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:cd4ccc364cf75d1422e66e247e52a93da6a9b73cefa8cad696f3cbbb75af179d"},
|
||||
{file = "greenlet-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c8b1c43e75c42a6cafcc71defa9e01ead39ae80bd733a2608b297412beede68"},
|
||||
{file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:659f167f419a4609bc0516fb18ea69ed39dbb25594934bd2dd4d0401660e8a1e"},
|
||||
{file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:356e4519d4dfa766d50ecc498544b44c0249b6de66426041d7f8b751de4d6b48"},
|
||||
{file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:811e1d37d60b47cb8126e0a929b58c046251f28117cb16fcd371eed61f66b764"},
|
||||
{file = "greenlet-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d38ffd0e81ba8ef347d2be0772e899c289b59ff150ebbbbe05dc61b1246eb4e0"},
|
||||
{file = "greenlet-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0109af1138afbfb8ae647e31a2b1ab030f58b21dd8528c27beaeb0093b7938a9"},
|
||||
{file = "greenlet-2.0.1-cp38-cp38-win32.whl", hash = "sha256:88c8d517e78acdf7df8a2134a3c4b964415b575d2840a2746ddb1cc6175f8608"},
|
||||
{file = "greenlet-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:d6ee1aa7ab36475035eb48c01efae87d37936a8173fc4d7b10bb02c2d75dd8f6"},
|
||||
{file = "greenlet-2.0.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:b1992ba9d4780d9af9726bbcef6a1db12d9ab1ccc35e5773685a24b7fb2758eb"},
|
||||
{file = "greenlet-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:b5e83e4de81dcc9425598d9469a624826a0b1211380ac444c7c791d4a2137c19"},
|
||||
{file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:505138d4fa69462447a562a7c2ef723c6025ba12ac04478bc1ce2fcc279a2db5"},
|
||||
{file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cce1e90dd302f45716a7715517c6aa0468af0bf38e814ad4eab58e88fc09f7f7"},
|
||||
{file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e9744c657d896c7b580455e739899e492a4a452e2dd4d2b3e459f6b244a638d"},
|
||||
{file = "greenlet-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:662e8f7cad915ba75d8017b3e601afc01ef20deeeabf281bd00369de196d7726"},
|
||||
{file = "greenlet-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:41b825d65f31e394b523c84db84f9383a2f7eefc13d987f308f4663794d2687e"},
|
||||
{file = "greenlet-2.0.1-cp39-cp39-win32.whl", hash = "sha256:db38f80540083ea33bdab614a9d28bcec4b54daa5aff1668d7827a9fc769ae0a"},
|
||||
{file = "greenlet-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b23d2a46d53210b498e5b701a1913697671988f4bf8e10f935433f6e7c332fb6"},
|
||||
{file = "greenlet-2.0.1.tar.gz", hash = "sha256:42e602564460da0e8ee67cb6d7236363ee5e131aa15943b6670e44e5c2ed0f67"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["Sphinx", "docutils (<0.18)"]
|
||||
test = ["faulthandler", "objgraph", "psutil"]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
|
||||
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.5.11"
|
||||
description = "File identification library for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "identify-2.5.11-py2.py3-none-any.whl", hash = "sha256:e7db36b772b188099616aaf2accbee122949d1c6a1bac4f38196720d6f9f06db"},
|
||||
{file = "identify-2.5.11.tar.gz", hash = "sha256:14b7076b29c99b1b0b8b08e96d448c7b877a9b07683cd8cfda2ea06af85ffa1c"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
license = ["ukkonen"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.4"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
|
||||
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "1.1.1"
|
||||
description = "iniconfig: brain-dead simple config-ini parsing"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
||||
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
version = "1.7.0"
|
||||
description = "Node.js virtual environment builder"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
|
||||
files = [
|
||||
{file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"},
|
||||
{file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
setuptools = "*"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "22.0"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"},
|
||||
{file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.6.0"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "platformdirs-2.6.0-py3-none-any.whl", hash = "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca"},
|
||||
{file = "platformdirs-2.6.0.tar.gz", hash = "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.0.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "2.20.0"
|
||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"},
|
||||
{file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cfgv = ">=2.0.0"
|
||||
identify = ">=1.0.0"
|
||||
nodeenv = ">=0.11.1"
|
||||
pyyaml = ">=5.1"
|
||||
toml = "*"
|
||||
virtualenv = ">=20.0.8"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.10.2"
|
||||
description = "Data validation and settings management using python type hints"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"},
|
||||
{file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"},
|
||||
{file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"},
|
||||
{file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"},
|
||||
{file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"},
|
||||
{file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"},
|
||||
{file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"},
|
||||
{file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"},
|
||||
{file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"},
|
||||
{file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"},
|
||||
{file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"},
|
||||
{file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"},
|
||||
{file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"},
|
||||
{file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"},
|
||||
{file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"},
|
||||
{file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"},
|
||||
{file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"},
|
||||
{file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"},
|
||||
{file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"},
|
||||
{file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"},
|
||||
{file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"},
|
||||
{file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"},
|
||||
{file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"},
|
||||
{file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"},
|
||||
{file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"},
|
||||
{file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"},
|
||||
{file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"},
|
||||
{file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"},
|
||||
{file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"},
|
||||
{file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"},
|
||||
{file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"},
|
||||
{file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"},
|
||||
{file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"},
|
||||
{file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"},
|
||||
{file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"},
|
||||
{file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = ">=4.1.0"
|
||||
|
||||
[package.extras]
|
||||
dotenv = ["python-dotenv (>=0.10.4)"]
|
||||
email = ["email-validator (>=1.0.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.2.0"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"},
|
||||
{file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
attrs = ">=19.2.0"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "4.0.0"
|
||||
description = "Pytest plugin for measuring coverage."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"},
|
||||
{file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
coverage = {version = ">=5.2.1", extras = ["toml"]}
|
||||
pytest = ">=4.6"
|
||||
|
||||
[package.extras]
|
||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.8.2"
|
||||
description = "Extensions to the standard Python datetime module"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
files = [
|
||||
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
|
||||
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0"
|
||||
description = "YAML parser and emitter for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
|
||||
{file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"},
|
||||
{file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"},
|
||||
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"},
|
||||
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"},
|
||||
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"},
|
||||
{file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"},
|
||||
{file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
|
||||
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.28.1"
|
||||
description = "Python HTTP for Humans."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7, <4"
|
||||
files = [
|
||||
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
|
||||
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=2017.4.17"
|
||||
charset-normalizer = ">=2,<3"
|
||||
idna = ">=2.5,<4"
|
||||
urllib3 = ">=1.21.1,<1.27"
|
||||
|
||||
[package.extras]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "65.6.3"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"},
|
||||
{file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
files = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.0"
|
||||
description = "Sniff out which async library your code is running under"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
|
||||
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "1.4.45"
|
||||
description = "Database Abstraction Library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
|
||||
files = [
|
||||
{file = "SQLAlchemy-1.4.45-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:f1d3fb02a4d0b07d1351a4a52f159e5e7b3045c903468b7e9349ebf0020ffdb9"},
|
||||
{file = "SQLAlchemy-1.4.45-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9b7025d46aba946272f6b6b357a22f3787473ef27451f342df1a2a6de23743e3"},
|
||||
{file = "SQLAlchemy-1.4.45-cp27-cp27m-win32.whl", hash = "sha256:26b8424b32eeefa4faad21decd7bdd4aade58640b39407bf43e7d0a7c1bc0453"},
|
||||
{file = "SQLAlchemy-1.4.45-cp27-cp27m-win_amd64.whl", hash = "sha256:13578d1cda69bc5e76c59fec9180d6db7ceb71c1360a4d7861c37d87ea6ca0b1"},
|
||||
{file = "SQLAlchemy-1.4.45-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6cd53b4c756a6f9c6518a3dc9c05a38840f9ae442c91fe1abde50d73651b6922"},
|
||||
{file = "SQLAlchemy-1.4.45-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:ca152ffc7f0aa069c95fba46165030267ec5e4bb0107aba45e5e9e86fe4d9363"},
|
||||
{file = "SQLAlchemy-1.4.45-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06055476d38ed7915eeed22b78580556d446d175c3574a01b9eb04d91f3a8b2e"},
|
||||
{file = "SQLAlchemy-1.4.45-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:081e2a2d75466353c738ca2ee71c0cfb08229b4f9909b5fa085f75c48d021471"},
|
||||
{file = "SQLAlchemy-1.4.45-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96821d806c0c90c68ce3f2ce6dd529c10e5d7587961f31dd5c30e3bfddc4545d"},
|
||||
{file = "SQLAlchemy-1.4.45-cp310-cp310-win32.whl", hash = "sha256:c8051bff4ce48cbc98f11e95ac46bfd1e36272401070c010248a3230d099663f"},
|
||||
{file = "SQLAlchemy-1.4.45-cp310-cp310-win_amd64.whl", hash = "sha256:16ad798fc121cad5ea019eb2297127b08c54e1aa95fe17b3fea9fdbc5c34fe62"},
|
||||
{file = "SQLAlchemy-1.4.45-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:099efef0de9fbda4c2d7cb129e4e7f812007901942259d4e6c6e19bd69de1088"},
|
||||
{file = "SQLAlchemy-1.4.45-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29a29d02c9e6f6b105580c5ed7afb722b97bc2e2fdb85e1d45d7ddd8440cfbca"},
|
||||
{file = "SQLAlchemy-1.4.45-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc10423b59d6d032d6dff0bb42aa06dc6a8824eb6029d70c7d1b6981a2e7f4d8"},
|
||||
{file = "SQLAlchemy-1.4.45-cp311-cp311-win32.whl", hash = "sha256:1a92685db3b0682776a5abcb5f9e9addb3d7d9a6d841a452a17ec2d8d457bea7"},
|
||||
{file = "SQLAlchemy-1.4.45-cp311-cp311-win_amd64.whl", hash = "sha256:db3ccbce4a861bf4338b254f95916fc68dd8b7aa50eea838ecdaf3a52810e9c0"},
|
||||
{file = "SQLAlchemy-1.4.45-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:a62ae2ea3b940ce9c9cbd675489c2047921ce0a79f971d3082978be91bd58117"},
|
||||
{file = "SQLAlchemy-1.4.45-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87f8595390764db333a1705591d0934973d132af607f4fa8b792b366eacbb3c"},
|
||||
{file = "SQLAlchemy-1.4.45-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a21c1fb71c69c8ec65430160cd3eee44bbcea15b5a4e556f29d03f246f425ec"},
|
||||
{file = "SQLAlchemy-1.4.45-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7944b04e6fcf8d733964dd9ee36b6a587251a1a4049af3a9b846f6e64eb349a"},
|
||||
{file = "SQLAlchemy-1.4.45-cp36-cp36m-win32.whl", hash = "sha256:a3bcd5e2049ceb97e8c273e6a84ff4abcfa1dc47b6d8bbd36e07cce7176610d3"},
|
||||
{file = "SQLAlchemy-1.4.45-cp36-cp36m-win_amd64.whl", hash = "sha256:5953e225be47d80410ae519f865b5c341f541d8e383fb6d11f67fb71a45bf890"},
|
||||
{file = "SQLAlchemy-1.4.45-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:6a91b7883cb7855a27bc0637166eed622fdf1bb94a4d1630165e5dd88c7e64d3"},
|
||||
{file = "SQLAlchemy-1.4.45-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d458fd0566bc9e10b8be857f089e96b5ca1b1ef033226f24512f9ffdf485a8c0"},
|
||||
{file = "SQLAlchemy-1.4.45-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f4ad3b081c0dbb738886f8d425a5d983328670ee83b38192687d78fc82bd1e"},
|
||||
{file = "SQLAlchemy-1.4.45-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd95a3e6ab46da2c5b0703e797a772f3fab44d085b3919a4f27339aa3b1f51d3"},
|
||||
{file = "SQLAlchemy-1.4.45-cp37-cp37m-win32.whl", hash = "sha256:715f5859daa3bee6ecbad64501637fa4640ca6734e8cda6135e3898d5f8ccadd"},
|
||||
{file = "SQLAlchemy-1.4.45-cp37-cp37m-win_amd64.whl", hash = "sha256:2d1539fbc82d2206380a86d6d7d0453764fdca5d042d78161bbfb8dd047c80ec"},
|
||||
{file = "SQLAlchemy-1.4.45-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:01aa76f324c9bbc0dcb2bc3d9e2a9d7ede4808afa1c38d40d5e2007e3163b206"},
|
||||
{file = "SQLAlchemy-1.4.45-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:416fe7d228937bd37990b5a429fd00ad0e49eabcea3455af7beed7955f192edd"},
|
||||
{file = "SQLAlchemy-1.4.45-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7e32ce2584564d9e068bb7e0ccd1810cbb0a824c0687f8016fe67e97c345a637"},
|
||||
{file = "SQLAlchemy-1.4.45-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:561605cfc26273825ed2fb8484428faf36e853c13e4c90c61c58988aeccb34ed"},
|
||||
{file = "SQLAlchemy-1.4.45-cp38-cp38-win32.whl", hash = "sha256:55ddb5585129c5d964a537c9e32a8a68a8c6293b747f3fa164e1c034e1657a98"},
|
||||
{file = "SQLAlchemy-1.4.45-cp38-cp38-win_amd64.whl", hash = "sha256:445914dcadc0b623bd9851260ee54915ecf4e3041a62d57709b18a0eed19f33b"},
|
||||
{file = "SQLAlchemy-1.4.45-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:2db887dbf05bcc3151de1c4b506b14764c6240a42e844b4269132a7584de1e5f"},
|
||||
{file = "SQLAlchemy-1.4.45-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52b90c9487e4449ad954624d01dea34c90cd8c104bce46b322c83654f37a23c5"},
|
||||
{file = "SQLAlchemy-1.4.45-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f61e54b8c2b389de1a8ad52394729c478c67712dbdcdadb52c2575e41dae94a5"},
|
||||
{file = "SQLAlchemy-1.4.45-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e91a5e45a2ea083fe344b3503405978dff14d60ef3aa836432c9ca8cd47806b6"},
|
||||
{file = "SQLAlchemy-1.4.45-cp39-cp39-win32.whl", hash = "sha256:0e068b8414d60dd35d43c693555fc3d2e1d822cef07960bb8ca3f1ee6c4ff762"},
|
||||
{file = "SQLAlchemy-1.4.45-cp39-cp39-win_amd64.whl", hash = "sha256:2d6f178ff2923730da271c8aa317f70cf0df11a4d1812f1d7a704b1cf29c5fe3"},
|
||||
{file = "SQLAlchemy-1.4.45.tar.gz", hash = "sha256:fd69850860093a3f69fefe0ab56d041edfdfe18510b53d9a2eaecba2f15fa795"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
|
||||
|
||||
[package.extras]
|
||||
aiomysql = ["aiomysql", "greenlet (!=0.4.17)"]
|
||||
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"]
|
||||
asyncio = ["greenlet (!=0.4.17)"]
|
||||
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"]
|
||||
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"]
|
||||
mssql = ["pyodbc"]
|
||||
mssql-pymssql = ["pymssql"]
|
||||
mssql-pyodbc = ["pyodbc"]
|
||||
mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"]
|
||||
mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"]
|
||||
mysql-connector = ["mysql-connector-python"]
|
||||
oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"]
|
||||
postgresql = ["psycopg2 (>=2.7)"]
|
||||
postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
|
||||
postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"]
|
||||
postgresql-psycopg2binary = ["psycopg2-binary"]
|
||||
postgresql-psycopg2cffi = ["psycopg2cffi"]
|
||||
pymysql = ["pymysql", "pymysql (<1)"]
|
||||
sqlcipher = ["sqlcipher3-binary"]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.22.0"
|
||||
description = "The little ASGI library that shines."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "starlette-0.22.0-py3-none-any.whl", hash = "sha256:b5eda991ad5f0ee5d8ce4c4540202a573bb6691ecd0c712262d0bc85cf8f2c50"},
|
||||
{file = "starlette-0.22.0.tar.gz", hash = "sha256:b092cbc365bea34dd6840b42861bdabb2f507f8671e642e8272d2442e08ea4ff"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
anyio = ">=3.4.0,<5"
|
||||
|
||||
[package.extras]
|
||||
full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
files = [
|
||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
description = "A lil' TOML parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.4.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
|
||||
{file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.13"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||
files = [
|
||||
{file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"},
|
||||
{file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
|
||||
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.20.0"
|
||||
description = "The lightning-fast ASGI server."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "uvicorn-0.20.0-py3-none-any.whl", hash = "sha256:c3ed1598a5668208723f2bb49336f4509424ad198d6ab2615b7783db58d919fd"},
|
||||
{file = "uvicorn-0.20.0.tar.gz", hash = "sha256:a4e12017b940247f836bc90b72e725d7dfd0c8ed1c51eb365f5ba30d9f5127d8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.0"
|
||||
h11 = ">=0.8"
|
||||
|
||||
[package.extras]
|
||||
standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.17.1"
|
||||
description = "Virtual Python Environment builder"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"},
|
||||
{file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
distlib = ">=0.3.6,<1"
|
||||
filelock = ">=3.4.1,<4"
|
||||
platformdirs = ">=2.4,<3"
|
||||
|
||||
[package.extras]
|
||||
docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"]
|
||||
testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "dc9cca0226b975a91650ffdc42c0572f04707fc0de80fbcfee9809ebaf34a0bf"
|
|
@ -0,0 +1,24 @@
|
|||
[tool.poetry]
|
||||
name = "backend"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Bertrand Benjamin <benjamin.bertrand@opytex.org>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
sqlalchemy = "^1.4.45"
|
||||
fastapi = "^0.88.0"
|
||||
uvicorn = "^0.20.0"
|
||||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pre-commit = "^2.20.0"
|
||||
pytest = "^7.2.0"
|
||||
faker = "^15.3.4"
|
||||
requests = "^2.28.1"
|
||||
pytest-cov = "^4.0.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
source: sheets/
|
||||
output: reports/
|
||||
templates: templates/
|
|
@ -1,5 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from .csv_extraction import flat_df_students, flat_df_for
|
||||
from .df_marks_manip import pp_q_scores
|
|
@ -1,30 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
NO_ST_COLUMNS = {
|
||||
"assessment": "Nom",
|
||||
"term": "Trimestre",
|
||||
"date": "Date",
|
||||
"exercise": "Exercice",
|
||||
"question": "Question",
|
||||
"competence": "Competence",
|
||||
"theme": "Domaine",
|
||||
"comment": "Commentaire",
|
||||
"is_leveled": "Est_nivele",
|
||||
"score_rate": "Bareme",
|
||||
}
|
||||
|
||||
COLUMNS = {
|
||||
**NO_ST_COLUMNS,
|
||||
"student": "Eleve",
|
||||
"score": "Score",
|
||||
"mark": "Note",
|
||||
"level": "Niveau",
|
||||
"normalized": "Normalise",
|
||||
}
|
||||
|
||||
VALIDSCORE = {
|
||||
"NOTFILLED": "", # The item is not scored yet
|
||||
"NOANSWER": ".", # Student gives no answer (this score will impact the fianl mark)
|
||||
"ABS": "a", # Student has absent (this score won't be impact the final mark)
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
""" Extracting data from xlsx files """
|
||||
|
||||
import pandas as pd
|
||||
from .config import NO_ST_COLUMNS, COLUMNS, VALIDSCORE
|
||||
|
||||
pd.set_option("Precision", 2)
|
||||
|
||||
|
||||
def try_replace(x, old, new):
|
||||
try:
|
||||
return str(x).replace(old, new)
|
||||
except ValueError:
|
||||
return x
|
||||
|
||||
|
||||
def extract_students(df, no_student_columns=NO_ST_COLUMNS.values()):
|
||||
""" Extract the list of students from df
|
||||
|
||||
:param df: the dataframe
|
||||
:param no_student_columns: columns that are not students
|
||||
:return: list of students
|
||||
"""
|
||||
students = df.columns.difference(no_student_columns)
|
||||
return students
|
||||
|
||||
|
||||
def flat_df_students(
|
||||
df, no_student_columns=NO_ST_COLUMNS.values(), postprocessing=True
|
||||
):
|
||||
""" Flat the dataframe by returning a dataframe with on student on each line
|
||||
|
||||
:param df: the dataframe (one row per questions)
|
||||
:param no_student_columns: columns that are not students
|
||||
:return: dataframe with one row per questions and students
|
||||
|
||||
Columns of csv files:
|
||||
|
||||
- NO_ST_COLUMNS meta data on questions
|
||||
- one for each students
|
||||
|
||||
This function flat student's columns to "student" and "score"
|
||||
"""
|
||||
students = extract_students(df, no_student_columns)
|
||||
scores = []
|
||||
for st in students:
|
||||
scores.append(
|
||||
pd.melt(
|
||||
df,
|
||||
id_vars=no_student_columns,
|
||||
value_vars=st,
|
||||
var_name=COLUMNS["student"],
|
||||
value_name=COLUMNS["score"],
|
||||
).dropna(subset=[COLUMNS["score"]])
|
||||
)
|
||||
if postprocessing:
|
||||
return postprocess(pd.concat(scores))
|
||||
return pd.concat(scores)
|
||||
|
||||
|
||||
def flat_df_for(
|
||||
df, student, no_student_columns=NO_ST_COLUMNS.values(), postprocessing=True
|
||||
):
|
||||
""" Extract the data only for one student
|
||||
|
||||
:param df: the dataframe (one row per questions)
|
||||
:param no_student_columns: columns that are not students
|
||||
:return: dataframe with one row per questions and students
|
||||
|
||||
Columns of csv files:
|
||||
|
||||
- NO_ST_COLUMNS meta data on questions
|
||||
- one for each students
|
||||
|
||||
"""
|
||||
students = extract_students(df, no_student_columns)
|
||||
if student not in students:
|
||||
raise KeyError("This student is not in the table")
|
||||
st_df = df[list(no_student_columns) + [student]]
|
||||
st_df = st_df.rename(columns={student: COLUMNS["score"]}).dropna(
|
||||
subset=[COLUMNS["score"]]
|
||||
)
|
||||
if postprocessing:
|
||||
return postprocess(st_df)
|
||||
return st_df
|
||||
|
||||
|
||||
def postprocess(df):
|
||||
""" Postprocessing score dataframe
|
||||
|
||||
- Replace na with an empty string
|
||||
- Replace "NOANSWER" with -1
|
||||
- Turn commas number to dot numbers
|
||||
"""
|
||||
|
||||
df[COLUMNS["question"]].fillna("", inplace=True)
|
||||
df[COLUMNS["exercise"]].fillna("", inplace=True)
|
||||
df[COLUMNS["comment"]].fillna("", inplace=True)
|
||||
df[COLUMNS["competence"]].fillna("", inplace=True)
|
||||
|
||||
df[COLUMNS["score"]] = pd.to_numeric(
|
||||
df[COLUMNS["score"]]
|
||||
.replace(VALIDSCORE["NOANSWER"], -1)
|
||||
.apply(lambda x: try_replace(x, ",", "."))
|
||||
)
|
||||
df[COLUMNS["score_rate"]] = pd.to_numeric(
|
||||
df[COLUMNS["score_rate"]].apply(lambda x: try_replace(x, ",", ".")),
|
||||
errors="coerce",
|
||||
)
|
||||
|
||||
return df
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
|
@ -1,206 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from math import ceil, floor
|
||||
from .config import COLUMNS, VALIDSCORE
|
||||
|
||||
# Values manipulations
|
||||
|
||||
|
||||
def round_half_point(val):
|
||||
try:
|
||||
return 0.5 * ceil(2.0 * val)
|
||||
except ValueError:
|
||||
return val
|
||||
except TypeError:
|
||||
return val
|
||||
|
||||
|
||||
def score_to_mark(x):
|
||||
""" Compute the mark
|
||||
|
||||
if the item is leveled then the score is multiply by the score_rate
|
||||
otherwise it copies the score
|
||||
|
||||
:param x: dictionnary with COLUMNS["is_leveled"], COLUMNS["score"] and COLUMNS["score_rate"] keys
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... COLUMNS["score_rate"]:[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... COLUMNS["is_leveled"]:[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... COLUMNS["score"]:[1, 0.33, 2, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3],
|
||||
... }
|
||||
>>> df = pd.DataFrame(d)
|
||||
>>> score_to_mark(df.loc[0])
|
||||
1.0
|
||||
>>> score_to_mark(df.loc[10])
|
||||
1.3333333333333333
|
||||
"""
|
||||
# -1 is no answer
|
||||
if x[COLUMNS["score"]] == -1:
|
||||
return 0
|
||||
|
||||
if x[COLUMNS["is_leveled"]]:
|
||||
if x[COLUMNS["score"]] not in [0, 1, 2, 3]:
|
||||
raise ValueError(f"The evaluation is out of range: {x[COLUMNS['score']]} at {x}")
|
||||
#return round_half_point(x[COLUMNS["score"]] * x[COLUMNS["score_rate"]] / 3)
|
||||
return round(x[COLUMNS["score"]] * x[COLUMNS["score_rate"]] / 3, 2)
|
||||
|
||||
if x[COLUMNS["score"]] > x[COLUMNS["score_rate"]]:
|
||||
raise ValueError(
|
||||
f"The score ({x['score']}) is greated than the rating scale ({x[COLUMNS['score_rate']]}) at {x}"
|
||||
)
|
||||
return x[COLUMNS["score"]]
|
||||
|
||||
|
||||
def score_to_level(x):
|
||||
""" Compute the level (".",0,1,2,3).
|
||||
|
||||
:param x: dictionnary with COLUMNS["is_leveled"], COLUMNS["score"] and COLUMNS["score_rate"] keys
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... COLUMNS["score_rate"]:[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... COLUMNS["is_leveled"]:[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... COLUMNS["score"]:[1, 0.33, np.nan, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3],
|
||||
... }
|
||||
>>> df = pd.DataFrame(d)
|
||||
>>> score_to_level(df.loc[0])
|
||||
3
|
||||
>>> score_to_level(df.loc[1])
|
||||
1
|
||||
>>> score_to_level(df.loc[2])
|
||||
'na'
|
||||
>>> score_to_level(df.loc[3])
|
||||
3
|
||||
>>> score_to_level(df.loc[5])
|
||||
3
|
||||
>>> score_to_level(df.loc[10])
|
||||
2
|
||||
"""
|
||||
# negatives are no answer or negatives points
|
||||
if x[COLUMNS["score"]] <= -1:
|
||||
return np.nan
|
||||
|
||||
if x[COLUMNS["is_leveled"]]:
|
||||
return int(x[COLUMNS["score"]])
|
||||
|
||||
return int(ceil(x[COLUMNS["score"]] / x[COLUMNS["score_rate"]] * 3))
|
||||
|
||||
|
||||
# DataFrame columns manipulations
|
||||
|
||||
|
||||
def compute_mark(df):
|
||||
""" Add Mark column to df
|
||||
|
||||
:param df: DataFrame with COLUMNS["score"], COLUMNS["is_leveled"] and COLUMNS["score_rate"] columns.
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... COLUMNS["score_rate"]:[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... COLUMNS["is_leveled"]:[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... COLUMNS["score"]:[1, 0.33, 2, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3],
|
||||
... }
|
||||
>>> df = pd.DataFrame(d)
|
||||
>>> compute_mark(df)
|
||||
0 1.00
|
||||
1 0.33
|
||||
2 2.00
|
||||
3 1.50
|
||||
4 0.67
|
||||
5 2.00
|
||||
6 0.67
|
||||
7 1.00
|
||||
8 1.50
|
||||
9 1.00
|
||||
10 1.33
|
||||
11 2.00
|
||||
dtype: float64
|
||||
"""
|
||||
return df[[COLUMNS["score"], COLUMNS["is_leveled"], COLUMNS["score_rate"]]].apply(
|
||||
score_to_mark, axis=1
|
||||
)
|
||||
|
||||
|
||||
def compute_level(df):
|
||||
""" Add Mark column to df
|
||||
|
||||
:param df: DataFrame with COLUMNS["score"], COLUMNS["is_leveled"] and COLUMNS["score_rate"] columns.
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... COLUMNS["score_rate"]:[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... COLUMNS["is_leveled"]:[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... COLUMNS["score"]:[np.nan, 0.33, 2, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3],
|
||||
... }
|
||||
>>> df = pd.DataFrame(d)
|
||||
>>> compute_level(df)
|
||||
0 na
|
||||
1 1
|
||||
2 3
|
||||
3 3
|
||||
4 1
|
||||
5 3
|
||||
6 2
|
||||
7 3
|
||||
8 3
|
||||
9 2
|
||||
10 2
|
||||
11 3
|
||||
dtype: object
|
||||
"""
|
||||
return df[[COLUMNS["score"], COLUMNS["is_leveled"], COLUMNS["score_rate"]]].apply(
|
||||
score_to_level, axis=1
|
||||
)
|
||||
|
||||
|
||||
def compute_normalized(df):
|
||||
""" Compute the normalized mark (Mark / score_rate)
|
||||
|
||||
:param df: DataFrame with "Mark" and COLUMNS["score_rate"] columns
|
||||
|
||||
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6,
|
||||
... COLUMNS["score_rate"]:[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2,
|
||||
... COLUMNS["is_leveled"]:[0]*4+[1]*2 + [0]*4+[1]*2,
|
||||
... COLUMNS["score"]:[1, 0.33, 2, 1.5, 1, 3, 0.666, 1, 1.5, 1, 2, 3],
|
||||
... }
|
||||
>>> df = pd.DataFrame(d)
|
||||
>>> df["Mark"] = compute_marks(df)
|
||||
>>> compute_normalized(df)
|
||||
0 1.00
|
||||
1 0.33
|
||||
2 1.00
|
||||
3 0.75
|
||||
4 0.33
|
||||
5 1.00
|
||||
6 0.67
|
||||
7 1.00
|
||||
8 0.75
|
||||
9 0.50
|
||||
10 0.67
|
||||
11 1.00
|
||||
dtype: float64
|
||||
"""
|
||||
return df[COLUMNS["mark"]] / df[COLUMNS["score_rate"]]
|
||||
|
||||
|
||||
# Postprocessing question scores
|
||||
|
||||
|
||||
def pp_q_scores(df):
|
||||
""" Postprocessing questions scores dataframe
|
||||
|
||||
:param df: questions-scores dataframe
|
||||
:return: same data frame with mark, level and normalize columns
|
||||
"""
|
||||
assign = {
|
||||
COLUMNS["mark"]: compute_mark,
|
||||
COLUMNS["level"]: compute_level,
|
||||
COLUMNS["normalized"]: compute_normalized,
|
||||
}
|
||||
return df.assign(**assign)
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
|
@ -1,10 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import yaml
|
||||
|
||||
CONFIGPATH = "recoconfig.yml"
|
||||
|
||||
with open(CONFIGPATH, "r") as configfile:
|
||||
config = yaml.load(configfile, Loader=yaml.FullLoader)
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import click
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from PyInquirer import prompt, print_json
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
from .config import config
|
||||
from ..config import NO_ST_COLUMNS
|
||||
|
||||
|
||||
class PromptAbortException(EOFError):
|
||||
def __init__(self, message, errors=None):
|
||||
|
||||
# Call the base class constructor with the parameters it needs
|
||||
super(PromptAbortException, self).__init__("Abort questionnary", errors)
|
||||
|
||||
|
||||
def get_tribes(answers):
|
||||
""" List tribes based on subdirectory of config["source"] which have an "eleves.csv" file inside """
|
||||
return [
|
||||
p.name for p in Path(config["source"]).iterdir() if (p / "eleves.csv").exists()
|
||||
]
|
||||
|
||||
|
||||
def prepare_csv():
|
||||
items = new_eval()
|
||||
|
||||
item = items[0]
|
||||
# item = {"tribe": "308", "date": datetime.today(), "assessment": "plop"}
|
||||
csv_output = (
|
||||
Path(config["source"])
|
||||
/ item["tribe"]
|
||||
/ f"{item['date']:%y%m%d}_{item['assessment']}.csv"
|
||||
)
|
||||
|
||||
students = pd.read_csv(Path(config["source"]) / item["tribe"] / "eleves.csv")["Nom"]
|
||||
|
||||
columns = list(NO_ST_COLUMNS.keys())
|
||||
items = [[it[c] for c in columns] for it in items]
|
||||
columns = list(NO_ST_COLUMNS.values())
|
||||
items_df = pd.DataFrame.from_records(items, columns=columns)
|
||||
for s in students:
|
||||
items_df[s] = np.nan
|
||||
|
||||
items_df.to_csv(csv_output, index=False, date_format="%d/%m/%Y")
|
||||
click.echo(f"Saving csv file to {csv_output}")
|
||||
|
||||
|
||||
def new_eval(answers={}):
|
||||
click.echo(f"Préparation d'un nouveau devoir")
|
||||
|
||||
eval_questions = [
|
||||
{"type": "input", "name": "assessment", "message": "Nom de l'évaluation",},
|
||||
{
|
||||
"type": "list",
|
||||
"name": "tribe",
|
||||
"message": "Classe concernée",
|
||||
"choices": get_tribes,
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"name": "date",
|
||||
"message": "Date du devoir (%y%m%d)",
|
||||
"default": datetime.today().strftime("%y%m%d"),
|
||||
"filter": lambda val: datetime.strptime(val, "%y%m%d"),
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"name": "term",
|
||||
"message": "Trimestre",
|
||||
"choices": ["1", "2", "3"],
|
||||
},
|
||||
]
|
||||
|
||||
eval_ans = prompt(eval_questions)
|
||||
|
||||
items = []
|
||||
add_exo = True
|
||||
while add_exo:
|
||||
ex_items = new_exercice(eval_ans)
|
||||
items += ex_items
|
||||
add_exo = prompt(
|
||||
[
|
||||
{
|
||||
"type": "confirm",
|
||||
"name": "add_exo",
|
||||
"message": "Ajouter un autre exercice",
|
||||
"default": True,
|
||||
}
|
||||
]
|
||||
)["add_exo"]
|
||||
return items
|
||||
|
||||
|
||||
def new_exercice(answers={}):
|
||||
exercise_questions = [
|
||||
{"type": "input", "name": "exercise", "message": "Nom de l'exercice"},
|
||||
]
|
||||
|
||||
click.echo(f"Nouvel exercice")
|
||||
exercise_ans = prompt(exercise_questions, answers=answers)
|
||||
|
||||
items = []
|
||||
|
||||
add_item = True
|
||||
while add_item:
|
||||
try:
|
||||
item_ans = new_item(exercise_ans)
|
||||
except PromptAbortException:
|
||||
click.echo("Création de l'item annulée")
|
||||
else:
|
||||
items.append(item_ans)
|
||||
add_item = prompt(
|
||||
[
|
||||
{
|
||||
"type": "confirm",
|
||||
"name": "add_item",
|
||||
"message": f"Ajouter un autre item pour l'exercice {exercise_ans['exercise']}",
|
||||
"default": True,
|
||||
}
|
||||
]
|
||||
)["add_item"]
|
||||
|
||||
return items
|
||||
|
||||
|
||||
def new_item(answers={}):
|
||||
item_questions = [
|
||||
{"type": "input", "name": "question", "message": "Nom de l'item",},
|
||||
{"type": "input", "name": "comment", "message": "Commentaire",},
|
||||
{
|
||||
"type": "list",
|
||||
"name": "competence",
|
||||
"message": "Competence",
|
||||
"choices": ["Cher", "Rep", "Mod", "Rai", "Cal", "Com"],
|
||||
},
|
||||
{"type": "input", "name": "theme", "message": "Domaine",},
|
||||
{
|
||||
"type": "confirm",
|
||||
"name": "is_leveled",
|
||||
"message": "Évaluation par niveau",
|
||||
"default": True,
|
||||
},
|
||||
{"type": "input", "name": "score_rate", "message": "Bareme"},
|
||||
{
|
||||
"type": "confirm",
|
||||
"name": "correct",
|
||||
"message": "Tout est correct?",
|
||||
"default": True,
|
||||
},
|
||||
]
|
||||
click.echo(f"Nouvelle question pour l'exercice {answers['exercise']}")
|
||||
item_ans = prompt(item_questions, answers=answers)
|
||||
if item_ans["correct"]:
|
||||
return item_ans
|
||||
raise PromptAbortException("Abort item creation")
|
|
@ -1,102 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import click
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
import sys
|
||||
import papermill as pm
|
||||
from datetime import datetime
|
||||
|
||||
from .prepare_csv import prepare_csv
|
||||
from .config import config
|
||||
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
@cli.command()
|
||||
def print_config():
|
||||
click.echo(f"Config file is {CONFIGPATH}")
|
||||
click.echo("It contains")
|
||||
click.echo(config)
|
||||
|
||||
|
||||
def reporting(csv_file):
|
||||
# csv_file = Path(csv_file)
|
||||
tribe_dir = csv_file.parent
|
||||
csv_filename = csv_file.name.split(".")[0]
|
||||
|
||||
assessment = str(csv_filename).split("_")[-1].capitalize()
|
||||
date = str(csv_filename).split("_")[0]
|
||||
try:
|
||||
date = datetime.strptime(date, "%y%m%d")
|
||||
except ValueError:
|
||||
date = datetime.today().strptime(date, "%y%m%d")
|
||||
|
||||
tribe = str(tribe_dir).split("/")[-1]
|
||||
|
||||
template = Path(config["templates"]) / "tpl_evaluation.ipynb"
|
||||
|
||||
dest = Path(config["output"]) / tribe / csv_filename
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
click.echo(f"Building {assessment} ({date:%d/%m/%y}) report")
|
||||
pm.execute_notebook(
|
||||
str(template),
|
||||
str(dest / f"{assessment}.ipynb"),
|
||||
parameters=dict(
|
||||
tribe=tribe,
|
||||
assessment=assessment,
|
||||
date=f"{date:%d/%m/%y}",
|
||||
csv_file=str(csv_file.absolute()),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument("target", required=False)
|
||||
def report(target=""):
|
||||
""" Make a report for the eval
|
||||
|
||||
:param target: csv file or a directory where csvs are
|
||||
"""
|
||||
try:
|
||||
if target.endswith(".csv"):
|
||||
csv = Path(target)
|
||||
if not csv.exists():
|
||||
click.echo(f"{target} does not exists")
|
||||
sys.exit(1)
|
||||
if csv.suffix != ".csv":
|
||||
click.echo(f"{target} has to be a csv file")
|
||||
sys.exit(1)
|
||||
csvs = [csv]
|
||||
else:
|
||||
csvs = list(Path(target).glob("**/*.csv"))
|
||||
except AttributeError:
|
||||
csvs = list(Path(config["source"]).glob("**/*.csv"))
|
||||
|
||||
for csv in csvs:
|
||||
click.echo(f"Processing {csv}")
|
||||
try:
|
||||
reporting(csv)
|
||||
except pm.exceptions.PapermillExecutionError as e:
|
||||
click.echo(f"Error with {csv}: {e}")
|
||||
|
||||
|
||||
@cli.command()
|
||||
def prepare():
|
||||
""" Prepare csv file """
|
||||
|
||||
items = prepare_csv()
|
||||
|
||||
click.echo(items)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument("tribe")
|
||||
def random_pick(tribe):
|
||||
""" Randomly pick a student """
|
||||
pass
|
|
@ -1,76 +0,0 @@
|
|||
ansiwrap==0.8.4
|
||||
appdirs==1.4.3
|
||||
attrs==19.1.0
|
||||
backcall==0.1.0
|
||||
black==19.10b0
|
||||
bleach==3.1.0
|
||||
certifi==2019.6.16
|
||||
chardet==3.0.4
|
||||
Click==7.0
|
||||
colorama==0.4.1
|
||||
cycler==0.10.0
|
||||
decorator==4.4.0
|
||||
defusedxml==0.6.0
|
||||
entrypoints==0.3
|
||||
future==0.17.1
|
||||
idna==2.8
|
||||
importlib-resources==1.0.2
|
||||
ipykernel==5.1.3
|
||||
ipython==7.11.1
|
||||
ipython-genutils==0.2.0
|
||||
ipywidgets==7.5.1
|
||||
jedi==0.15.2
|
||||
Jinja2==2.10.3
|
||||
jsonschema==3.2.0
|
||||
jupyter==1.0.0
|
||||
jupyter-client==5.3.4
|
||||
jupyter-console==6.1.0
|
||||
jupyter-core==4.6.1
|
||||
jupytex==0.0.3
|
||||
kiwisolver==1.1.0
|
||||
Markdown==3.1.1
|
||||
MarkupSafe==1.1.1
|
||||
matplotlib==3.1.2
|
||||
mistune==0.8.4
|
||||
nbconvert==5.6.1
|
||||
nbformat==5.0.3
|
||||
notebook==6.0.3
|
||||
numpy==1.18.1
|
||||
pandas==0.25.3
|
||||
pandocfilters==1.4.2
|
||||
papermill==1.2.1
|
||||
parso==0.5.2
|
||||
pathspec==0.7.0
|
||||
pexpect==4.8.0
|
||||
pickleshare==0.7.5
|
||||
prometheus-client==0.7.1
|
||||
prompt-toolkit==1.0.14
|
||||
ptyprocess==0.6.0
|
||||
Pygments==2.5.2
|
||||
PyInquirer==1.0.3
|
||||
pyparsing==2.4.6
|
||||
pyrsistent==0.15.7
|
||||
python-dateutil==2.8.0
|
||||
pytz==2019.3
|
||||
PyYAML==5.3
|
||||
pyzmq==18.1.1
|
||||
qtconsole==4.6.0
|
||||
-e git+git_opytex:/lafrite/recopytex.git@7e026bedb24c1ca8bef3b71b3d63f8b0d6916e81#egg=Recopytex
|
||||
regex==2020.1.8
|
||||
requests==2.22.0
|
||||
scipy==1.4.1
|
||||
Send2Trash==1.5.0
|
||||
six==1.12.0
|
||||
tenacity==6.0.0
|
||||
terminado==0.8.3
|
||||
testpath==0.4.4
|
||||
textwrap3==0.9.2
|
||||
toml==0.10.0
|
||||
tornado==6.0.3
|
||||
tqdm==4.41.1
|
||||
traitlets==4.3.2
|
||||
typed-ast==1.4.1
|
||||
urllib3==1.25.8
|
||||
wcwidth==0.1.8
|
||||
webencodings==0.5.1
|
||||
widgetsnbextension==3.5.1
|
31
setup.py
31
setup.py
|
@ -1,31 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='Recopytex',
|
||||
version='1.1.1',
|
||||
description='Assessment analysis',
|
||||
author='Benjamin Bertrand',
|
||||
author_email='',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
'pandas',
|
||||
'numpy',
|
||||
'papermill',
|
||||
'pyyaml',
|
||||
'PyInquirer',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
recopytex=recopytex.scripts.recopytex:cli
|
||||
''',
|
||||
)
|
||||
|
||||
# -----------------------------
|
||||
# Reglages pour 'vim'
|
||||
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
||||
# cursor: 16 del
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,134 @@
|
|||
import sqlite3
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import clear_mappers, sessionmaker
|
||||
|
||||
from backend import config
|
||||
from backend.adapters.orm import metadata, start_mappers
|
||||
from backend.adapters.sqlite import create_db
|
||||
from backend.model.student import Student
|
||||
from backend.model.tribe import Tribe
|
||||
from tests.model.fakes import build_student, build_tribes
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def in_memory_db():
|
||||
engine = create_engine("sqlite:///:memory:")
|
||||
metadata.create_all(engine)
|
||||
return engine
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def session(in_memory_db):
|
||||
start_mappers()
|
||||
yield sessionmaker(bind=in_memory_db)()
|
||||
clear_mappers()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def memory_sqlite_conn():
|
||||
sqlite_db = ":memory:"
|
||||
conn = sqlite3.connect(sqlite_db)
|
||||
create_db(conn)
|
||||
yield conn
|
||||
conn.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def clean_db():
|
||||
sqlite_db = "sqlite.db"
|
||||
conn = sqlite3.connect(sqlite_db)
|
||||
create_db(conn)
|
||||
yield
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""DROP TABLE tribes""")
|
||||
cursor.execute("""DROP TABLE students""")
|
||||
conn.commit()
|
||||
|
||||
|
||||
def populate_tribes(conn) -> list[Tribe]:
|
||||
cursor = conn.cursor()
|
||||
tribes = build_tribes(3)
|
||||
cursor.executemany(
|
||||
"""
|
||||
INSERT INTO tribes(name, level) VALUES (?, ?)
|
||||
""",
|
||||
[t.to_tuple() for t in tribes],
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
return tribes
|
||||
|
||||
|
||||
def populate_students(conn, tribes: list[Tribe]) -> list[Student]:
|
||||
cursor = conn.cursor()
|
||||
prebuild_students = build_student(tribes, 2)
|
||||
cursor.executemany(
|
||||
"""
|
||||
INSERT INTO students(id, name, tribe_name) VALUES (:id, :name, :tribe_name)
|
||||
""",
|
||||
[s.to_dict() for s in prebuild_students],
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
return prebuild_students
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def populate_db():
|
||||
class Student_tribe_context:
|
||||
_tribes = []
|
||||
_students = []
|
||||
|
||||
def __init__(self, conn):
|
||||
self.conn = conn
|
||||
|
||||
def __enter__(self):
|
||||
self._tribes += populate_tribes(self.conn)
|
||||
self._students += populate_students(self.conn, self._tribes)
|
||||
return self._tribes, self._students
|
||||
|
||||
def __exit__(self, *args):
|
||||
|
||||
for student in self._students:
|
||||
self.conn.execute(
|
||||
"""
|
||||
DELETE FROM students WHERE id=:id
|
||||
""",
|
||||
{"id": student.id},
|
||||
)
|
||||
for tribe in self._tribes:
|
||||
self.conn.execute(
|
||||
"""
|
||||
DELETE FROM tribes WHERE name=:name
|
||||
""",
|
||||
{"name": tribe.name},
|
||||
)
|
||||
self.conn.commit()
|
||||
|
||||
def fixture(conn):
|
||||
return Student_tribe_context(conn)
|
||||
|
||||
yield fixture
|
||||
|
||||
|
||||
def wait_for_webapp_to_come_up():
|
||||
deadline = time.time() + 10
|
||||
url = config.get_api_url()
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
return requests.get(url)
|
||||
except ConnectionError:
|
||||
time.sleep(0.5)
|
||||
pytest.fail("API never came up")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def restart_api():
|
||||
(Path(__file__).parent.parent / "backend" / "api" / "main.py").touch()
|
||||
time.sleep(0.5)
|
||||
wait_for_webapp_to_come_up()
|
|
@ -0,0 +1,104 @@
|
|||
import pytest
|
||||
import requests
|
||||
|
||||
from backend import config
|
||||
from tests.model.fakes import build_student, build_tribes
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restart_api")
|
||||
@pytest.mark.usefixtures("clean_db")
|
||||
def test_api_post_student():
|
||||
url = config.get_api_url()
|
||||
tribe = build_tribes(1)[0]
|
||||
requests.post(f"{url}/tribes", json=tribe.to_dict())
|
||||
|
||||
data = {"name": "zart", "tribe_name": tribe.name}
|
||||
r = requests.post(f"{url}/students", json=data)
|
||||
|
||||
post_request = r.history[0]
|
||||
assert post_request.status_code == 302
|
||||
|
||||
assert r.status_code == 200
|
||||
assert r.json()["name"] == "zart"
|
||||
assert r.json()["tribe_name"] == tribe.name
|
||||
assert r.json()["id"]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restart_api")
|
||||
@pytest.mark.usefixtures("clean_db")
|
||||
def test_api_post_student_with_id():
|
||||
url = config.get_api_url()
|
||||
tribe = build_tribes(1)[0]
|
||||
requests.post(f"{url}/tribes", json=tribe.to_dict())
|
||||
|
||||
data = {"id": "1234", "name": "zart", "tribe_name": tribe.name}
|
||||
requests.post(f"{url}/students", json=data)
|
||||
r = requests.post(f"{url}/students", json=data)
|
||||
|
||||
assert r.status_code == 409
|
||||
assert (
|
||||
r.json()
|
||||
== f"You can't post a student with an id. It is already registrered. Use PUT to modify it."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restart_api")
|
||||
@pytest.mark.usefixtures("clean_db")
|
||||
def test_api_post_student_in_non_existant_tribe():
|
||||
url = config.get_api_url()
|
||||
tribe = build_tribes(1)[0]
|
||||
requests.post(f"{url}/tribes", json=tribe.to_dict())
|
||||
|
||||
data = {"name": "zart", "tribe_name": tribe.name + "_"}
|
||||
requests.post(f"{url}/students", json=data)
|
||||
r = requests.post(f"{url}/students", json=data)
|
||||
|
||||
assert r.status_code == 409
|
||||
assert (
|
||||
r.json()
|
||||
== f"The tribe {tribe.name+'_'} does not exists. You can't add a student in it."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restart_api")
|
||||
@pytest.mark.usefixtures("clean_db")
|
||||
def test_api_put_student():
|
||||
url = config.get_api_url()
|
||||
tribe = build_tribes(1)[0]
|
||||
requests.post(f"{url}/tribes", json=tribe.to_dict())
|
||||
|
||||
data = {"name": "zart", "tribe_name": tribe.name}
|
||||
r = requests.post(f"{url}/students", json=data)
|
||||
|
||||
student = r.json()
|
||||
student["name"] = "Choupinou"
|
||||
|
||||
r2 = requests.put(f"{url}/students/{student['id']}", json=student)
|
||||
|
||||
post_request = r2.history[0]
|
||||
assert post_request.status_code == 302
|
||||
|
||||
assert r2.status_code == 200
|
||||
assert r2.json()["name"] == "Choupinou"
|
||||
assert r2.json()["tribe_name"] == tribe.name
|
||||
assert r2.json()["id"] == r.json()["id"]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restart_api")
|
||||
@pytest.mark.usefixtures("clean_db")
|
||||
def test_api_delete_student():
|
||||
url = config.get_api_url()
|
||||
tribe = build_tribes(1)[0]
|
||||
requests.post(f"{url}/tribes", json=tribe.to_dict())
|
||||
|
||||
student = build_student([tribe], 1)[0]
|
||||
r = requests.post(
|
||||
f"{url}/students", json={"name": student.name, "tribe_name": student.tribe.name}
|
||||
)
|
||||
student_id = r.json()["id"]
|
||||
|
||||
r = requests.delete(f"{url}/students/{student_id}")
|
||||
assert r.status_code == 204
|
||||
|
||||
r = requests.get(f"{url}/students/")
|
||||
assert r.json() == []
|
|
@ -0,0 +1,119 @@
|
|||
import pytest
|
||||
import requests
|
||||
|
||||
from backend import config
|
||||
from tests.model.fakes import build_tribes
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restart_api")
|
||||
@pytest.mark.usefixtures("clean_db")
|
||||
def test_api_post_tribe():
|
||||
data = {"name": "tribe", "level": "2nd"}
|
||||
|
||||
url = config.get_api_url()
|
||||
r = requests.post(f"{url}/tribes", json=data)
|
||||
|
||||
post_request = r.history[0]
|
||||
assert post_request.status_code == 302
|
||||
|
||||
assert r.status_code == 200
|
||||
assert r.json() == {
|
||||
"assessments": [],
|
||||
"level": "2nd",
|
||||
"name": "tribe",
|
||||
"students": [],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restart_api")
|
||||
@pytest.mark.usefixtures("clean_db")
|
||||
def test_api_post_tribe_already_exists():
|
||||
data = {"name": "Pioupiou", "level": "2nd"}
|
||||
|
||||
url = config.get_api_url()
|
||||
requests.post(f"{url}/tribes", json=data)
|
||||
r = requests.post(f"{url}/tribes", json=data)
|
||||
|
||||
assert r.status_code == 409
|
||||
assert r.json() == f"The tribe {data['name']} already exists"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restart_api")
|
||||
@pytest.mark.usefixtures("clean_db")
|
||||
def test_api_put_tribe():
|
||||
tribe = build_tribes(1)[0]
|
||||
|
||||
url = config.get_api_url()
|
||||
r = requests.post(f"{url}/tribes", json=tribe.to_dict())
|
||||
|
||||
mod_tribe = tribe
|
||||
mod_tribe.level = "other level"
|
||||
r = requests.put(f"{url}/tribes/{tribe.name}", json=mod_tribe.to_dict())
|
||||
post_request = r.history[0]
|
||||
assert post_request.status_code == 302
|
||||
|
||||
assert r.status_code == 200
|
||||
|
||||
r = requests.get(f"{url}/tribes")
|
||||
assert [t["name"] for t in r.json()] == [mod_tribe.name]
|
||||
assert [t["level"] for t in r.json()] == [mod_tribe.level]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restart_api")
|
||||
@pytest.mark.usefixtures("clean_db")
|
||||
def test_api_put_tribe_doesnt_exists():
|
||||
tribe = build_tribes(1)[0]
|
||||
|
||||
url = config.get_api_url()
|
||||
r = requests.put(f"{url}/tribes/{tribe.name}", json=tribe.to_dict())
|
||||
assert r.status_code == 409
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restart_api")
|
||||
@pytest.mark.usefixtures("clean_db")
|
||||
def test_api_delete_tribe():
|
||||
tribe = build_tribes(1)[0]
|
||||
|
||||
url = config.get_api_url()
|
||||
r = requests.post(f"{url}/tribes", json=tribe.to_dict())
|
||||
|
||||
r = requests.delete(f"{url}/tribes/{tribe.name}")
|
||||
assert r.status_code == 204
|
||||
|
||||
r = requests.get(f"{url}/tribes")
|
||||
assert r.json() == []
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restart_api")
|
||||
@pytest.mark.usefixtures("clean_db")
|
||||
def test_api_delete_tribe_doesnt_exists():
|
||||
tribe = build_tribes(1)[0]
|
||||
|
||||
url = config.get_api_url()
|
||||
r = requests.post(f"{url}/tribes", json=tribe.to_dict())
|
||||
|
||||
r = requests.delete(f"{url}/tribes/notexisting")
|
||||
assert r.status_code == 409
|
||||
|
||||
r = requests.get(f"{url}/tribes")
|
||||
assert [t["name"] for t in r.json()] == [tribe.name]
|
||||
assert [t["level"] for t in r.json()] == [tribe.level]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restart_api")
|
||||
@pytest.mark.usefixtures("clean_db")
|
||||
def test_api_post_list_tribe():
|
||||
tribe = build_tribes(1)[0]
|
||||
|
||||
url = config.get_api_url()
|
||||
r = requests.post(f"{url}/tribes", json=tribe.to_dict())
|
||||
|
||||
r = requests.get(f"{url}/tribes")
|
||||
assert r.json() == [
|
||||
{
|
||||
"assessments": [],
|
||||
"level": tribe.level,
|
||||
"name": tribe.name,
|
||||
"students": [],
|
||||
}
|
||||
]
|
|
@ -0,0 +1,43 @@
|
|||
from backend.adapters.orm import metadata, start_mappers
|
||||
from backend.model.student import Student
|
||||
from backend.model.tribe import Tribe
|
||||
|
||||
|
||||
def test_tribes_mapper_can_load_tribe(session):
|
||||
session.execute(
|
||||
"INSERT INTO tribes (name, level) VALUES "
|
||||
"('tribe1', '2nd'),"
|
||||
"('tribe2', '1ST')"
|
||||
)
|
||||
expected = [
|
||||
(Tribe("tribe1", "2nd")),
|
||||
(Tribe("tribe2", "1ST")),
|
||||
]
|
||||
assert session.query(Tribe).all() == expected
|
||||
|
||||
|
||||
def test_tribe_mapper_can_save_tribe(session):
|
||||
tribe_infos = ("tribe1", "2nd")
|
||||
tribe = Tribe(*tribe_infos)
|
||||
session.add(tribe)
|
||||
|
||||
rows = list(session.execute("SELECT name, level FROM 'tribes'"))
|
||||
assert rows == []
|
||||
|
||||
session.commit()
|
||||
|
||||
rows = list(session.execute("SELECT name, level FROM 'tribes'"))
|
||||
assert rows == [tribe_infos]
|
||||
|
||||
|
||||
def test_tribe_mapper_can_save_and_load_tribe(session):
|
||||
tribe_infos = ("tribe1", "2nd")
|
||||
tribe = Tribe(*tribe_infos)
|
||||
|
||||
assert session.query(Tribe).all() == []
|
||||
session.add(tribe)
|
||||
|
||||
assert session.query(Tribe).all() == [tribe]
|
||||
session.commit()
|
||||
|
||||
assert session.query(Tribe).all() == [tribe]
|
|
@ -0,0 +1,107 @@
|
|||
import sqlite3
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.model.student import Student
|
||||
from backend.repository.student_sqlite_repository import (
|
||||
StudentRepositoryError,
|
||||
StudentSQLiteRepository,
|
||||
)
|
||||
|
||||
|
||||
def test_get_student(memory_sqlite_conn, populate_db):
|
||||
with populate_db(memory_sqlite_conn) as (prebuild_tribes, prebuild_students):
|
||||
student_repo = StudentSQLiteRepository(memory_sqlite_conn)
|
||||
|
||||
student_id = prebuild_students[0].id
|
||||
student = student_repo.get(student_id, prebuild_tribes)
|
||||
|
||||
assert prebuild_students[0] == student
|
||||
|
||||
|
||||
def test_get_student_not_exists(memory_sqlite_conn, populate_db):
|
||||
with populate_db(memory_sqlite_conn) as (prebuild_tribes, _):
|
||||
student_repo = StudentSQLiteRepository(memory_sqlite_conn)
|
||||
with pytest.raises(ValueError):
|
||||
student_repo.get("student0", prebuild_tribes)
|
||||
|
||||
|
||||
def test_list_students(memory_sqlite_conn, populate_db):
|
||||
with populate_db(memory_sqlite_conn) as (prebuild_tribes, prebuild_students):
|
||||
student_repo = StudentSQLiteRepository(memory_sqlite_conn)
|
||||
students = student_repo.list(prebuild_tribes)
|
||||
|
||||
assert prebuild_students == students
|
||||
|
||||
|
||||
def test_add_student(memory_sqlite_conn, populate_db):
|
||||
with populate_db(memory_sqlite_conn) as (prebuild_tribes, _):
|
||||
student_repo = StudentSQLiteRepository(memory_sqlite_conn)
|
||||
|
||||
student_infos = {"name": "student1", "tribe": prebuild_tribes[0]}
|
||||
student = Student(**student_infos)
|
||||
student_repo.add(student)
|
||||
memory_sqlite_conn.commit()
|
||||
|
||||
cursor = memory_sqlite_conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT id, name, tribe_name FROM students WHERE id=?
|
||||
""",
|
||||
(student.id,),
|
||||
)
|
||||
|
||||
row = cursor.fetchone()
|
||||
assert row == student.to_tuple()
|
||||
|
||||
|
||||
def test_add_student_fail_exists(memory_sqlite_conn, populate_db):
|
||||
with populate_db(memory_sqlite_conn) as (prebuild_tribes, _):
|
||||
student_repo = StudentSQLiteRepository(memory_sqlite_conn)
|
||||
|
||||
student_infos = {"name": "student1", "tribe": prebuild_tribes[0]}
|
||||
student = Student(**student_infos)
|
||||
student_repo.add(student)
|
||||
memory_sqlite_conn.commit()
|
||||
|
||||
with pytest.raises(sqlite3.IntegrityError):
|
||||
student_repo.add(student)
|
||||
|
||||
|
||||
def test_update_student(memory_sqlite_conn, populate_db):
|
||||
with populate_db(memory_sqlite_conn) as (prebuild_tribes, prebuild_students):
|
||||
student_repo = StudentSQLiteRepository(memory_sqlite_conn)
|
||||
|
||||
student = prebuild_students[0]
|
||||
student.name = "Boby"
|
||||
student.tribe = prebuild_tribes[-1]
|
||||
|
||||
student_repo.update(student)
|
||||
memory_sqlite_conn.commit()
|
||||
|
||||
student_list = student_repo.list(prebuild_tribes)
|
||||
assert set(student_list) == set(prebuild_students)
|
||||
|
||||
moded_student = next(filter(lambda s: s.id == student.id, student_list))
|
||||
assert moded_student == student
|
||||
|
||||
|
||||
def test_update_student_does_not_exists(memory_sqlite_conn, populate_db):
|
||||
with populate_db(memory_sqlite_conn) as (prebuild_tribes, _):
|
||||
student_repo = StudentSQLiteRepository(memory_sqlite_conn)
|
||||
|
||||
student = Student(name="jkl", tribe=prebuild_tribes[0])
|
||||
|
||||
with pytest.raises(StudentRepositoryError):
|
||||
student_repo.update(student)
|
||||
|
||||
|
||||
def test_delete_student(memory_sqlite_conn, populate_db):
|
||||
with populate_db(memory_sqlite_conn) as (prebuild_tribes, prebuild_students):
|
||||
student_repo = StudentSQLiteRepository(memory_sqlite_conn)
|
||||
|
||||
deleted_student = prebuild_students.pop()
|
||||
student_repo.delete(deleted_student.id)
|
||||
memory_sqlite_conn.commit()
|
||||
|
||||
assert student_repo.list(prebuild_tribes) == prebuild_students
|
|
@ -0,0 +1,93 @@
|
|||
import pytest
|
||||
|
||||
from backend.model.tribe import Tribe
|
||||
from backend.repository.tribe_sqlite_repository import (
|
||||
TribeRepositoryError,
|
||||
TribeSQLiteRepository,
|
||||
)
|
||||
|
||||
|
||||
def test_get_tribe(memory_sqlite_conn, populate_db):
|
||||
with populate_db(memory_sqlite_conn) as (prebuild_tribes, _):
|
||||
name = prebuild_tribes[0].name
|
||||
|
||||
tribe_repo = TribeSQLiteRepository(memory_sqlite_conn)
|
||||
tribes = tribe_repo.get(name)
|
||||
|
||||
assert prebuild_tribes[0] == tribes
|
||||
|
||||
|
||||
def test_get_tribe_not_exists(memory_sqlite_conn):
|
||||
tribe_repo = TribeSQLiteRepository(memory_sqlite_conn)
|
||||
with pytest.raises(TribeRepositoryError):
|
||||
tribe_repo.get("Tribe0")
|
||||
|
||||
|
||||
def test_list_tribes(memory_sqlite_conn, populate_db):
|
||||
with populate_db(memory_sqlite_conn) as (prebuild_tribes, _):
|
||||
tribe_repo = TribeSQLiteRepository(memory_sqlite_conn)
|
||||
listed_tribes = tribe_repo.list()
|
||||
|
||||
assert prebuild_tribes == listed_tribes
|
||||
|
||||
|
||||
def test_add_tribe(memory_sqlite_conn):
|
||||
tribe_repo = TribeSQLiteRepository(memory_sqlite_conn)
|
||||
|
||||
tribe_infos = ("tribe1", "2nd")
|
||||
tribe = Tribe(*tribe_infos)
|
||||
tribe_repo.add(tribe)
|
||||
memory_sqlite_conn.commit()
|
||||
|
||||
cursor = memory_sqlite_conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT * FROM tribes WHERE name=?
|
||||
""",
|
||||
("tribe1",),
|
||||
)
|
||||
|
||||
row = cursor.fetchone()
|
||||
assert row == tribe_infos
|
||||
|
||||
|
||||
def test_add_tribe_fail_exists(memory_sqlite_conn, populate_db):
|
||||
with populate_db(memory_sqlite_conn) as (prebuild_tribes, _):
|
||||
tribe_repo = TribeSQLiteRepository(memory_sqlite_conn)
|
||||
|
||||
existing_tribe = prebuild_tribes[0]
|
||||
with pytest.raises(TribeRepositoryError):
|
||||
tribe_repo.add(existing_tribe)
|
||||
|
||||
|
||||
def test_update_tribe(memory_sqlite_conn, populate_db):
|
||||
|
||||
with populate_db(memory_sqlite_conn) as (prebuild_tribes, _):
|
||||
tribe_repo = TribeSQLiteRepository(memory_sqlite_conn)
|
||||
|
||||
name = prebuild_tribes[0].name
|
||||
new_tribe = Tribe("Tribe0", "Term")
|
||||
tribe_repo.update(name, new_tribe)
|
||||
memory_sqlite_conn.commit()
|
||||
|
||||
prebuild_tribes[0] = new_tribe
|
||||
assert tribe_repo.list() == prebuild_tribes
|
||||
|
||||
|
||||
def test_update_tribe_not_exists(memory_sqlite_conn, populate_db):
|
||||
with populate_db(memory_sqlite_conn) as (prebuild_tribes, _):
|
||||
tribe_repo = TribeSQLiteRepository(memory_sqlite_conn)
|
||||
|
||||
name = prebuild_tribes[0].name
|
||||
new_tribe = Tribe("Tribe0", "Term")
|
||||
with pytest.raises(TribeRepositoryError):
|
||||
tribe_repo.update("iouiou", new_tribe)
|
||||
|
||||
|
||||
def test_delete_tribe(memory_sqlite_conn, populate_db):
|
||||
with populate_db(memory_sqlite_conn) as (prebuild_tribes, _):
|
||||
tribe_repo = TribeSQLiteRepository(memory_sqlite_conn)
|
||||
deleted_tribe = prebuild_tribes.pop()
|
||||
deleted_tribe.name = "iouiou"
|
||||
with pytest.raises(TribeRepositoryError):
|
||||
tribe_repo.delete(deleted_tribe)
|
|
@ -0,0 +1,93 @@
|
|||
from random import choice, randint
|
||||
|
||||
from faker import Faker
|
||||
from faker.providers import DynamicProvider
|
||||
|
||||
from backend.model.assessment import Assessment, Domain, Exercise, Question, Skill
|
||||
from backend.model.student import Student
|
||||
from backend.model.tribe import Tribe
|
||||
from backend.repository.abstract_repository import AbstractRepository
|
||||
|
||||
level_provider = DynamicProvider(
|
||||
provider_name="level",
|
||||
elements=["2nd", "1ST", "SNT", "1G", "TG", "EnsSci"],
|
||||
)
|
||||
|
||||
faker = Faker("fr_FR")
|
||||
faker.add_provider(level_provider)
|
||||
|
||||
|
||||
def build_tribes(quantity: int = 1) -> list[Tribe]:
|
||||
return [Tribe(name=faker.word(), level=faker.level()) for _ in range(quantity)]
|
||||
|
||||
|
||||
def build_assessments(
|
||||
tribes: list[Tribe], assessment_per_tribe: int = 1
|
||||
) -> list[Assessment]:
|
||||
|
||||
assessments = []
|
||||
for t in tribes:
|
||||
assessments += [
|
||||
Assessment("faker.word()", t, randint(1, 3))
|
||||
for _ in range(assessment_per_tribe)
|
||||
]
|
||||
|
||||
return assessments
|
||||
|
||||
|
||||
def build_exercises(
|
||||
assessments: list[Assessment], exercise_per_assessment=1
|
||||
) -> list[Exercise]:
|
||||
exercises = []
|
||||
for assessment in assessments:
|
||||
exercises += [
|
||||
Exercise("faker.word()", assessment, "today")
|
||||
for _ in range(exercise_per_assessment)
|
||||
]
|
||||
|
||||
return exercises
|
||||
|
||||
|
||||
def build_skills(quantity=1) -> list[Skill]:
|
||||
return [Skill(faker.word(), faker.text()) for _ in range(quantity)]
|
||||
|
||||
|
||||
def build_domains(quantity=1) -> list[Domain]:
|
||||
return [Domain(faker.word(), faker.text()) for _ in range(quantity)]
|
||||
|
||||
|
||||
def build_questions(
|
||||
exercises: list[Exercise],
|
||||
question_per_exercise=1,
|
||||
) -> list[Question]:
|
||||
skills = build_skills()
|
||||
domains = build_domains()
|
||||
|
||||
questions = []
|
||||
for exercise in exercises:
|
||||
questions += [
|
||||
Question(
|
||||
faker.word(),
|
||||
exercise,
|
||||
description="desc",
|
||||
skill=choice(skills),
|
||||
domain=choice(domains),
|
||||
is_leveled=choice([True, False]),
|
||||
scale=randint(1, 20),
|
||||
)
|
||||
for _ in range(question_per_exercise)
|
||||
]
|
||||
|
||||
return questions
|
||||
|
||||
|
||||
def build_student(
|
||||
tribes: list[Tribe],
|
||||
students_per_tribe=1,
|
||||
) -> list[Student]:
|
||||
students = []
|
||||
for tribe in tribes:
|
||||
students += [
|
||||
Student(name=faker.name(), tribe=tribe) for _ in range(students_per_tribe)
|
||||
]
|
||||
return students
|
|
@ -0,0 +1,26 @@
|
|||
from random import choice, randint
|
||||
|
||||
from tests.model.fakes import build_questions
|
||||
|
||||
from .fakes import build_assessments, build_exercises, build_tribes
|
||||
|
||||
|
||||
def test_assessement_register_exercise():
|
||||
exercise_qty = randint(1, 10)
|
||||
tribes = build_tribes(1)
|
||||
assessments = build_assessments(tribes, 1)
|
||||
exercises = build_exercises(assessments, exercise_qty)
|
||||
|
||||
assert len(assessments[0].exercises) == exercise_qty
|
||||
assert assessments[0].exercises == exercises
|
||||
|
||||
|
||||
def test_exercise_register_question():
|
||||
question_qty = randint(1, 10)
|
||||
tribes = build_tribes(1)
|
||||
assessments = build_assessments(tribes, 1)
|
||||
exercises = build_exercises(assessments, 1)
|
||||
questions = build_questions(exercises, question_qty)
|
||||
|
||||
assert len(exercises[0].questions) == question_qty
|
||||
assert exercises[0].questions == questions
|
|
@ -0,0 +1,16 @@
|
|||
from backend.model.student import Student
|
||||
from backend.model.tribe import Tribe
|
||||
from tests.model.fakes import build_tribes
|
||||
|
||||
|
||||
def test_init_student():
|
||||
tribe = build_tribes(1)[0]
|
||||
student = Student(name="Bob", tribe=tribe)
|
||||
print(student)
|
||||
|
||||
assert type(student.name) == str
|
||||
assert type(student.tribe) == Tribe
|
||||
assert type(student.id) == str
|
||||
|
||||
student2 = Student(name="Hop", tribe=tribe)
|
||||
assert student.id != student2.id
|
|
@ -0,0 +1,33 @@
|
|||
from random import randint
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.model.student import Student
|
||||
from backend.model.tribe import Tribe, TribeError
|
||||
from tests.model.fakes import build_assessments, build_tribes
|
||||
|
||||
|
||||
def test_tribe_register_assessment():
|
||||
assessments_qty = randint(1, 10)
|
||||
tribes = build_tribes(1)
|
||||
assessments = build_assessments(tribes, assessments_qty)
|
||||
|
||||
assert len(tribes[0].assessments) == assessments_qty
|
||||
assert tribes[0].assessments == assessments
|
||||
|
||||
|
||||
def test_tribe_register_student():
|
||||
tribe = Tribe("foo", "2nd")
|
||||
student = Student(id="1", name="Bob", tribe=tribe)
|
||||
|
||||
assert len(tribe.students) == 1
|
||||
assert tribe.students[0] == student
|
||||
|
||||
|
||||
def test_tribe_register_student_already_exists_overwrite():
|
||||
tribe = Tribe("foo", "2nd")
|
||||
student = Student(id="1", name="Bob", tribe=tribe)
|
||||
changed_student = Student(id="1", name="Choupy", tribe=tribe)
|
||||
|
||||
assert tribe.students[0] == changed_student
|
||||
assert student.name not in [s.name for s in tribe.students]
|
|
@ -0,0 +1,350 @@
|
|||
import pytest
|
||||
|
||||
from backend.model.student import Student
|
||||
from backend.model.tribe import Tribe
|
||||
from backend.repository.abstract_repository import AbstractRepository
|
||||
from backend.repository.student_sqlite_repository import StudentRepositoryError
|
||||
from backend.repository.tribe_sqlite_repository import TribeRepositoryError
|
||||
from backend.service import services
|
||||
from backend.service.services import (
|
||||
StudentDoesExist,
|
||||
StudentExists,
|
||||
TribeDoesNotExist,
|
||||
TribeExists,
|
||||
)
|
||||
from tests.model.fakes import build_student, build_tribes
|
||||
|
||||
|
||||
class FakeTribeRepository(AbstractRepository):
|
||||
def __init__(self, tribes: list[Tribe] = []) -> None:
|
||||
self._tribes = {t.name: t for t in tribes}
|
||||
|
||||
def add(self, tribe: Tribe) -> None:
|
||||
if tribe.name not in self._tribes.keys():
|
||||
self._tribes[tribe.name] = tribe
|
||||
else:
|
||||
raise TribeRepositoryError(f"{tribe} already exists")
|
||||
|
||||
def update(self, name: str, tribe: Tribe) -> None:
|
||||
try:
|
||||
self._tribes.pop(name)
|
||||
self._tribes[tribe.name] = tribe
|
||||
except KeyError:
|
||||
raise TribeRepositoryError(f"The tribe {tribe} does not exists")
|
||||
|
||||
def list(self) -> list[Tribe]:
|
||||
return list(self._tribes.values())
|
||||
|
||||
def get(self, name: str) -> Tribe:
|
||||
try:
|
||||
return self._tribes[name]
|
||||
except KeyError:
|
||||
raise TribeRepositoryError(f"The tribe {name} does not exists")
|
||||
|
||||
def delete(self, name: str) -> None:
|
||||
try:
|
||||
self._tribes.pop(name)
|
||||
except KeyError:
|
||||
raise TribeRepositoryError(f"The tribe {name} does not exists")
|
||||
|
||||
|
||||
class FakeStudentRepository(AbstractRepository):
|
||||
def __init__(self, students: list[Student] = []) -> None:
|
||||
self._students = {s.id: s for s in students}
|
||||
|
||||
def add(self, student: Student) -> None:
|
||||
if student.id not in self._students.keys():
|
||||
self._students[student.id] = student
|
||||
else:
|
||||
raise StudentRepositoryError(f"{student} already exists")
|
||||
|
||||
def update(self, student: Student) -> None:
|
||||
if student.id not in self._students.keys():
|
||||
raise StudentRepositoryError(f"The student {student} does not exists")
|
||||
|
||||
self._students[student.id] = student
|
||||
|
||||
def list(self) -> list[Student]:
|
||||
return list(self._students.values())
|
||||
|
||||
def get(self, id: str) -> Student:
|
||||
try:
|
||||
return self._students[id]
|
||||
except KeyError:
|
||||
raise KeyError(f"The student ({id=}) does not exists")
|
||||
|
||||
def delete(self, id: str) -> None:
|
||||
try:
|
||||
self._students.pop(id)
|
||||
except KeyError:
|
||||
raise StudentRepositoryError(f"The student with id {id} does not exists")
|
||||
|
||||
|
||||
class FakeConn:
|
||||
committed = False
|
||||
|
||||
def commit(self):
|
||||
self.committed = True
|
||||
|
||||
def reset_commit(self):
|
||||
self.committed = False
|
||||
|
||||
|
||||
def test_add_tribe():
|
||||
tribe_repo = FakeTribeRepository()
|
||||
tribe = build_tribes(1)[0]
|
||||
conn = FakeConn()
|
||||
services.add_tribe(
|
||||
name=tribe.name, level=tribe.level, tribe_repo=tribe_repo, conn=conn
|
||||
)
|
||||
|
||||
assert conn.committed is True
|
||||
assert tribe_repo.list() == [tribe]
|
||||
|
||||
|
||||
def test_add_tribe_fail_exists():
|
||||
tribe_repo = FakeTribeRepository()
|
||||
tribe = build_tribes(1)[0]
|
||||
conn = FakeConn()
|
||||
services.add_tribe(
|
||||
name=tribe.name, level=tribe.level, tribe_repo=tribe_repo, conn=conn
|
||||
)
|
||||
conn.reset_commit()
|
||||
|
||||
with pytest.raises(TribeExists):
|
||||
services.add_tribe(
|
||||
name=tribe.name, level=tribe.level, tribe_repo=tribe_repo, conn=conn
|
||||
)
|
||||
|
||||
assert conn.committed == False
|
||||
|
||||
|
||||
def test_update_tribe():
|
||||
tribes = build_tribes(3)
|
||||
tribe_repo = FakeTribeRepository(tribes)
|
||||
conn = FakeConn()
|
||||
|
||||
other_level = "iouiouiou"
|
||||
tribes[0].level = other_level
|
||||
services.update_tribe(
|
||||
name=tribes[0].name, level=other_level, tribe_repo=tribe_repo, conn=conn
|
||||
)
|
||||
|
||||
assert conn.committed is True
|
||||
assert set(tribe_repo.list()) == set(tribes)
|
||||
|
||||
|
||||
def test_update_tribe_fail_not_exists():
|
||||
tribes = build_tribes(3)
|
||||
tribe_repo = FakeTribeRepository(tribes)
|
||||
conn = FakeConn()
|
||||
|
||||
with pytest.raises(TribeDoesNotExist):
|
||||
services.update_tribe(
|
||||
name="azerty", level="jkl", tribe_repo=tribe_repo, conn=conn
|
||||
)
|
||||
|
||||
assert conn.committed == False
|
||||
|
||||
|
||||
def test_delete_tribe():
|
||||
tribes = build_tribes(3)
|
||||
tribe_repo = FakeTribeRepository(tribes)
|
||||
conn = FakeConn()
|
||||
|
||||
tribe = tribes.pop()
|
||||
services.delete_tribe(name=tribe.name, tribe_repo=tribe_repo, conn=conn)
|
||||
|
||||
assert conn.committed is True
|
||||
assert set(tribe_repo.list()) == set(tribes)
|
||||
|
||||
|
||||
def test_delete_tribe_fail_not_exists():
|
||||
tribes = build_tribes(3)
|
||||
tribe_repo = FakeTribeRepository(tribes)
|
||||
conn = FakeConn()
|
||||
|
||||
with pytest.raises(TribeDoesNotExist):
|
||||
services.delete_tribe(name="azerty", tribe_repo=tribe_repo, conn=conn)
|
||||
|
||||
assert conn.committed == False
|
||||
|
||||
|
||||
def test_add_student():
|
||||
tribes = build_tribes(1)
|
||||
tribe_repo = FakeTribeRepository(tribes)
|
||||
student = build_student(tribes, 1)[0]
|
||||
student_repo = FakeStudentRepository()
|
||||
conn = FakeConn()
|
||||
|
||||
saved_student = services.add_student(
|
||||
name=student.name,
|
||||
tribe=student.tribe.name,
|
||||
student_repo=student_repo,
|
||||
tribe_repo=tribe_repo,
|
||||
conn=conn,
|
||||
)
|
||||
|
||||
assert conn.committed is True
|
||||
|
||||
listed_student = student_repo.list()[0]
|
||||
|
||||
assert student.name == listed_student.name
|
||||
assert student.tribe.name == listed_student.tribe.name
|
||||
# The id is not passed to the service, they can't have the same.
|
||||
assert student.id != listed_student.id
|
||||
|
||||
assert saved_student == listed_student
|
||||
|
||||
|
||||
def test_add_student_tribe_doesnt_exist():
|
||||
tribes = build_tribes(1)
|
||||
tribe_repo = FakeTribeRepository(tribes)
|
||||
students = build_student(tribes, 1)
|
||||
student_repo = FakeStudentRepository()
|
||||
conn = FakeConn()
|
||||
|
||||
student = students[0]
|
||||
with pytest.raises(TribeDoesNotExist):
|
||||
services.add_student(
|
||||
name=student.name,
|
||||
tribe="iuouiouiouio",
|
||||
student_repo=student_repo,
|
||||
tribe_repo=tribe_repo,
|
||||
conn=conn,
|
||||
)
|
||||
|
||||
assert conn.committed is False
|
||||
|
||||
|
||||
def test_update_student():
|
||||
tribes = build_tribes(2)
|
||||
tribe_repo = FakeTribeRepository(tribes)
|
||||
students = build_student(tribes, 1)
|
||||
student_repo = FakeStudentRepository(students)
|
||||
conn = FakeConn()
|
||||
|
||||
id = students[0].id
|
||||
new_name = "new name"
|
||||
new_tribe_name = tribes[1].name
|
||||
|
||||
saved_student = services.update_student(
|
||||
id=id,
|
||||
name=new_name,
|
||||
tribe=new_tribe_name,
|
||||
student_repo=student_repo,
|
||||
tribe_repo=tribe_repo,
|
||||
conn=conn,
|
||||
)
|
||||
|
||||
assert conn.committed is True
|
||||
|
||||
mod_student = student_repo.get(id)
|
||||
assert mod_student.name == new_name
|
||||
assert mod_student.tribe.name == new_tribe_name
|
||||
|
||||
listed_student = student_repo.list()
|
||||
assert len(listed_student) == 2
|
||||
|
||||
|
||||
def test_update_student_tribe_doesnt_exist():
|
||||
tribes = build_tribes(2)
|
||||
tribe_repo = FakeTribeRepository(tribes)
|
||||
students = build_student(tribes, 1)
|
||||
student_repo = FakeStudentRepository(students)
|
||||
conn = FakeConn()
|
||||
|
||||
id = students[0].id
|
||||
new_name = "new name"
|
||||
new_tribe_name = "not existing tribe"
|
||||
|
||||
with pytest.raises(TribeDoesNotExist):
|
||||
services.update_student(
|
||||
id=id,
|
||||
name=new_name,
|
||||
tribe=new_tribe_name,
|
||||
student_repo=student_repo,
|
||||
tribe_repo=tribe_repo,
|
||||
conn=conn,
|
||||
)
|
||||
|
||||
assert conn.committed is False
|
||||
|
||||
mod_student = student_repo.get(id)
|
||||
assert mod_student.name == students[0].name
|
||||
assert mod_student.tribe.name == students[0].tribe.name
|
||||
|
||||
listed_student = student_repo.list()
|
||||
assert len(listed_student) == 2
|
||||
|
||||
|
||||
def test_update_student_doesnt_exist():
|
||||
tribes = build_tribes(2)
|
||||
tribe_repo = FakeTribeRepository(tribes)
|
||||
students = build_student(tribes, 1)
|
||||
student_repo = FakeStudentRepository(students)
|
||||
conn = FakeConn()
|
||||
|
||||
id = "not existing id"
|
||||
new_name = students[0].name
|
||||
new_tribe_name = students[0].tribe.name
|
||||
|
||||
with pytest.raises(StudentDoesExist):
|
||||
services.update_student(
|
||||
id=id,
|
||||
name=new_name,
|
||||
tribe=new_tribe_name,
|
||||
student_repo=student_repo,
|
||||
tribe_repo=tribe_repo,
|
||||
conn=conn,
|
||||
)
|
||||
|
||||
assert conn.committed is False
|
||||
|
||||
original_student = student_repo.get(students[0].id)
|
||||
assert original_student.name == students[0].name
|
||||
assert original_student.tribe.name == students[0].tribe.name
|
||||
|
||||
listed_student = student_repo.list()
|
||||
assert len(listed_student) == 2
|
||||
|
||||
|
||||
def test_delete_student():
|
||||
tribes = build_tribes(2)
|
||||
tribe_repo = FakeTribeRepository(tribes)
|
||||
students = build_student(tribes, 1)
|
||||
student_repo = FakeStudentRepository(students)
|
||||
conn = FakeConn()
|
||||
|
||||
student = students.pop()
|
||||
|
||||
services.delete_student(
|
||||
id=student.id,
|
||||
student_repo=student_repo,
|
||||
conn=conn,
|
||||
)
|
||||
|
||||
assert conn.committed is True
|
||||
|
||||
listed_student = student_repo.list()
|
||||
assert listed_student == students
|
||||
|
||||
|
||||
def test_delete_student_doesnt_exist():
|
||||
tribes = build_tribes(2)
|
||||
tribe_repo = FakeTribeRepository(tribes)
|
||||
students = build_student(tribes, 1)
|
||||
student_repo = FakeStudentRepository(students)
|
||||
conn = FakeConn()
|
||||
|
||||
with pytest.raises(StudentDoesExist):
|
||||
services.delete_student(
|
||||
id="not existing id",
|
||||
student_repo=student_repo,
|
||||
conn=conn,
|
||||
)
|
||||
|
||||
assert conn.committed is False
|
||||
|
||||
listed_student = student_repo.list()
|
||||
assert set(listed_student) == set(students)
|
Loading…
Reference in New Issue