Initial
This commit is contained in:
commit
50920532bf
BIN
assets/Presentations/DiKum.pdf
Normal file
BIN
assets/Presentations/DiKum.pdf
Normal file
Binary file not shown.
BIN
assets/Presentations/MeWi_1.pdf
Normal file
BIN
assets/Presentations/MeWi_1.pdf
Normal file
Binary file not shown.
BIN
assets/Presentations/MeWi_2.pdf
Normal file
BIN
assets/Presentations/MeWi_2.pdf
Normal file
Binary file not shown.
BIN
assets/Presentations/MeWi_3.pdf
Normal file
BIN
assets/Presentations/MeWi_3.pdf
Normal file
Binary file not shown.
BIN
assets/Presentations/MeWi_4.pdf
Normal file
BIN
assets/Presentations/MeWi_4.pdf
Normal file
Binary file not shown.
BIN
assets/Presentations/MeWi_5.pdf
Normal file
BIN
assets/Presentations/MeWi_5.pdf
Normal file
Binary file not shown.
BIN
assets/Presentations/MeWi_6.pdf
Normal file
BIN
assets/Presentations/MeWi_6.pdf
Normal file
Binary file not shown.
36
assets/Student_list.csv
Normal file
36
assets/Student_list.csv
Normal file
@ -0,0 +1,36 @@
|
||||
First Name,Last Name,Sex,Group,Grader,Tutorial 1,Tutorial 2,Extended Applications,Numpy & MatPlotLib,SciPy,Monte Carlo,Pandas & Seaborn,Folium,Statistical Test Methods,Data Analysis
|
||||
Abdalaziz,Abunjaila,Male,DiKum,30%,30.5,15,18,28,17,17,17,22,0,18
|
||||
Marleen,Adolphi,Female,MeWi6,30%,29.5,15,18,32,19,20,17,24,23,0
|
||||
Sarina,Apel,Female,MeWi1,30%,28.5,15,18,32,20,20,21,24,20,23
|
||||
Skofiare,Berisha,Female,DiKum,30%,29.5,13,18,34,20,17,20,26,16,0
|
||||
Aurela,Brahimi,Female,MeWi2,30%,17.5,15,15.5,26,16,17,19,16,0,0
|
||||
Cam Thu,Do,Female,MeWi3,30%,31,15,18,34,19,20,21.5,22,12,0
|
||||
Nova,Eib,Female,MeWi4,30%,31,15,15,34,20,20,21,27,19,21
|
||||
Lena,Fricke,Female,MeWi4,30%,0,0,0,0,0,0,0,0,0,0
|
||||
Nele,Grundke,Female,MeWi6,30%,23.5,13,16,28,20,17,21,18,22,11
|
||||
Anna,Grünewald,Female,MeWi3,30%,12,14,16,29,16,15,19,9,0,0
|
||||
Yannik,Haupt,Male,NoGroup,30%,18,6,14,21,13,2,9,0,0,0
|
||||
Janna,Heiny,Female,MeWi1,30%,30,15,18,33,18,20,22,25,24,30
|
||||
Milena,Krieger,Female,MeWi1,30%,30,15,18,33,20,20,21.5,26,20,22
|
||||
Julia,Limbach,Female,MeWi6,30%,27.5,12,18,29,11,19,17.5,26,24,28
|
||||
Viktoria,Litza,Female,MeWi5,30%,21.5,15,18,27,13,20,22,21,21,30
|
||||
Leonie,Manthey,Female,MeWi1,30%,28.5,14,18,29,20,10,18,23,16,28
|
||||
Izabel,Mike,Female,MeWi2,30%,29.5,15,15,35,11,15,19,21,21,27
|
||||
Lea,Noglik,Female,MeWi5,30%,22.5,15,17,34,13,10,20,21,19,6
|
||||
Donika,Nuhiu,Female,MeWi5,30%,31,13.5,18,35,14,10,17,18,19,8
|
||||
Julia,Renner,Female,MeWi4,30%,27.5,10,14,32,20,17,11,20,24,14
|
||||
Fabian,Rothberger,Male,MeWi3,30%,30.5,15,18,34,17,17,19,22,18,30
|
||||
Natascha,Rott,Female,MeWi1,30%,29.5,12,18,32,19,20,21,26,23,26
|
||||
Isabel,Rudolf,Female,MeWi4,30%,27.5,9,17,34,16,19,19,21,16,14
|
||||
Melina,Sablotny,Female,MeWi6,30%,31,15,18,33,20,20,21,19,11,28
|
||||
Alea,Schleier,Female,DiKum,30%,27,14,18,34,16,18,21.5,22,15,22
|
||||
Flemming,Schur,Male,MeWi3,30%,29.5,15,17,34,19,20,19,22,18,27
|
||||
Marie,Seeger,Female,DiKum,30%,27.5,15,18,32,14,9,17,22,9,25
|
||||
Lucy,Thiele,Female,MeWi6,30%,27.5,15,18,27,20,17,19,18,22,25
|
||||
Lara,Troschke,Female,MeWi2,30%,28.5,14,17,28,13,19,21,25,12,24
|
||||
Inga-Brit,Turschner,Female,MeWi2,30%,25.5,14,18,34,20,16,19,22,17,30
|
||||
Alea,Unger,Female,MeWi5,30%,30,12,18,31,20,20,21,22,15,21.5
|
||||
Marie,Wallbaum,Female,MeWi5,30%,28.5,14,18,34,17,20,19,24,12,22
|
||||
Katharina,Walz,Female,MeWi4,30%,31,15,18,31,19,19,17,24,17,14.5
|
||||
Xiaowei,Wang,Male,NoGroup,30%,30.5,14,18,26,19,17,0,0,0,0
|
||||
Lilly-Lu,Warnken,Female,DiKum,30%,30,15,18,30,14,17,19,14,16,24
|
|
BIN
assets/WiSe_24_25.db
Normal file
BIN
assets/WiSe_24_25.db
Normal file
Binary file not shown.
67
assets/convert.py
Normal file
67
assets/convert.py
Normal file
@ -0,0 +1,67 @@
|
||||
import pandas as pd
|
||||
import pprint
|
||||
import sys
|
||||
sys.path.append('../learnlytics/')
|
||||
from dbmodel import *
|
||||
|
||||
df = pd.read_csv("Student_list.csv")
|
||||
df = df.dropna()
|
||||
courses = {
|
||||
'Tutorial 1': 31,
|
||||
'Tutorial 2': 15,
|
||||
'Extended Applications': 18,
|
||||
'Numpy & MatPlotLib': 35,
|
||||
'SciPy': 20,
|
||||
'Monte Carlo': 20,
|
||||
'Pandas & Seaborn': 22,
|
||||
'Folium': 27,
|
||||
'Statistical Test Methods': 24,
|
||||
'Data Analysis': 30
|
||||
}
|
||||
|
||||
groups = {
|
||||
"NoGroup": "No Project",
|
||||
"MeWi1": "Covid-19",
|
||||
"MeWi2": "Covid-19",
|
||||
"MeWi3": "Discovery of Handwashing",
|
||||
"MeWi4": "Uber Trips",
|
||||
"MeWi5": "Extramarital Affairs",
|
||||
"MeWi6": "Hochschulstatistik",
|
||||
"DiKum": "Facebook Data"
|
||||
}
|
||||
|
||||
print(df)
|
||||
init_db('WiSe_24_25.db')
|
||||
|
||||
|
||||
# Create Class
|
||||
clas = Class.create(name='WiSe 24/25')
|
||||
#print(clas.id)
|
||||
|
||||
# Create Courses
|
||||
for k, v in courses.items():
|
||||
Lecture.create(title=k, points=v, class_id=clas.id)
|
||||
#print(l.title, l.points, l.class_id, l.id)
|
||||
|
||||
for k, v in groups.items():
|
||||
Group.create(name=k, project=v, has_passed=True, class_id=clas.id)
|
||||
|
||||
for index, row in df.iterrows():
|
||||
s = Student.create(
|
||||
prename=row["First Name"],
|
||||
surname=row["Last Name"],
|
||||
sex=row["Sex"],
|
||||
class_id=clas.id,
|
||||
group_id=Group.select().where(Group.name == row["Group"]),
|
||||
grader=row["Grader"],
|
||||
)
|
||||
|
||||
for title, points in list(row.to_dict().items())[5:]:
|
||||
Submission.create(
|
||||
student_id=s.id,
|
||||
lecture_id=Lecture.select().where(Lecture.title == title),
|
||||
class_id=clas.id,
|
||||
points=points
|
||||
)
|
||||
|
||||
|
235
assets/covid_faelle_MeWi_2.html
Normal file
235
assets/covid_faelle_MeWi_2.html
Normal file
File diff suppressed because one or more lines are too long
10
learnlytics/dbmodel/__init__.py
Normal file
10
learnlytics/dbmodel/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
from .utils import (
|
||||
tables,
|
||||
table_labels,
|
||||
init_db,
|
||||
save_as_json,
|
||||
create_from_json
|
||||
)
|
||||
|
||||
from .model import *
|
||||
|
BIN
learnlytics/dbmodel/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
learnlytics/dbmodel/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
learnlytics/dbmodel/__pycache__/model.cpython-312.pyc
Normal file
BIN
learnlytics/dbmodel/__pycache__/model.cpython-312.pyc
Normal file
Binary file not shown.
BIN
learnlytics/dbmodel/__pycache__/utils.cpython-312.pyc
Normal file
BIN
learnlytics/dbmodel/__pycache__/utils.cpython-312.pyc
Normal file
Binary file not shown.
47
learnlytics/dbmodel/model.dbml
Normal file
47
learnlytics/dbmodel/model.dbml
Normal file
@ -0,0 +1,47 @@
|
||||
// Use DBML to define your database structure
|
||||
// Docs: https://dbml.dbdiagram.io/docs
|
||||
|
||||
Table Class {
|
||||
id integer [primary key]
|
||||
name varchar
|
||||
created_at timestamp
|
||||
}
|
||||
|
||||
Table Student {
|
||||
id integer [primary key]
|
||||
name varchar
|
||||
sex varchar
|
||||
class_id integer [ref: < Class.id]
|
||||
group_id integer [ref: < Group.id]
|
||||
grader varchar
|
||||
created_at timestamp
|
||||
}
|
||||
|
||||
Table Lecture {
|
||||
id integer [primary key]
|
||||
title varchar
|
||||
points integer
|
||||
class_id integer [ref: < Class.id]
|
||||
created_at timestamp
|
||||
}
|
||||
|
||||
Table Submission {
|
||||
id integer [primary key]
|
||||
student_id integer [ref: < Student.id]
|
||||
lecture_id integer [ref: < Lecture.id]
|
||||
class_id integer [ref: < Class.id]
|
||||
points float
|
||||
created_at timestamp
|
||||
}
|
||||
|
||||
Table Group {
|
||||
id integer [primary key]
|
||||
name varchar
|
||||
project varchar
|
||||
has_passed boolean
|
||||
presentation varchar
|
||||
class_id integer [ref: < Class.id]
|
||||
created_at timestamp
|
||||
}
|
||||
|
||||
|
88
learnlytics/dbmodel/model.py
Normal file
88
learnlytics/dbmodel/model.py
Normal file
@ -0,0 +1,88 @@
|
||||
'''
|
||||
peewee ORM Database Model definition
|
||||
Documentation: https://docs.peewee-orm.com
|
||||
|
||||
please look up model.dbml for Documentation
|
||||
Online Viewer: https://dbdiagram.io
|
||||
|
||||
'''
|
||||
|
||||
|
||||
from peewee import *
|
||||
from datetime import datetime
|
||||
|
||||
db = DatabaseProxy()
|
||||
|
||||
# WIP: Add Switch Function
|
||||
if True:
|
||||
database = SqliteDatabase(None, autoconnect=False)
|
||||
else:
|
||||
database = PostgresqlDatabase(None, autoconnect=False)
|
||||
|
||||
db.initialize(database)
|
||||
|
||||
class BaseModel(Model):
|
||||
'''
|
||||
Base Model (needed for peewee) defines the Class Meta
|
||||
and bounds Global db obj to every Table
|
||||
'''
|
||||
class Meta:
|
||||
database = db
|
||||
|
||||
|
||||
class Class(BaseModel):
|
||||
'''
|
||||
Baseline Order Base
|
||||
|
||||
Table for Storing a Class
|
||||
'''
|
||||
name = CharField()
|
||||
created_at = DateTimeField(default=datetime.now)
|
||||
|
||||
|
||||
class Group(BaseModel):
|
||||
'''
|
||||
Table for Storing a project Group
|
||||
'''
|
||||
name = CharField()
|
||||
project = CharField()
|
||||
has_passed = BooleanField()
|
||||
presentation = CharField(null = True)
|
||||
class_id = ForeignKeyField(Class, backref='class')
|
||||
created_at = DateTimeField(default=datetime.now)
|
||||
|
||||
|
||||
class Student(BaseModel):
|
||||
'''
|
||||
Table for Storing a Student and linking him to appropiate Tables
|
||||
'''
|
||||
prename = CharField()
|
||||
surname = CharField()
|
||||
sex = CharField()
|
||||
class_id = ForeignKeyField(Class, backref='class')
|
||||
group_id = ForeignKeyField(Group, backref='group')
|
||||
grader = CharField()
|
||||
created_at = DateTimeField(default=datetime.now)
|
||||
|
||||
|
||||
class Lecture(BaseModel):
|
||||
'''
|
||||
Table for defining a Lecture
|
||||
'''
|
||||
title = CharField()
|
||||
points = IntegerField()
|
||||
class_id = ForeignKeyField(Class, backref='class')
|
||||
created_at = DateTimeField(default=datetime.now)
|
||||
|
||||
|
||||
class Submission(BaseModel):
|
||||
'''
|
||||
Table for defining a Submission from a Student for Lecture
|
||||
'''
|
||||
student_id = ForeignKeyField(Student, backref='student')
|
||||
lecture_id = ForeignKeyField(Lecture, backref='lecture')
|
||||
class_id = ForeignKeyField(Class, backref='class')
|
||||
points = FloatField()
|
||||
created_at = DateTimeField(default=datetime.now)
|
||||
|
||||
|
173
learnlytics/dbmodel/utils.py
Normal file
173
learnlytics/dbmodel/utils.py
Normal file
@ -0,0 +1,173 @@
|
||||
'''
|
||||
Module provids Utilities to Interface with Database Model
|
||||
|
||||
Includes:
|
||||
- DateTime De-/Encoder for JSON loads/dumps
|
||||
- Database Initialize Helper
|
||||
- Module Class Summarizer
|
||||
'''
|
||||
|
||||
|
||||
import sys, inspect, json
|
||||
from datetime import datetime, date
|
||||
from pathlib import Path
|
||||
from playhouse.shortcuts import model_to_dict, dict_to_model
|
||||
from .model import *
|
||||
|
||||
class DateTimeEncoder(json.JSONEncoder):
|
||||
'''
|
||||
Helper Class converting datetime.datetime -> isoformated String
|
||||
'''
|
||||
|
||||
|
||||
def default(self, obj):
|
||||
if isinstance(obj, (datetime, date)):
|
||||
return obj.isoformat()
|
||||
|
||||
# Predefined used timestamp keys
|
||||
KEYNAMES = [
|
||||
"date", "timestamp", "last_updatet", "last_created", "last_editet", "created_at"
|
||||
]
|
||||
|
||||
def DateTimeDecoder(obj: dict) -> dict:
|
||||
'''
|
||||
Helper Function converting isoformated String -> datetime.datetime
|
||||
'''
|
||||
|
||||
|
||||
for key, value in obj.items():
|
||||
if key not in KEYNAMES:
|
||||
continue
|
||||
try:
|
||||
obj[key] = datetime.fromisoformat(value)
|
||||
except ValueError:
|
||||
pass
|
||||
return obj
|
||||
|
||||
|
||||
def get_module_cls(module: str) -> tuple[tuple[str, object]]:
|
||||
'''
|
||||
Given a module name function returns a list of Classes defined only in that Module
|
||||
'''
|
||||
|
||||
|
||||
assert type(module) is str, "Provided Module isn't a String"
|
||||
members = inspect.getmembers(sys.modules[module], inspect.isclass)
|
||||
return tuple(member for member in members if member[1].__module__ == module)
|
||||
|
||||
# precalculated from model.py
|
||||
tables = tuple(table[1] for table in get_module_cls('dbmodel.model') if not table[1] is BaseModel)
|
||||
table_labels = tuple(table[0] for table in get_module_cls('dbmodel.model') if not table[1] is BaseModel)
|
||||
|
||||
def init_db(name: Path | str) -> None:
|
||||
'''
|
||||
(Creates,) Connects and Initializes the db descriptor from a given *.db file
|
||||
'''
|
||||
|
||||
|
||||
assert isinstance(name, (Path, str)), "Provided Name isn't of type Path | str"
|
||||
|
||||
# convert to String
|
||||
if type(name) is Path:
|
||||
name = str(name)
|
||||
|
||||
# Switch Database; Initialize if needed
|
||||
if not db.is_closed():
|
||||
db.close()
|
||||
db.init(name)
|
||||
db.connect()
|
||||
db.create_tables(tables) # Ensure tables exist
|
||||
|
||||
|
||||
def save_as_json(filename: str, path: Path | str = Path('.')) -> Path:
|
||||
'''
|
||||
Saves the current loaded Database as <filename>.json to given <path>
|
||||
|
||||
JSON Format:
|
||||
{
|
||||
<Tablename>: [
|
||||
{<Tableparam>: <Value>, ...}
|
||||
],
|
||||
...
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
assert type(filename) is str, "Provided Filename isn't a String"
|
||||
assert isinstance(path, Path | str), "Provided Path isn't of type Path | str"
|
||||
|
||||
# Convert given path to Path
|
||||
if type(path) is str:
|
||||
path = Path(path)
|
||||
|
||||
filename = Path(filename)
|
||||
|
||||
# Set Correct Suffix
|
||||
if filename.suffix != '.json':
|
||||
filename = filename.with_suffix('.json')
|
||||
|
||||
file = path.resolve().absolute() / filename
|
||||
|
||||
# dump db
|
||||
database = {
|
||||
table.__name__: list(table.select().dicts())
|
||||
for table in tables
|
||||
}
|
||||
|
||||
# db -> json
|
||||
with open(file, "w") as fp:
|
||||
json.dump(database, fp, cls=DateTimeEncoder)
|
||||
|
||||
return file
|
||||
|
||||
def create_from_json(dbname: str, file: Path | str) -> Path:
|
||||
'''
|
||||
Creates a new Database <dbname>.db in from given <file>
|
||||
|
||||
Valid JSON Format:
|
||||
{
|
||||
<Tablename>: [
|
||||
{<Tableparam>: <Value>, ...}
|
||||
],
|
||||
...
|
||||
}
|
||||
'''
|
||||
|
||||
assert type(dbname) is str, "Provided Database name isn't a String"
|
||||
assert isinstance(file, (Path | str)), "Provided file descriptor isn't of type Path | str"
|
||||
|
||||
# Convert file to Path
|
||||
if type(file) is str:
|
||||
file = Path(file)
|
||||
assert file.suffix == '.json', "File isn't a JSON"
|
||||
|
||||
# Set correct Suffix and connect
|
||||
dbname = Path(dbname).resolve().absolute()
|
||||
if dbname.suffix != '.db':
|
||||
dbname = dbname.with_suffix('.db')
|
||||
init_db(dbname)
|
||||
|
||||
# load from json
|
||||
with open(file, "r") as fp:
|
||||
data = json.load(fp, object_hook=DateTimeDecoder)
|
||||
|
||||
assert all([keys == table.__name__ for keys, table in zip(data.keys(), tables)]), f"{file.name} can't be convert to Database"
|
||||
|
||||
# Insert Data
|
||||
for k, v in data.items():
|
||||
if not v:
|
||||
continue
|
||||
|
||||
table = next((table for table in tables if table.__name__ == k))
|
||||
table.insert_many(v).execute()
|
||||
|
||||
return dbname
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_db('/home/phil/programming/grapher/assets/WiSe_24_25.db')
|
||||
f = save_as_json("file")
|
||||
print(f)
|
||||
print(create_from_json("test", f))
|
||||
|
||||
|
||||
|
9
learnlytics/grader/__init__.py
Normal file
9
learnlytics/grader/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
from .valuation import (
|
||||
get_gradings,
|
||||
get_grader,
|
||||
Std30PercentRule,
|
||||
Std50PercentRule,
|
||||
StdGermanGradingMiddleSchool,
|
||||
StdGermanGradingHighSchool
|
||||
)
|
||||
|
BIN
learnlytics/grader/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
learnlytics/grader/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
learnlytics/grader/__pycache__/valuation.cpython-312.pyc
Normal file
BIN
learnlytics/grader/__pycache__/valuation.cpython-312.pyc
Normal file
Binary file not shown.
198
learnlytics/grader/valuation.py
Normal file
198
learnlytics/grader/valuation.py
Normal file
@ -0,0 +1,198 @@
|
||||
from collections.abc import Sequence, Iterable, Mapping
|
||||
from typing import Any
|
||||
import inspect
|
||||
from abc import ABC, abstractmethod
|
||||
import weakref
|
||||
|
||||
from PIL import ImageColor
|
||||
from colour import Color
|
||||
PASSED: str = "#1AFE49"
|
||||
FAILED: str = "#FF124F"
|
||||
|
||||
def hex_to_rgba(color: str) -> tuple:
|
||||
return tuple([e/255 for e in ImageColor.getcolor(color, "RGBA")])
|
||||
|
||||
def gradient(color1: str, color2: str, num: int) -> list[tuple]:
|
||||
c1, c2 = Color(color1), Color(color2)
|
||||
colors = list(c2.range_to(c1, num))
|
||||
colors = [hex_to_rgba(str(c)) for c in colors]
|
||||
return colors
|
||||
|
||||
class BaseGrading(Mapping, ABC):
|
||||
__instances: list[Mapping] = list()
|
||||
|
||||
def __init__(self, schema: dict[str, int | float], name=None, alt_name=None):
|
||||
all_str = all(isinstance(k, str) for k in schema.keys())
|
||||
assert all_str or all(isinstance(k, int) for k in schema.keys()), "Keys must be all of type (str, int)"
|
||||
assert all(isinstance(v, float) for v in schema.values()), "All values must be floats in range(0,1)"
|
||||
assert all(v <= 1 and v >= 0 for v in schema.values()), "All values must be floats in range(0,1)"
|
||||
if all_str:
|
||||
self.schema = dict()
|
||||
for k, v in schema.items():
|
||||
self.schema[k.title()] = v
|
||||
else:
|
||||
self.schema = schema
|
||||
|
||||
self.__class__.__instances.append(weakref.proxy(self))
|
||||
self.name = name
|
||||
self.alt_name = alt_name
|
||||
|
||||
def __getitem__(self, index):
|
||||
if index >= len(self):
|
||||
raise IndexError
|
||||
return self.schema[index]
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.schema)
|
||||
|
||||
def __contains__(self, item: int | str | float) -> bool:
|
||||
if isinstance(item, (int, str)):
|
||||
if isinstance(item, str):
|
||||
item = item.title()
|
||||
return item in self.schema
|
||||
if isinstance(item, float):
|
||||
return item <= 1 and item >= 0
|
||||
return False
|
||||
|
||||
def __iter__(self) -> Iterable:
|
||||
yield from self.schema
|
||||
|
||||
def __reversed__(self) -> Iterable:
|
||||
yield from reversed(self.schema)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self.name}: ({str(self.schema)})>"
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if other == self:
|
||||
return True
|
||||
if isinstance(other, BaseEval):
|
||||
return self.schema == other.schema
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(self, other) -> bool:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def get(self, key: int | str) -> float | None:
|
||||
if isinstance(key, str):
|
||||
key = key.title()
|
||||
if key in self:
|
||||
return self.schema[key]
|
||||
return None
|
||||
|
||||
def keys(self) -> tuple:
|
||||
return list(self.schema.keys())
|
||||
|
||||
def values(self) -> tuple:
|
||||
return list(self.schema.values())
|
||||
|
||||
def items(self) -> list[tuple]:
|
||||
return list(self.schema.items())
|
||||
|
||||
@abstractmethod
|
||||
def has_passed(self, value: int | float, max: int | float) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_grade(self, value: int | float, max: int | float) -> str | int:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_grade_color(self, value: int | float, max: int | float) -> tuple:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_instances(cls):
|
||||
yield from cls.__instances
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, name: str):
|
||||
for instance in cls.__instances:
|
||||
if instance.alt_name == name:
|
||||
return instance
|
||||
|
||||
get_gradings = lambda: BaseGrading.get_instances()
|
||||
get_grader = lambda name: BaseGrading.get_instance(name)
|
||||
|
||||
class StdPercentRule(BaseGrading):
|
||||
def has_passed(self, value: int | float, max: int | float) -> bool:
|
||||
return value >= max * self.schema["Passed"]
|
||||
|
||||
def get_grade(self, value: int | float, max: int | float) -> str:
|
||||
return "Passed" if self.has_passed(value, max) else "Not Passed"
|
||||
|
||||
def get_grade_color(self, value: int | float, max: int | float) -> tuple:
|
||||
if self.has_passed(value, max):
|
||||
return hex_to_rgba(PASSED)
|
||||
return hex_to_rgba(FAILED)
|
||||
|
||||
class StdGermanGrading(BaseGrading):
|
||||
def has_passed(self, value: int | float, max: int | float) -> bool:
|
||||
return value/max >= 0.45
|
||||
|
||||
def search_grade(self, value: float) -> int:
|
||||
if value <= 0:
|
||||
return min(self.schema.keys())
|
||||
|
||||
searched = max(self.schema.keys())
|
||||
found = False
|
||||
while not found:
|
||||
if self.schema[searched] <= value:
|
||||
found = True
|
||||
else:
|
||||
searched -= 1
|
||||
return searched
|
||||
|
||||
def get_grade(self, value: int | float, max: int | float) -> int:
|
||||
return self.search_grade(value/max)
|
||||
|
||||
def get_grade_color(self, value: float, max: int | float) -> tuple:
|
||||
grade = self.get_grade(value, max)
|
||||
colors = gradient(PASSED, FAILED, len(self.schema))
|
||||
return colors[grade]
|
||||
|
||||
# Definitions
|
||||
Std30PercentRule = StdPercentRule({
|
||||
"pAssed": 0.3,
|
||||
"Failed": 0.0
|
||||
}, "Std30PercentRule", "30%")
|
||||
|
||||
Std50PercentRule = StdPercentRule({
|
||||
"Passed": 0.5,
|
||||
"Failed": 0.0
|
||||
}, "Std50PercentRule", "50%")
|
||||
|
||||
StdGermanGradingMiddleSchool = StdGermanGrading({
|
||||
1: 0.96,
|
||||
2: 0.80,
|
||||
3: 0.60,
|
||||
4: 0.45,
|
||||
5: 0.16,
|
||||
6: 0.00
|
||||
}, "StdGermanGradingMiddleSchool", "Mittelstufe")
|
||||
|
||||
StdGermanGradingHighSchool = StdGermanGrading({
|
||||
15: 0.95,
|
||||
14: 0.90,
|
||||
13: 0.85,
|
||||
12: 0.80,
|
||||
11: 0.75,
|
||||
10: 0.70,
|
||||
9: 0.65,
|
||||
8: 0.60,
|
||||
7: 0.55,
|
||||
6: 0.50,
|
||||
5: 0.45,
|
||||
4: 0.40,
|
||||
3: 0.33,
|
||||
2: 0.27,
|
||||
1: 0.20,
|
||||
0: 0.00
|
||||
}, "StdGermanGradingHighSchool", "Oberstufe")
|
||||
|
||||
|
||||
#print(StdGermanGradingHighSchool.get_grade(0.0, 24))
|
||||
|
3
learnlytics/gui/__init__.py
Normal file
3
learnlytics/gui/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .analyzer import analyzer_layout
|
||||
from .database import database_editor_layout
|
||||
from .gui import menu_bar, status_bar
|
BIN
learnlytics/gui/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
learnlytics/gui/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
learnlytics/gui/__pycache__/gui.cpython-312.pyc
Normal file
BIN
learnlytics/gui/__pycache__/gui.cpython-312.pyc
Normal file
Binary file not shown.
65
learnlytics/gui/analyzer/__init__.py
Normal file
65
learnlytics/gui/analyzer/__init__.py
Normal file
@ -0,0 +1,65 @@
|
||||
from imgui_bundle import hello_imgui, imgui
|
||||
from typing import List
|
||||
|
||||
from .student_list import student_list
|
||||
from .student_graph import student_graph
|
||||
from .group_graph import group_graph
|
||||
|
||||
def analyzer_docking_splits() -> List[hello_imgui.DockingSplit]:
|
||||
split_main_misc = hello_imgui.DockingSplit()
|
||||
split_main_misc.initial_dock = "MainDockSpace"
|
||||
split_main_misc.new_dock = "MiscSpace"
|
||||
split_main_misc.direction = imgui.Dir.down
|
||||
split_main_misc.ratio = 0.25
|
||||
|
||||
# Then, add a space to the left which occupies a column whose width is 25% of the app width
|
||||
split_main_command = hello_imgui.DockingSplit()
|
||||
split_main_command.initial_dock = "MainDockSpace"
|
||||
split_main_command.new_dock = "CommandSpace"
|
||||
split_main_command.direction = imgui.Dir.left
|
||||
split_main_command.ratio = 0.2
|
||||
|
||||
# Then, add CommandSpace2 below MainDockSpace
|
||||
split_main_command2 = hello_imgui.DockingSplit()
|
||||
split_main_command2.initial_dock = "MainDockSpace"
|
||||
split_main_command2.new_dock = "CommandSpace2"
|
||||
split_main_command2.direction = imgui.Dir.down
|
||||
split_main_command2.ratio = 0.25
|
||||
|
||||
splits = [split_main_misc, split_main_command, split_main_command2]
|
||||
return splits
|
||||
|
||||
def set_analyzer_layout() -> List[hello_imgui.DockableWindow]:
|
||||
student_selector = hello_imgui.DockableWindow()
|
||||
student_selector.label = "Class list"
|
||||
student_selector.dock_space_name = "CommandSpace"
|
||||
student_selector.gui_function = lambda: student_list()
|
||||
|
||||
student_info = hello_imgui.DockableWindow()
|
||||
student_info.label = "Student Analyzer"
|
||||
student_info.dock_space_name = "MainDockSpace"
|
||||
student_info.gui_function = lambda: student_graph()
|
||||
|
||||
group_info = hello_imgui.DockableWindow()
|
||||
group_info.label = "Group Analyzer"
|
||||
group_info.dock_space_name = "MainDockSpace"
|
||||
group_info.gui_function = lambda: group_graph()
|
||||
|
||||
#student_ranking = hello_imgui.DockableWindow()
|
||||
#student_ranking.label = "Ranking"
|
||||
#student_ranking.dock_space_name = "MainDockSpace"
|
||||
#student_ranking.gui_function = lambda: ranking()
|
||||
|
||||
return [
|
||||
student_selector,
|
||||
student_info,
|
||||
group_info
|
||||
]
|
||||
|
||||
def analyzer_layout() -> hello_imgui.DockingParams:
|
||||
docking_params = hello_imgui.DockingParams()
|
||||
docking_params.layout_name = "Analyzer"
|
||||
docking_params.docking_splits = analyzer_docking_splits()
|
||||
docking_params.dockable_windows = set_analyzer_layout()
|
||||
return docking_params
|
||||
|
BIN
learnlytics/gui/analyzer/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
learnlytics/gui/analyzer/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
learnlytics/gui/analyzer/__pycache__/analyzer.cpython-312.pyc
Normal file
BIN
learnlytics/gui/analyzer/__pycache__/analyzer.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
learnlytics/gui/analyzer/__pycache__/group_graph.cpython-312.pyc
Normal file
BIN
learnlytics/gui/analyzer/__pycache__/group_graph.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
learnlytics/gui/analyzer/__pycache__/plotter.cpython-312.pyc
Normal file
BIN
learnlytics/gui/analyzer/__pycache__/plotter.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
319
learnlytics/gui/analyzer/analyzer.py
Normal file
319
learnlytics/gui/analyzer/analyzer.py
Normal file
@ -0,0 +1,319 @@
|
||||
# Custom
|
||||
from dbmodel import *
|
||||
from grader.valuation import *
|
||||
|
||||
# External
|
||||
from imgui_bundle import (
|
||||
imgui,
|
||||
immapp,
|
||||
hello_imgui,
|
||||
imgui_md,
|
||||
implot,
|
||||
implot3d,
|
||||
immvision,
|
||||
ImVec2,
|
||||
ImVec4,
|
||||
im_file_dialog
|
||||
)
|
||||
from PIL import ImageColor
|
||||
import numpy as np
|
||||
from numpy.typing import NDArray
|
||||
|
||||
# Built In
|
||||
from typing import List, Any
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def select_class(app_state: AppState) -> None:
|
||||
statics = select_class
|
||||
|
||||
if not app_state.current_class_id:
|
||||
imgui.text("No class found in Database")
|
||||
return
|
||||
|
||||
if not statics.inited:
|
||||
statics.select = 0
|
||||
statics.classes = Class.select()
|
||||
statics.labels = [c.name for c in statics.classes]
|
||||
statics.inited = True
|
||||
|
||||
changed, statics.select = imgui.combo("Classes", statics.select, statics.labels)
|
||||
if changed:
|
||||
app_state.current_class_id = statics.classes[statics.select].id
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def select_student(app_state: AppState) -> None:
|
||||
statics = select_student
|
||||
|
||||
if not app_state.current_class_id:
|
||||
return
|
||||
|
||||
if not statics.inited:
|
||||
statics.select = 0
|
||||
statics.students = Student.select().where(Student.class_id == app_state.current_class_id)
|
||||
statics.inited = True
|
||||
|
||||
if not statics.students:
|
||||
imgui.text("No Studends found")
|
||||
return
|
||||
|
||||
for n, student in enumerate(statics.students, start=1):
|
||||
display = f"{n}. {student.prename} {student.surname}"
|
||||
_, clicked = imgui.selectable(display, statics.select == n-1)
|
||||
if clicked:
|
||||
statics.select = n-1
|
||||
app_state.current_student_id = statics.students[statics.select].id
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def student_list(app_state: AppState) -> None:
|
||||
statics = student_list
|
||||
|
||||
select_class(app_state)
|
||||
imgui.separator()
|
||||
select_student(app_state)
|
||||
|
||||
|
||||
def plot_bar_line_percentage(data: np.array, labels: list, avg: float) -> None:
|
||||
if not data.size > 0:
|
||||
imgui.text("No Data available")
|
||||
return
|
||||
|
||||
name = hash(avg)
|
||||
|
||||
if avg.is_integer():
|
||||
avg = int(avg)
|
||||
avg = np.ones(len(data)) * avg
|
||||
|
||||
w, h = imgui.get_window_size()
|
||||
implot.push_colormap(implot.Colormap_.hot.value)
|
||||
if implot.begin_plot(f"Performance##{name}", ImVec2(-1, h*0.4), implot.Flags_.no_mouse_text.value | implot.Flags_.no_inputs.value):
|
||||
implot.setup_axes("Lectures", "Percentage")
|
||||
implot.setup_axes_limits(-1, len(data), 0, 110)
|
||||
implot.push_style_var(implot.StyleVar_.fill_alpha.value, 0.6)
|
||||
implot.push_style_var(implot.StyleVar_.line_weight.value, 3)
|
||||
implot.setup_axis_ticks(implot.ImAxis_.x1.value, 0, len(labels), len(labels), [" " for _ in labels], False)
|
||||
implot.plot_bars("Submissions", data)
|
||||
implot.plot_line("Average", avg)
|
||||
|
||||
implot.push_style_color(implot.Col_.inlay_text, ImVec4(190,190,40,255)/255)
|
||||
for x_pos, label in enumerate(labels):
|
||||
y_pos = 50
|
||||
implot.plot_text(label, x_pos, y_pos//2, ImVec2(0,0), implot.TextFlags_.vertical.value)
|
||||
implot.pop_style_color()
|
||||
|
||||
implot.pop_style_var()
|
||||
implot.end_plot()
|
||||
|
||||
COLOR_TEXT_PASSED = tuple([e/255 for e in ImageColor.getcolor("#1AFE49","RGBA")])
|
||||
COLOR_TEXT_FAILED = tuple([e/255 for e in ImageColor.getcolor("#FF124F","RGBA")])
|
||||
COLOR_TEXT_PROJECT = tuple([e/255 for e in ImageColor.getcolor("#0A9CF5","RGBA")])
|
||||
@immapp.static(inited=False)
|
||||
def student_graph(app_state: AppState) -> None:
|
||||
statics = student_graph
|
||||
if not statics.inited:
|
||||
statics.id = -1
|
||||
statics.student = None
|
||||
statics.group = None
|
||||
statics.lectures = None
|
||||
statics.points = None
|
||||
statics.sub_points = None
|
||||
statics.subs_data = None
|
||||
statics.subs_labels = None
|
||||
statics.max_points = None
|
||||
statics.avg = None
|
||||
statics.inited = True
|
||||
|
||||
if id != app_state.current_student_id:
|
||||
statics.id = app_state.current_student_id
|
||||
|
||||
if not app_state.current_student_id:
|
||||
imgui.text("No Students in Database")
|
||||
return
|
||||
statics.student = Student.get_by_id(app_state.current_student_id)
|
||||
submissions = Submission.select().where(Submission.student_id == statics.student.id)
|
||||
|
||||
statics.group = Group.get_by_id(statics.student.group_id)
|
||||
statics.lectures = [Lecture.get_by_id(sub.lecture_id) for sub in submissions]
|
||||
statics.max_points = np.sum([l.points for l in statics.lectures])
|
||||
statics.sub_points = [sub.points for sub in submissions]
|
||||
statics.points = np.sum(statics.sub_points)
|
||||
if statics.points.is_integer():
|
||||
statics.points = int(statics.points)
|
||||
#statics.grader = get_grader("Oberstufe")
|
||||
statics.grader = get_grader(statics.student.grader)
|
||||
|
||||
statics.subs_data = np.array([p/mp.points for p, mp in zip(statics.sub_points, statics.lectures)], dtype=np.float32)*100
|
||||
statics.subs_labels = [f"{l.title} {int(points) if points.is_integer() else points}/{l.points}" for l, points in zip(statics.lectures, statics.sub_points)]
|
||||
statics.avg = statics.points/statics.max_points*100
|
||||
|
||||
w, h = imgui.get_window_size()
|
||||
imgui_md.render(f"# {statics.student.prename} {statics.student.surname} ({statics.group.name})")
|
||||
imgui_md.render(f"### {statics.points}/{statics.max_points} ({statics.student.grader})")
|
||||
imgui.text(" ")
|
||||
imgui.progress_bar(statics.points/statics.max_points, ImVec2(w*0.9, h*0.05), f"{statics.points}/{statics.max_points} {statics.points/statics.max_points:.1%}")
|
||||
plot_bar_line_percentage(statics.subs_data, statics.subs_labels, statics.avg)
|
||||
imgui.separator()
|
||||
imgui.text_colored(COLOR_TEXT_PROJECT, f"{statics.group.name}: {statics.group.project}")
|
||||
for n, data in enumerate(zip(statics.lectures, statics.sub_points), start=1):
|
||||
lecture, points = data
|
||||
COLOR = statics.grader.get_grade_color(points, lecture.points)
|
||||
imgui.text_colored(COLOR, f"{n}. {lecture.title} {points}/{lecture.points} ({statics.grader.get_grade(points, lecture.points)}) ")
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def sex_graph(app_state: AppState) -> None:
|
||||
statics = sex_graph
|
||||
|
||||
if db.is_closed():
|
||||
imgui.text("No Database loaded")
|
||||
return
|
||||
|
||||
if not statics.inited:
|
||||
statics.max_points = None
|
||||
statics.male_points = None
|
||||
statics.female_points = None
|
||||
statics.male_percentage = None
|
||||
statics.female_percentage = None
|
||||
statics.male_labels = None
|
||||
statics.female_labels = None
|
||||
statics.lectures = None
|
||||
statics.class_id = -1
|
||||
statics.state = 0
|
||||
statics.inited = True
|
||||
|
||||
if not app_state.current_class_id:
|
||||
imgui.text("No Class found")
|
||||
return
|
||||
|
||||
if statics.class_id != app_state.current_class_id:
|
||||
statics.class_id = app_state.current_class_id
|
||||
lectures = Lecture.select().where(Lecture.class_id == statics.class_id)
|
||||
statics.lectures = lectures
|
||||
|
||||
statics.max_points = np.empty(len(lectures))
|
||||
statics.male_points = np.empty(len(lectures))
|
||||
statics.female_points = np.empty(len(lectures))
|
||||
for n, lecture in enumerate(lectures):
|
||||
statics.max_points[n] = lecture.points # Acc points
|
||||
|
||||
m_count = 0
|
||||
f_count = 0
|
||||
m_points = 0
|
||||
f_points = 0
|
||||
|
||||
for sub in Submission.select().where(Submission.lecture_id == lecture.id):
|
||||
if Student.get_by_id(sub.student_id).sex == 'Male':
|
||||
m_points += sub.points
|
||||
m_count += 1
|
||||
else:
|
||||
f_points += sub.points
|
||||
f_count += 1
|
||||
statics.male_points[n] = m_points/m_count/lecture.points
|
||||
statics.female_points[n] = f_points/f_count/lecture.points
|
||||
|
||||
statics.male_percentage = np.sum(statics.male_points)/len(statics.male_points)
|
||||
statics.female_percentage = np.sum(statics.female_points)/len(statics.female_points)
|
||||
|
||||
statics.male_labels = [f"{l.title} | {points:.1%}" for l, points in zip(lectures, statics.male_points)]
|
||||
statics.female_labels = [f"{l.title} | {points:.1%}" for l, points in zip(lectures, statics.female_points)]
|
||||
|
||||
w, h = imgui.get_window_size()
|
||||
if statics.state == 0:
|
||||
imgui_md.render("# Male")
|
||||
imgui.progress_bar(statics.male_percentage, ImVec2(w*0.9, h*0.05), f"{statics.male_percentage:.1%} n={len(Student.select().where(Student.sex == 'Male'))}")
|
||||
plot_bar_line_percentage(statics.male_points*100, statics.male_labels, statics.male_percentage*100)
|
||||
|
||||
imgui_md.render("# Female")
|
||||
imgui.progress_bar(statics.female_percentage, ImVec2(w*0.9, h*0.05), f"{statics.female_percentage:.1%} n={len(Student.select().where(Student.sex == 'Female'))}")
|
||||
plot_bar_line_percentage(statics.female_points*100, statics.female_labels, statics.female_percentage*100)
|
||||
|
||||
if statics.state == 1:
|
||||
male_xs = np.arange(1, len(statics.male_points)+1, dtype=np.float32)
|
||||
male_ys = male_xs*0
|
||||
male_zs = np.array(statics.male_points*100, dtype=np.float32)
|
||||
|
||||
female_xs = np.arange(1, len(statics.female_points)+1, dtype=np.float32)
|
||||
female_ys = np.ones(len(statics.female_points), dtype=np.float32)
|
||||
female_zs = np.array(statics.female_points*100, dtype=np.float32)
|
||||
|
||||
if implot3d.begin_plot("3D Gender Plot", ImVec2(w*0.9, h*0.9)):
|
||||
implot3d.setup_axes("Lecture", "Gender", "Percentage")
|
||||
implot3d.setup_box_scale(1.1, 1.1, 1.1)
|
||||
implot3d.setup_axes_limits(0, len(statics.male_points)+1, -0.2, 1.2, 0, 110)
|
||||
|
||||
implot3d.set_next_marker_style(implot3d.Marker_.square.value)
|
||||
implot3d.plot_line("Male", male_xs, male_ys, male_zs)
|
||||
|
||||
implot3d.set_next_marker_style(implot3d.Marker_.circle.value)
|
||||
implot3d.plot_line("Female", female_xs, female_ys, female_zs)
|
||||
|
||||
for n, l in enumerate(statics.lectures, start=1):
|
||||
implot3d.plot_text(l.title, n, np.clip(n/len(statics.lectures)), 50, np.pi/4)
|
||||
|
||||
implot3d.end_plot()
|
||||
|
||||
if imgui.button("Change 2D/3D"):
|
||||
statics.state = not statics.state
|
||||
|
||||
|
||||
|
||||
COLOR_TEXT_FIRST = tuple([e/255 for e in ImageColor.getcolor("#FFCF40","RGBA")])
|
||||
COLOR_TEXT_SECOND = tuple([e/255 for e in ImageColor.getcolor("#A5A9B4","RGBA")])
|
||||
COLOR_TEXT_THIRD = tuple([e/255 for e in ImageColor.getcolor("#97564A","RGBA")])
|
||||
COLOR_TEXT_ELSE = tuple([e/255 for e in ImageColor.getcolor("#85EBD9","RGBA")])
|
||||
@immapp.static(inited=False)
|
||||
def ranking() -> None:
|
||||
statics = ranking
|
||||
|
||||
if not statics.inited:
|
||||
statics.id = -1
|
||||
statics.rank = None
|
||||
statics.state = 1
|
||||
statics.inited = True
|
||||
|
||||
imgui_md.render("# Student Ranking")
|
||||
|
||||
if not app_state.current_class_id:
|
||||
imgui.text("No class selected")
|
||||
return
|
||||
|
||||
if statics.id != app_state.current_class_id:
|
||||
statics.id = app_state.current_class_id
|
||||
|
||||
students = Student.select().where(Student.class_id == statics.id)
|
||||
max_points = np.sum([l.points for l in Lecture.select().where(Lecture.class_id == statics.id)])
|
||||
|
||||
statics.rank = list()
|
||||
for student in students:
|
||||
points = list()
|
||||
for sub in Submission.select().where(Submission.student_id == student.id):
|
||||
points.append(sub.points)
|
||||
statics.rank.append((student, sum(points)/max_points))
|
||||
statics.rank = sorted(statics.rank, key=lambda item: item[1], reverse=True)
|
||||
|
||||
if statics.state == 0:
|
||||
for n, data in enumerate(statics.rank, start=1):
|
||||
student, points = data
|
||||
display = f"{n}. {student.prename} {student.surname} {points:.1%}"
|
||||
COLOR = COLOR_TEXT_ELSE
|
||||
if n == 1:
|
||||
COLOR = COLOR_TEXT_FIRST
|
||||
if n == 2:
|
||||
COLOR = COLOR_TEXT_SECOND
|
||||
if n == 3:
|
||||
COLOR = COLOR_TEXT_THIRD
|
||||
imgui.text_colored(COLOR, display)
|
||||
|
||||
if statics.state == 1:
|
||||
w, h = imgui.get_window_size()
|
||||
if implot.begin_plot("Ranking", ImVec2(w*0.9, h*0.9)):
|
||||
implot.plot_bars("Ranking", np.array([p[1] for p in reversed(statics.rank)])*100, 0.67, 0, implot.BarsFlags_.horizontal.value)
|
||||
for n, s in enumerate(reversed(statics.rank)):
|
||||
student = s[0]
|
||||
implot.plot_text(f"{student.prename} {student.surname}", s[1]*50, n)
|
||||
implot.end_plot()
|
||||
|
||||
if imgui.button("Change"):
|
||||
statics.state = not statics.state
|
||||
|
||||
|
||||
|
19
learnlytics/gui/analyzer/analyzer_state.py
Normal file
19
learnlytics/gui/analyzer/analyzer_state.py
Normal file
@ -0,0 +1,19 @@
|
||||
class AnalyzerState:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if not cls._instance:
|
||||
cls._instance = super(AnalyzerState, cls).__new__(cls, *args, **kwargs)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
self.class_id = 1
|
||||
self.group_id = 1
|
||||
self.student_id = 1
|
||||
|
||||
def __str__(self):
|
||||
return f'''
|
||||
Class: {self.class_id}
|
||||
Group: {self.group_id}
|
||||
Student: {self.student_id}
|
||||
'''
|
204
learnlytics/gui/analyzer/group_graph.py
Normal file
204
learnlytics/gui/analyzer/group_graph.py
Normal file
@ -0,0 +1,204 @@
|
||||
from imgui_bundle import (
|
||||
imgui,
|
||||
immapp,
|
||||
imgui_md,
|
||||
im_file_dialog,
|
||||
ImVec2
|
||||
)
|
||||
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
import subprocess, os, platform
|
||||
from peewee import fn
|
||||
|
||||
from dbmodel import *
|
||||
from .analyzer_state import AnalyzerState
|
||||
from .plotter import plot_pie, plot_pdf
|
||||
|
||||
state = AnalyzerState()
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def header(group_id: int) -> None:
|
||||
statics = header
|
||||
|
||||
if group_id < 1:
|
||||
return
|
||||
|
||||
if not statics.inited:
|
||||
statics.group_id = group_id
|
||||
statics.group = Group.get_by_id(group_id)
|
||||
statics.data = [
|
||||
(s, np.sum([sub.points for sub in Submission.select().where(Submission.student_id == s.id)]))
|
||||
for s in Student.select().where(Student.group_id == group_id)
|
||||
]
|
||||
statics.data.sort(key=lambda tup: tup[1], reverse=True)
|
||||
statics.inited = True
|
||||
|
||||
if statics.group_id != group_id:
|
||||
statics.inited = False
|
||||
|
||||
imgui_md.render(f"# {statics.group.name} - {statics.group.project}")
|
||||
imgui.text("")
|
||||
|
||||
if statics.group.name != "NoGroup":
|
||||
changed, _ = imgui.checkbox("Passed", statics.group.has_passed)
|
||||
if changed:
|
||||
statics.group.has_passed = not statics.group.has_passed
|
||||
statics.group.save()
|
||||
|
||||
if imgui.begin_table("Students", len(statics.data), imgui.TableFlags_.sizing_fixed_fit):
|
||||
for n, d in enumerate(statics.data, start=1):
|
||||
s, points = d
|
||||
if points.is_integer():
|
||||
points = int(points)
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
imgui.set_next_item_width(-1)
|
||||
imgui.text(f"{n}. {s.prename} {s.surname} - {points} Points")
|
||||
imgui.end_table()
|
||||
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def plot(group_id: int) -> None:
|
||||
statics = plot
|
||||
|
||||
if group_id < 1:
|
||||
return
|
||||
|
||||
if not statics.inited:
|
||||
statics.group_id = group_id
|
||||
students = Student.select().where(Student.group_id == group_id)
|
||||
|
||||
statics.data = {
|
||||
f"{s.prename} {s.surname}":
|
||||
np.sum([sub.points for sub in Submission.select().where(Submission.student_id == s.id)], dtype=np.float32)
|
||||
for s in students
|
||||
}
|
||||
|
||||
data = np.array(list(statics.data.values()))
|
||||
statics.labels = list(statics.data.keys())
|
||||
statics.data = data / data.sum() * 100
|
||||
|
||||
statics.inited = True
|
||||
|
||||
if statics.group_id != group_id:
|
||||
statics.inited = False
|
||||
|
||||
plot_pie(statics.data, statics.labels)
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def presentation(group_id: int) -> None:
|
||||
statics = presentation
|
||||
|
||||
if group_id < 1:
|
||||
return
|
||||
|
||||
if not statics.inited:
|
||||
statics.group_id = group_id
|
||||
statics.group = Group.get_by_id(statics.group_id)
|
||||
statics.res = None
|
||||
statics.inited = True
|
||||
|
||||
if statics.group_id != group_id:
|
||||
statics.inited = False
|
||||
|
||||
if statics.group.name == "NoGroup":
|
||||
return
|
||||
|
||||
if not statics.group.presentation:
|
||||
imgui.text("No Presentation set for this Group")
|
||||
else:
|
||||
plot_pdf(Path(statics.group.presentation))
|
||||
imgui.same_line()
|
||||
|
||||
# Button to open File Selection
|
||||
if imgui.button("Set Presentation"):
|
||||
im_file_dialog.FileDialog.instance().open(
|
||||
"SelectPresentation", "Open Presentation", "Presentation (*.pdf; *.html){.pdf,.html}"
|
||||
)
|
||||
|
||||
# Handle File Dialog
|
||||
if im_file_dialog.FileDialog.instance().is_done("SelectPresentation"):
|
||||
if im_file_dialog.FileDialog.instance().has_result():
|
||||
statics.res = im_file_dialog.FileDialog.instance().get_result()
|
||||
im_file_dialog.FileDialog.instance().close()
|
||||
|
||||
# Save path to database
|
||||
if statics.res:
|
||||
file = statics.res.path()
|
||||
statics.group.presentation = file
|
||||
statics.group.save()
|
||||
statics.res = None
|
||||
|
||||
imgui.same_line()
|
||||
# Open System Window for PDF
|
||||
if statics.group.presentation and imgui.button("Open"):
|
||||
# I Hate everything about it
|
||||
if platform.system() == 'Darwin': # MacOS
|
||||
subprocess.Popen(('open', statics.group.presentation))
|
||||
elif platform.system() == 'Windows': # Windows
|
||||
os.startfile(statics.group.presentation)
|
||||
else: # Linux & Variants
|
||||
subprocess.Popen(('xdg-open', statics.group.presentation))
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def group_plot(class_id: int) -> None:
|
||||
if class_id < 1:
|
||||
return
|
||||
|
||||
statics = group_plot
|
||||
|
||||
if not statics.inited:
|
||||
statics.class_id = class_id
|
||||
max_points = Lecture.select(fn.SUM(Lecture.points)).where(Lecture.class_id == class_id).scalar()
|
||||
data = {
|
||||
group.name:
|
||||
np.sum([Submission.select(fn.SUM(Submission.points)).where(Submission.student_id == s.id).scalar()
|
||||
/ Student.select().where(Student.group_id == group.id).count()
|
||||
for s in Student.select().where(Student.group_id == group.id)])/max_points
|
||||
|
||||
for group in Group.select().where(Group.class_id == class_id)
|
||||
}
|
||||
|
||||
statics.labels = list(data.keys())
|
||||
data = np.array(list(data.values()))
|
||||
statics.data = data / data.sum() *100
|
||||
|
||||
statics.inited = True
|
||||
|
||||
if statics.class_id != class_id:
|
||||
statics.inited = False
|
||||
|
||||
plot_pie(statics.data, statics.labels, title="Performance per Group")
|
||||
|
||||
def group_graph() -> None:
|
||||
if db.is_closed():
|
||||
imgui.text("No DB loaded")
|
||||
return
|
||||
|
||||
w, h = imgui.get_content_region_avail()
|
||||
|
||||
if imgui.begin_child("Header", ImVec2(w * 0.3, h*0.5), imgui.ChildFlags_.borders.value):
|
||||
header(state.group_id)
|
||||
imgui.end_child()
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.begin_child("Plot", ImVec2(w * 0.7, h*0.5), imgui.ChildFlags_.borders.value):
|
||||
plot(state.group_id)
|
||||
imgui.end_child()
|
||||
|
||||
if imgui.begin_child("PDF", ImVec2(w*0.5, h*0.49), imgui.ChildFlags_.borders.value):
|
||||
#plot_pdf(Path("/storage/programming/Learnlytics/assets/MeWi_4.pdf"))
|
||||
presentation(state.group_id)
|
||||
imgui.end_child()
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.begin_child("Groups", ImVec2(w*0.5, h*0.49), imgui.ChildFlags_.borders.value):
|
||||
group_plot(state.class_id)
|
||||
imgui.end_child()
|
112
learnlytics/gui/analyzer/plotter.py
Normal file
112
learnlytics/gui/analyzer/plotter.py
Normal file
@ -0,0 +1,112 @@
|
||||
from imgui_bundle import (
|
||||
implot,
|
||||
imgui,
|
||||
immapp,
|
||||
immvision,
|
||||
ImVec4,
|
||||
ImVec2
|
||||
)
|
||||
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
from pdf2image import convert_from_path
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
immvision.use_rgb_color_order()
|
||||
|
||||
def plot_bar_line_percentage(data: np.array, labels: list, avg: float) -> None:
|
||||
if not data.size > 0:
|
||||
imgui.text("No Data available")
|
||||
return
|
||||
|
||||
name = hash(avg)
|
||||
|
||||
if avg.is_integer():
|
||||
avg = int(avg)
|
||||
avg = np.ones(len(data)) * avg
|
||||
|
||||
w, h = imgui.get_window_size()
|
||||
implot.push_colormap(implot.Colormap_.hot.value)
|
||||
if implot.begin_plot(f"Performance##{name}", ImVec2(-1, h*0.8), implot.Flags_.no_mouse_text.value | implot.Flags_.no_inputs.value):
|
||||
implot.setup_axes("Lectures", "Percentage")
|
||||
implot.setup_axes_limits(-1, len(data), 0, 110)
|
||||
implot.push_style_var(implot.StyleVar_.fill_alpha.value, 0.6)
|
||||
implot.push_style_var(implot.StyleVar_.line_weight.value, 3)
|
||||
implot.setup_axis_ticks(implot.ImAxis_.x1.value, 0, len(labels), len(labels), [" " for _ in labels], False)
|
||||
implot.plot_bars("Submissions", data)
|
||||
implot.plot_line("Average", avg)
|
||||
|
||||
implot.push_style_color(implot.Col_.inlay_text, ImVec4(190,190,40,255)/255)
|
||||
for x_pos, label in enumerate(labels):
|
||||
y_pos = 50
|
||||
implot.plot_text(label, x_pos, y_pos//2, ImVec2(0,0), implot.TextFlags_.vertical.value)
|
||||
implot.pop_style_color()
|
||||
|
||||
implot.pop_style_var()
|
||||
implot.end_plot()
|
||||
|
||||
def plot_pie(data: np.array, labels: list, title: str = "Group Performance") -> None:
|
||||
if not data.size > 0:
|
||||
imgui.text("No Data available")
|
||||
return
|
||||
|
||||
name = hash(labels[-1])
|
||||
|
||||
w, h = imgui.get_window_size()
|
||||
|
||||
implot.push_colormap(implot.Colormap_.hot.value)
|
||||
if implot.begin_plot(f"{title}##{name}", ImVec2(w*0.95, h*0.95), implot.Flags_.no_mouse_text.value | implot.Flags_.no_inputs.value):
|
||||
implot.setup_axes("", "", implot.AxisFlags_.no_decorations.value, implot.AxisFlags_.no_decorations.value)
|
||||
implot.setup_axes_limits(-1.1, 1.1, -1.1, 1.1)
|
||||
implot.plot_pie_chart(labels, data, 0, 0, 1, "%.2f%%", implot.PieChartFlags_.normalize.value)
|
||||
|
||||
implot.end_plot()
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def plot_pdf(pdf: Path, label: str = "Presentation") -> None:
|
||||
if not pdf.exists():
|
||||
return
|
||||
|
||||
statics = plot_pdf
|
||||
if not statics.inited:
|
||||
statics.pdf = pdf
|
||||
statics.images = convert_from_path(statics.pdf.resolve(), size=720, fmt='jpeg')
|
||||
|
||||
statics.params = immvision.ImageParams(
|
||||
refresh_image = True,
|
||||
show_image_info = False,
|
||||
show_pixel_info = False,
|
||||
show_zoom_buttons = False,
|
||||
show_options_button = False,
|
||||
can_resize = False,
|
||||
zoom_pan_matrix = None,
|
||||
pan_with_mouse = False,
|
||||
zoom_with_mouse_wheel = False,
|
||||
image_display_size = (500, 0)
|
||||
)
|
||||
statics.selected = 0
|
||||
statics.inited = True
|
||||
|
||||
if statics.pdf != pdf:
|
||||
statics.inited = False
|
||||
|
||||
|
||||
h = imgui.get_window_size().y
|
||||
|
||||
image = np.array(statics.images[statics.selected].convert("RGB"))
|
||||
size = (0,int(h*0.8))
|
||||
immvision.image_display(label, image, size, True)
|
||||
|
||||
if imgui.button("Back"):
|
||||
statics.selected -= 1
|
||||
if statics.selected < 0:
|
||||
statics.selected = 0
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.button("Next"):
|
||||
statics.selected += 1
|
||||
if statics.selected > len(statics.images)-1:
|
||||
statics.selected = len(statics.images)-1
|
||||
|
80
learnlytics/gui/analyzer/student_graph.py
Normal file
80
learnlytics/gui/analyzer/student_graph.py
Normal file
@ -0,0 +1,80 @@
|
||||
from imgui_bundle import (
|
||||
imgui,
|
||||
imgui_md,
|
||||
immapp,
|
||||
ImVec2,
|
||||
ImVec4
|
||||
)
|
||||
|
||||
import numpy as np
|
||||
from peewee import fn
|
||||
|
||||
from dbmodel import *
|
||||
|
||||
from .analyzer_state import AnalyzerState
|
||||
from .plotter import plot_bar_line_percentage
|
||||
|
||||
state = AnalyzerState()
|
||||
PROGRESS_BAR_COLOR = ImVec4(190, 190, 40, 255)/255
|
||||
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def header(student_id: int) -> None:
|
||||
statics = header
|
||||
|
||||
if student_id < 1:
|
||||
return
|
||||
|
||||
if not statics.inited:
|
||||
statics.student = Student.get_by_id(student_id)
|
||||
statics.inited = True
|
||||
|
||||
if statics.student.id != student_id:
|
||||
statics.inited = False
|
||||
|
||||
imgui_md.render(f"# {statics.student.prename} {statics.student.surname}")
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def plot(student_id: int) -> None:
|
||||
statics = plot
|
||||
|
||||
if student_id < 1:
|
||||
return
|
||||
|
||||
if not statics.inited:
|
||||
statics.student = Student.get_by_id(student_id)
|
||||
submissions = Submission.select().where(Submission.student_id == statics.student.id)
|
||||
lectures = Lecture.select().where(Lecture.class_id == statics.student.class_id)
|
||||
|
||||
statics.labels = [l.title for l in lectures]
|
||||
statics.data = np.array([sub.points/l.points for sub, l in zip(submissions, lectures)], dtype=np.float32) * 100
|
||||
statics.avg = np.mean(statics.data)/100
|
||||
statics.points = Submission.select(fn.SUM(Submission.points)).where(Submission.student_id == statics.student.id).scalar()
|
||||
if statics.points.is_integer():
|
||||
statics.points = int(statics.points)
|
||||
statics.max_points = Lecture.select(fn.SUM(Lecture.points)).where(Lecture.class_id == statics.student.class_id).scalar()
|
||||
statics.inited = True
|
||||
|
||||
if statics.student.id != student_id:
|
||||
statics.inited = False
|
||||
|
||||
imgui.text(f"{statics.points}/{statics.max_points}")
|
||||
imgui.same_line()
|
||||
w, h = imgui.get_window_size()
|
||||
imgui.push_style_color(imgui.Col_.plot_histogram.value, PROGRESS_BAR_COLOR)
|
||||
imgui.progress_bar(statics.avg, ImVec2(w*0.9, h*0.07), f"{statics.avg:.1%}")
|
||||
imgui.pop_style_color()
|
||||
plot_bar_line_percentage(statics.data, statics.labels, statics.avg*100)
|
||||
|
||||
def student_graph() -> None:
|
||||
if db.is_closed():
|
||||
imgui.text("No DB loaded")
|
||||
return
|
||||
|
||||
w, h = imgui.get_content_region_avail()
|
||||
|
||||
if imgui.begin_child("Header", ImVec2(w, h*0.5), imgui.ChildFlags_.borders.value):
|
||||
header(state.student_id)
|
||||
plot(state.student_id)
|
||||
imgui.end_child()
|
||||
imgui.separator()
|
73
learnlytics/gui/analyzer/student_list.py
Normal file
73
learnlytics/gui/analyzer/student_list.py
Normal file
@ -0,0 +1,73 @@
|
||||
from imgui_bundle import (
|
||||
imgui,
|
||||
immapp,
|
||||
imgui_md
|
||||
)
|
||||
|
||||
from dbmodel import *
|
||||
|
||||
from .analyzer_state import AnalyzerState
|
||||
|
||||
state = AnalyzerState()
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def class_selector() -> int:
|
||||
statics = class_selector
|
||||
|
||||
if not statics.inited:
|
||||
statics.selector = 0
|
||||
statics.inited = True
|
||||
|
||||
labels = (c.name for c in Class.select())
|
||||
_, statics.selector = imgui.combo("##Classes", statics.selector, list(labels))
|
||||
imgui.separator()
|
||||
return Class.select()[statics.selector].id
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def tree(class_id: int) -> None:
|
||||
statics = tree
|
||||
|
||||
if not statics.inited:
|
||||
statics.class_id = class_id
|
||||
statics.data = {
|
||||
group: list(Student.select().where(Student.group_id == group.id))
|
||||
for group in Group.select().where(Group.class_id == statics.class_id)
|
||||
}
|
||||
statics.selected = -1
|
||||
statics.ret = (-1, -1)
|
||||
statics.flags = imgui.TreeNodeFlags_.none.value
|
||||
statics.inited = True
|
||||
|
||||
if statics.class_id != class_id:
|
||||
statics.inited = False
|
||||
|
||||
if imgui.button("Expand"):
|
||||
statics.flags = imgui.TreeNodeFlags_.default_open.value
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.button("Collapse"):
|
||||
statics.flags = imgui.TreeNodeFlags_.none.value
|
||||
|
||||
n = 0
|
||||
for group, students in statics.data.items():
|
||||
if imgui.tree_node_ex(f"{group.name} - {group.project}", statics.flags):
|
||||
for student in students:
|
||||
changed, _ = imgui.selectable(f"{student.prename} {student.surname}", statics.selected == n)
|
||||
if changed:
|
||||
statics.selected = n
|
||||
statics.ret = (group.id, student.id)
|
||||
n += 1
|
||||
n += 1
|
||||
imgui.tree_pop()
|
||||
|
||||
return statics.ret
|
||||
|
||||
def student_list() -> None:
|
||||
|
||||
if db.is_closed():
|
||||
imgui.text("No DB loaded")
|
||||
return
|
||||
|
||||
state.class_id = class_selector()
|
||||
state.group_id, state.student_id = tree(state.class_id)
|
81
learnlytics/gui/database/__init__.py
Normal file
81
learnlytics/gui/database/__init__.py
Normal file
@ -0,0 +1,81 @@
|
||||
from imgui_bundle import hello_imgui, imgui
|
||||
#from ..app_state import AppState
|
||||
from typing import List
|
||||
|
||||
from .database import *
|
||||
|
||||
from .editor import editor
|
||||
|
||||
def database_docking_splits() -> List[hello_imgui.DockingSplit]:
|
||||
"""
|
||||
Defines the docking layout for the database editor.
|
||||
|
||||
Returns a list of docking splits that define the structure of the editor layout.
|
||||
|
||||
:return: A list of `hello_imgui.DockingSplit` objects defining docking positions and sizes.
|
||||
"""
|
||||
split_main_command = hello_imgui.DockingSplit()
|
||||
split_main_command.initial_dock = "MainDockSpace"
|
||||
split_main_command.new_dock = "CommandSpace"
|
||||
split_main_command.direction = imgui.Dir.down
|
||||
split_main_command.ratio = 0.3
|
||||
|
||||
split_main_command2 = hello_imgui.DockingSplit()
|
||||
split_main_command2.initial_dock = "CommandSpace"
|
||||
split_main_command2.new_dock = "CommandSpace2"
|
||||
split_main_command2.direction = imgui.Dir.right
|
||||
split_main_command2.ratio = 0.3
|
||||
|
||||
split_main_misc = hello_imgui.DockingSplit()
|
||||
split_main_misc.initial_dock = "MainDockSpace"
|
||||
split_main_misc.new_dock = "MiscSpace"
|
||||
split_main_misc.direction = imgui.Dir.left
|
||||
split_main_misc.ratio = 0.2
|
||||
|
||||
return [split_main_misc, split_main_command, split_main_command2]
|
||||
|
||||
def set_database_editor_layout() -> List[hello_imgui.DockableWindow]:
|
||||
"""
|
||||
Defines the dockable windows for the database editor.
|
||||
|
||||
Creates and returns a list of dockable windows, including the database file selector, log window,
|
||||
table viewer, and editor.
|
||||
|
||||
:param app_state: The application state.
|
||||
:return: A list of `hello_imgui.DockableWindow` objects representing the UI windows.
|
||||
"""
|
||||
file_dialog = hello_imgui.DockableWindow()
|
||||
file_dialog.label = "Database"
|
||||
file_dialog.dock_space_name = "MiscSpace"
|
||||
file_dialog.gui_function = lambda: select_file()
|
||||
|
||||
log = hello_imgui.DockableWindow()
|
||||
log.label = "Logs"
|
||||
log.dock_space_name = "CommandSpace2"
|
||||
log.gui_function = hello_imgui.log_gui
|
||||
|
||||
table_view = hello_imgui.DockableWindow()
|
||||
table_view.label = "Table"
|
||||
table_view.dock_space_name = "MainDockSpace"
|
||||
table_view.gui_function = lambda: table()
|
||||
|
||||
eeditor = hello_imgui.DockableWindow()
|
||||
eeditor.label = "Editor"
|
||||
eeditor.dock_space_name = "CommandSpace"
|
||||
eeditor.gui_function = lambda: editor()
|
||||
|
||||
return [file_dialog, log, table_view, eeditor]
|
||||
|
||||
def database_editor_layout() -> hello_imgui.DockingParams:
|
||||
"""
|
||||
Configures and returns the docking layout for the database editor.
|
||||
|
||||
:param app_state: The application state.
|
||||
:return: A `hello_imgui.DockingParams` object defining the layout configuration.
|
||||
"""
|
||||
docking_params = hello_imgui.DockingParams()
|
||||
docking_params.layout_name = "Database Editor"
|
||||
docking_params.docking_splits = database_docking_splits()
|
||||
docking_params.dockable_windows = set_database_editor_layout()
|
||||
return docking_params
|
||||
|
BIN
learnlytics/gui/database/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
learnlytics/gui/database/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
learnlytics/gui/database/__pycache__/database.cpython-312.pyc
Normal file
BIN
learnlytics/gui/database/__pycache__/database.cpython-312.pyc
Normal file
Binary file not shown.
BIN
learnlytics/gui/database/__pycache__/editor.cpython-312.pyc
Normal file
BIN
learnlytics/gui/database/__pycache__/editor.cpython-312.pyc
Normal file
Binary file not shown.
348
learnlytics/gui/database/database.py
Normal file
348
learnlytics/gui/database/database.py
Normal file
@ -0,0 +1,348 @@
|
||||
"""
|
||||
Database Editor UI Module
|
||||
|
||||
This module defines the graphical user interface (GUI) components and layout for a database editor using the
|
||||
HelloImGui and ImGui frameworks. It provides a class editor, docking layout configurations, and functions
|
||||
to set up the database editing environment.
|
||||
"""
|
||||
|
||||
# Custom
|
||||
from dbmodel import *
|
||||
from gui import *
|
||||
from grader import get_gradings
|
||||
|
||||
# External
|
||||
from imgui_bundle import (
|
||||
imgui,
|
||||
imgui_ctx,
|
||||
immapp,
|
||||
imgui_md,
|
||||
im_file_dialog,
|
||||
hello_imgui
|
||||
)
|
||||
|
||||
import peewee
|
||||
|
||||
# Built In
|
||||
from typing import List
|
||||
import shelve
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timezone
|
||||
import pytz
|
||||
from tzlocal import get_localzone
|
||||
|
||||
|
||||
def file_info(path: Path) -> None:
|
||||
"""
|
||||
Displays file information in an ImGui table.
|
||||
|
||||
Args:
|
||||
path (Path): The file path whose information is to be displayed.
|
||||
|
||||
The function retrieves the file's size, last access time, and creation time,
|
||||
formats the data, and presents it using ImGui tables.
|
||||
"""
|
||||
# Retrieve file statistics
|
||||
stat = path.stat()
|
||||
modified = datetime.fromtimestamp(stat.st_atime) # Last access time
|
||||
created = datetime.fromtimestamp(stat.st_ctime) # Creation time
|
||||
format = '%c' # Standard date-time format
|
||||
|
||||
# Prepare file data dictionary
|
||||
data = {
|
||||
"File": path.name,
|
||||
"Size": f"{stat.st_size/100:.2f} KB", # Convert bytes to KB (incorrect divisor, should be 1024)
|
||||
"Modified": modified.strftime(format),
|
||||
"Created": created.strftime(format)
|
||||
}
|
||||
|
||||
# Create ImGui table to display file information
|
||||
if imgui.begin_table("File Info", 2):
|
||||
imgui.table_setup_column(" ", 0)
|
||||
imgui.table_setup_column(" ")
|
||||
|
||||
# Iterate over file data and populate table
|
||||
for k, v in data.items():
|
||||
imgui.push_id(k)
|
||||
imgui.table_next_row()
|
||||
imgui.table_next_column()
|
||||
imgui.text(k)
|
||||
imgui.table_next_column()
|
||||
imgui.text(v)
|
||||
imgui.pop_id()
|
||||
|
||||
imgui.end_table()
|
||||
|
||||
@immapp.static(inited=False, res=False)
|
||||
def select_file() -> None:
|
||||
"""
|
||||
Handles the selection and loading of a database file (JSON or SQLite).
|
||||
It initializes necessary state, allows users to open a file dialog,
|
||||
and processes the selected file by either loading or converting it.
|
||||
"""
|
||||
statics = select_file # Access static variables within the function
|
||||
|
||||
# Initialize static variables on the first function call
|
||||
if not statics.inited:
|
||||
statics.res = None # Stores the selected file result
|
||||
statics.current = None # Stores the currently loaded database file path
|
||||
|
||||
# Retrieve the last used database file from persistent storage
|
||||
with shelve.open("state") as state:
|
||||
statics.current = Path(state.get("DB") or "")
|
||||
statics.inited = True
|
||||
|
||||
# Render UI title and display file information
|
||||
imgui_md.render("# Database Manager")
|
||||
file_info(statics.current)
|
||||
|
||||
# Button to open the file selection dialog
|
||||
if imgui.button("Open File"):
|
||||
im_file_dialog.FileDialog.instance().open(
|
||||
"SelectDatabase", "Open Database", "Database File (*.json;*.db){.json,.db}"
|
||||
)
|
||||
|
||||
# Handle the file dialog result
|
||||
if im_file_dialog.FileDialog.instance().is_done("SelectDatabase"):
|
||||
if im_file_dialog.FileDialog.instance().has_result():
|
||||
statics.res = im_file_dialog.FileDialog.instance().get_result()
|
||||
im_file_dialog.FileDialog.instance().close()
|
||||
|
||||
# Process the selected file if available
|
||||
if statics.res:
|
||||
filename = statics.res.filename()
|
||||
info = Path(statics.res.path())
|
||||
|
||||
imgui.separator() # UI separator for clarity
|
||||
file_info(info) # Display information about the selected file
|
||||
|
||||
file = None
|
||||
|
||||
# Load the selected database file
|
||||
if imgui.button("Load"):
|
||||
# Ensure any currently open database is closed before loading a new one
|
||||
if not db.is_closed():
|
||||
db.close()
|
||||
|
||||
# Handle JSON files by converting them to SQLite databases
|
||||
if statics.res.extension() == '.json':
|
||||
file = filename.removesuffix('.json') + '.db' # Convert JSON filename to SQLite filename
|
||||
init_db(file)
|
||||
load_from_json(str(info)) # Convert and load JSON data into the database
|
||||
|
||||
# Handle SQLite database files directly
|
||||
if statics.res.extension() == '.db':
|
||||
file = str(statics.res.path())
|
||||
init_db(file)
|
||||
|
||||
# Save the selected database path to persistent storage
|
||||
with shelve.open("state") as state:
|
||||
state["DB"] = file
|
||||
|
||||
# Update application state and reset selection result
|
||||
statics.res = None
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def table() -> None:
|
||||
statics = table
|
||||
|
||||
if db.is_closed():
|
||||
imgui.text("DB")
|
||||
return
|
||||
|
||||
if not statics.inited:
|
||||
statics.table_flags = (
|
||||
imgui.TableFlags_.row_bg.value
|
||||
| imgui.TableFlags_.borders.value
|
||||
| imgui.TableFlags_.resizable.value
|
||||
| imgui.TableFlags_.sizing_stretch_same.value
|
||||
)
|
||||
statics.class_id = None
|
||||
statics.inited = True
|
||||
|
||||
statics.students = Student.select().where(Student.class_id == statics.class_id)
|
||||
statics.lectures = Lecture.select().where(Lecture.class_id == statics.class_id)
|
||||
|
||||
statics.rows = len(statics.students)
|
||||
statics.cols = len(statics.lectures)
|
||||
statics.grid = list()
|
||||
|
||||
for student in statics.students:
|
||||
t_list = list()
|
||||
sub = Submission.select().where(Submission.student_id == student.id)
|
||||
for s in sub:
|
||||
t_list.append(s)
|
||||
statics.grid.append(t_list)
|
||||
|
||||
statics.table_header = [f"{lecture.title} ({lecture.points})" for lecture in statics.lectures]
|
||||
|
||||
if imgui.begin_table("Student Grid", statics.cols+1, statics.table_flags):
|
||||
# Setup Header
|
||||
imgui.table_setup_column("Students")
|
||||
for header in statics.table_header:
|
||||
imgui.table_setup_column(header)
|
||||
imgui.table_headers_row()
|
||||
|
||||
# Fill Student names
|
||||
for row in range(statics.rows):
|
||||
imgui.table_next_row()
|
||||
imgui.table_set_column_index(0)
|
||||
student = statics.students[row]
|
||||
imgui.text(f"{student.prename} {student.surname}")
|
||||
|
||||
for col in range(statics.cols):
|
||||
imgui.table_set_column_index(col+1)
|
||||
changed, value = imgui.input_float(f"##{statics.grid[row][col]}", statics.grid[row][col].points, 0.0, 0.0, "%.1f")
|
||||
|
||||
if changed:
|
||||
# Boundary Check
|
||||
if value < 0:
|
||||
value = 0
|
||||
if value > statics.lectures[col].points:
|
||||
value = statics.lectures[col].points
|
||||
|
||||
old_value = statics.grid[row][col].points
|
||||
statics.grid[row][col].points = value
|
||||
statics.grid[row][col].save()
|
||||
|
||||
student = statics.students[row]
|
||||
lecture = statics.lectures[col]
|
||||
sub = statics.grid[row][col]
|
||||
|
||||
imgui.end_table()
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def editor() -> None:
|
||||
"""
|
||||
Class Editor UI Component.
|
||||
|
||||
This function initializes and renders the class selection interface within the database editor.
|
||||
It maintains a static state to keep track of the selected class and fetches available classes.
|
||||
"""
|
||||
statics = class_editor
|
||||
if db.is_closed():
|
||||
imgui.text("DB")
|
||||
return
|
||||
statics.classes = Class.select()
|
||||
if not statics.inited:
|
||||
statics.selected = 0
|
||||
statics.value = statics.classes[statics.selected].name if statics.classes else str()
|
||||
statics.inited = True
|
||||
|
||||
# Fetch available classes from the database
|
||||
|
||||
# Render the UI for class selection
|
||||
imgui_md.render("# Edit Classes")
|
||||
changed, statics.selected = imgui.combo("##Classes", statics.selected, [c.name for c in statics.classes])
|
||||
if changed:
|
||||
statics.value = statics.classes[statics.selected].name
|
||||
_, statics.value = imgui.input_text("##input_class", statics.value)
|
||||
|
||||
if imgui.button("Update"):
|
||||
clas = statics.classes[statics.selected]
|
||||
clas.name, old_name = statics.value, clas.name
|
||||
clas.save()
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.button("New"):
|
||||
Class.create(name=statics.value)
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.button("Delete"):
|
||||
clas = statics.classes[statics.selected]
|
||||
clas.delete_instance()
|
||||
statics.selected -= 1
|
||||
statics.value = statics.classes[statics.selected].name
|
||||
|
||||
|
||||
def create_editor_popup(table, action: str, selectors: dict) -> None:
|
||||
table_flags = (
|
||||
imgui.TableFlags_.row_bg.value
|
||||
)
|
||||
cols = 2
|
||||
rows = len(table._meta.fields)
|
||||
|
||||
if imgui.begin_table("Editor Grid", cols, table_flags):
|
||||
# Setup Header
|
||||
for header in ["Attribute", "Value"]:
|
||||
imgui.table_setup_column(header, imgui.TableColumnFlags_.width_stretch.value, 1/len(header))
|
||||
imgui.table_headers_row()
|
||||
|
||||
id = 0
|
||||
for k, v in table._meta.fields.items():
|
||||
# Don't show Fields
|
||||
match type(v):
|
||||
case peewee.AutoField:
|
||||
continue
|
||||
case peewee.DateTimeField:
|
||||
continue
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_set_column_index(0)
|
||||
label = str(k).removesuffix('_id')
|
||||
imgui.text(label.title())
|
||||
imgui.table_set_column_index(1)
|
||||
|
||||
# Generate Input for Type
|
||||
match type(v):
|
||||
case peewee.IntegerField:
|
||||
if id not in selectors:
|
||||
selectors[id] = int()
|
||||
_, selectors[id] = imgui.input_int(f"##{id}", selectors[id], 1)
|
||||
case peewee.CharField:
|
||||
if id not in selectors:
|
||||
if k == 'grader':
|
||||
selectors[id] = int()
|
||||
else:
|
||||
selectors[id] = str()
|
||||
|
||||
if k == 'grader':
|
||||
graders = [g.alt_name for g in get_gradings()]
|
||||
_, selectors[id] = imgui.combo(f"##{id}", selectors[id], graders)
|
||||
else:
|
||||
_, selectors[id] = imgui.input_text(f"##{id}", selectors[id])
|
||||
case peewee.FloatField:
|
||||
if id not in selectors:
|
||||
selectors[id] = float()
|
||||
_, selectors[id] = imgui.input_float(f"##{id}", selectors[id])
|
||||
case peewee.ForeignKeyField:
|
||||
if id not in selectors:
|
||||
selectors[id] = int()
|
||||
|
||||
labels = list()
|
||||
match k:
|
||||
case 'class_id':
|
||||
labels = [clas.name for clas in Class.select()]
|
||||
case 'lecture_id':
|
||||
labels = [lecture.title for lecture in Lecture.select()]
|
||||
|
||||
if not labels:
|
||||
imgui.text("No Element for this Attribute")
|
||||
else:
|
||||
_, selectors[id] = imgui.combo(f"##{id}", selectors[id], labels)
|
||||
case _:
|
||||
imgui.text(f"Unknown Field {k}")
|
||||
|
||||
id += 1
|
||||
imgui.end_table()
|
||||
|
||||
if imgui.button(action):
|
||||
match action:
|
||||
case "Create":
|
||||
print("Create")
|
||||
case "Update":
|
||||
print("Update")
|
||||
case "Delete":
|
||||
print("Delete")
|
||||
case _:
|
||||
print("Unknown Case")
|
||||
|
||||
# Clear & Close Popup
|
||||
selectors.clear()
|
||||
imgui.close_current_popup()
|
||||
|
||||
|
||||
|
||||
|
141
learnlytics/gui/database/editor.py
Normal file
141
learnlytics/gui/database/editor.py
Normal file
@ -0,0 +1,141 @@
|
||||
from imgui_bundle import (
|
||||
imgui,
|
||||
immapp,
|
||||
imgui_md,
|
||||
hello_imgui
|
||||
)
|
||||
|
||||
from grader import get_gradings, get_grader
|
||||
|
||||
from dbmodel import *
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def editor() -> None:
|
||||
"""
|
||||
Database Editor UI Function.
|
||||
|
||||
Calls the class editor function to render its UI component.
|
||||
"""
|
||||
if db.is_closed():
|
||||
imgui.text("Please open a Database")
|
||||
return
|
||||
|
||||
statics = editor
|
||||
if not statics.inited:
|
||||
statics.selected = 0
|
||||
statics.actions = ["Create", "Update", "Delete"]
|
||||
statics.action = str()
|
||||
statics.inited = True
|
||||
statics.selectors = dict()
|
||||
|
||||
imgui.text("Select what you want to Edit:")
|
||||
changed, statics.selected = imgui.combo('##DBSelector', statics.selected, table_labels)
|
||||
for action in statics.actions:
|
||||
if imgui.button(action):
|
||||
imgui.open_popup(table_labels[statics.selected])
|
||||
statics.action = action
|
||||
imgui.same_line()
|
||||
|
||||
if imgui.begin_popup_modal(table_labels[statics.selected])[0]:
|
||||
table = tables[statics.selected]
|
||||
imgui_md.render(f"# {statics.action} {table_labels[statics.selected]}")
|
||||
|
||||
if imgui.begin_table("Editor Grid", 2, imgui.TableFlags_.row_bg.value):
|
||||
# Setup Header
|
||||
for header in ["Attribute", "Value"]:
|
||||
imgui.table_setup_column(header, imgui.TableColumnFlags_.width_stretch.value, 1/len(header))
|
||||
imgui.table_headers_row()
|
||||
|
||||
|
||||
student_editor(statics.action)
|
||||
|
||||
imgui.end_table()
|
||||
|
||||
|
||||
imgui.end_popup()
|
||||
|
||||
@immapp.static(inited=False)
|
||||
def student_editor(action: str) -> dict:
|
||||
'''
|
||||
|
||||
'''
|
||||
statics = student_editor
|
||||
if not statics.inited:
|
||||
statics.classes = tuple(Class.select())
|
||||
statics.residences = tuple(Residence.select())
|
||||
statics.classes_labels = tuple(clas.name for clas in statics.classes)
|
||||
statics.graders = tuple(grader.alt_name for grader in get_gradings())
|
||||
statics.buffer = {
|
||||
'prename': "",
|
||||
'surname': "",
|
||||
'sex': 0,
|
||||
'class_id': 0,
|
||||
'group_id': 0,
|
||||
'grader': 0,
|
||||
'residence': 0,
|
||||
|
||||
}
|
||||
statics.inited = True
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_set_column_index(0)
|
||||
imgui.text("First Name")
|
||||
imgui.table_set_column_index(1)
|
||||
_, statics.buffer['prename'] = imgui.input_text("##prename", statics.buffer['prename'])
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_set_column_index(0)
|
||||
imgui.text("Last Name")
|
||||
imgui.table_set_column_index(1)
|
||||
_, statics.buffer['surname'] = imgui.input_text("##surname", statics.buffer['surname'])
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_set_column_index(0)
|
||||
imgui.text("Gender")
|
||||
imgui.table_set_column_index(1)
|
||||
_, statics.buffer['sex'] = imgui.combo("##sex", statics.buffer['sex'], ['Male', 'Female'])
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_set_column_index(0)
|
||||
imgui.text("Class")
|
||||
imgui.table_set_column_index(1)
|
||||
_, statics.buffer['class_id'] = imgui.combo("##class_id", statics.buffer['class_id'], statics.classes_labels)
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_set_column_index(0)
|
||||
imgui.text("Project Group")
|
||||
imgui.table_set_column_index(1)
|
||||
groups = tuple(Group.select().where(Group.class_id == statics.classes[statics.buffer['class_id']].id))
|
||||
_, statics.buffer['group_id'] = imgui.combo("##group_id", statics.buffer['group_id'], tuple(group.name for group in groups))
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_set_column_index(0)
|
||||
imgui.text("Grader")
|
||||
imgui.table_set_column_index(1)
|
||||
_, statics.buffer['grader'] = imgui.combo("##grader", statics.buffer['grader'], statics.graders)
|
||||
|
||||
imgui.table_next_row()
|
||||
imgui.table_set_column_index(0)
|
||||
imgui.text("Residence")
|
||||
imgui.table_set_column_index(1)
|
||||
_, statics.buffer['residence'] = imgui.combo("##residence", statics.buffer['residence'], [str(residence.id) for residence in statics.residences])
|
||||
|
||||
if imgui.button(action):
|
||||
match action:
|
||||
case "Create":
|
||||
Student.create(
|
||||
prename = statics.buffer['prename'],
|
||||
surname = statics.buffer['surname'],
|
||||
sex = 'Female' if statics.buffer['sex'] else 'Male',
|
||||
class_id = statics.classes[statics.buffer['class_id']].id,
|
||||
group_id = groups[statics.buffer['group_id']].id,
|
||||
residence = statics.residences[statics.buffer['residence']]
|
||||
)
|
||||
case "Update":
|
||||
pass
|
||||
case "Delete":
|
||||
pass
|
||||
statics.inited = False
|
||||
imgui.close_current_popup()
|
||||
|
||||
|
30
learnlytics/gui/gui.py
Normal file
30
learnlytics/gui/gui.py
Normal file
@ -0,0 +1,30 @@
|
||||
"""
|
||||
Student Analyzer Application
|
||||
|
||||
This script initializes and runs the Student Analyzer application, which provides an interface for
|
||||
managing student data, class records, and submissions. It uses the Hello ImGui framework for UI rendering
|
||||
and integrates a database to store and manipulate student information.
|
||||
|
||||
Modules:
|
||||
- Custom Imports: Imports internal models and application state.
|
||||
- Layouts: Defines different UI layouts for the analyzer and database editor.
|
||||
- External Libraries: Uses imgui_bundle and Hello ImGui for UI rendering.
|
||||
- Built-in Libraries: Uses shelve for persistent state storage and typing for type hints.
|
||||
"""
|
||||
|
||||
# Custom
|
||||
from dbmodel import * # Importing database models like Class, Student, Lecture, and Submission
|
||||
|
||||
# External
|
||||
from imgui_bundle import imgui, hello_imgui # ImGui-based UI framework
|
||||
|
||||
def menu_bar(runner_params: hello_imgui.RunnerParams) -> None:
|
||||
"""Defines the application's menu bar."""
|
||||
hello_imgui.show_app_menu(runner_params)
|
||||
hello_imgui.show_view_menu(runner_params)
|
||||
|
||||
def status_bar() -> None:
|
||||
"""Displays the status bar information."""
|
||||
imgui.text("Student Analyzer by @DerGrumpf")
|
||||
|
||||
|
73
learnlytics/main.py
Normal file
73
learnlytics/main.py
Normal file
@ -0,0 +1,73 @@
|
||||
import shelve
|
||||
|
||||
from imgui_bundle import (
|
||||
hello_imgui,
|
||||
immapp
|
||||
)
|
||||
|
||||
from gui import (
|
||||
analyzer_layout,
|
||||
database_editor_layout,
|
||||
menu_bar,
|
||||
status_bar
|
||||
)
|
||||
|
||||
from dbmodel import init_db
|
||||
|
||||
def main() -> None:
|
||||
"""Main function to initialize and run the application."""
|
||||
|
||||
# Load Database
|
||||
try:
|
||||
with shelve.open("state") as state:
|
||||
v = state.get("DB") # Retrieve stored database connection info
|
||||
init_db(v)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
# Set Window Parameters
|
||||
runner_params = hello_imgui.RunnerParams()
|
||||
runner_params.app_window_params.window_title = "Analyzer"
|
||||
runner_params.imgui_window_params.menu_app_title = "Analyzer"
|
||||
runner_params.app_window_params.window_geometry.size = (1000, 900)
|
||||
runner_params.app_window_params.restore_previous_geometry = True
|
||||
runner_params.app_window_params.borderless = True
|
||||
runner_params.app_window_params.borderless_movable = True
|
||||
runner_params.app_window_params.borderless_resizable = True
|
||||
runner_params.app_window_params.borderless_closable = True
|
||||
|
||||
# Configure UI Elements
|
||||
runner_params.imgui_window_params.show_menu_bar = True
|
||||
runner_params.imgui_window_params.show_menu_app = False
|
||||
runner_params.imgui_window_params.show_menu_view = False
|
||||
runner_params.imgui_window_params.show_status_bar = True
|
||||
runner_params.callbacks.show_menus = lambda: menu_bar(runner_params)
|
||||
runner_params.callbacks.show_status = lambda: status_bar()
|
||||
|
||||
# Application layout
|
||||
runner_params.imgui_window_params.default_imgui_window_type = (
|
||||
hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space
|
||||
)
|
||||
runner_params.imgui_window_params.enable_viewports = True
|
||||
runner_params.docking_params = analyzer_layout()
|
||||
runner_params.alternative_docking_layouts = [
|
||||
database_editor_layout()
|
||||
]
|
||||
|
||||
# Save App Settings
|
||||
runner_params.ini_folder_type = hello_imgui.IniFolderType.app_user_config_folder
|
||||
runner_params.ini_filename = "Analyzer/Analyzer.ini"
|
||||
runner_params.docking_params.layout_condition = hello_imgui.DockingLayoutCondition.application_start
|
||||
|
||||
# Run the Application
|
||||
add_ons_params = immapp.AddOnsParams()
|
||||
add_ons_params.with_markdown = True
|
||||
add_ons_params.with_implot = True
|
||||
add_ons_params.with_implot3d = True
|
||||
|
||||
immapp.run(runner_params, add_ons_params)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
696
poetry.lock
generated
Normal file
696
poetry.lock
generated
Normal file
@ -0,0 +1,696 @@
|
||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
description = "Reusable constraint types to use with typing.Annotated"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
|
||||
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colour"
|
||||
version = "0.1.5"
|
||||
description = "converts and manipulates various color representation (HSL, RVB, web, X11, ...)"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "colour-0.1.5-py2.py3-none-any.whl", hash = "sha256:33f6db9d564fadc16e59921a56999b79571160ce09916303d35346dddc17978c"},
|
||||
{file = "colour-0.1.5.tar.gz", hash = "sha256:af20120fefd2afede8b001fbef2ea9da70ad7d49fafdb6489025dae8745c3aee"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
test = ["nose"]
|
||||
|
||||
[[package]]
|
||||
name = "flatpak-pip-generator"
|
||||
version = "24.0.0"
|
||||
description = "this is an unofficial pypi distribution of the flatpak_pip_generator script"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "flatpak_pip_generator-24.0.0-py3-none-any.whl", hash = "sha256:9ce8dac77eb2b6423af32ed44987204cf348f2e0e76400c1ba59304c124f8b19"},
|
||||
{file = "flatpak_pip_generator-24.0.0.tar.gz", hash = "sha256:cfe72c05b8d13084bd2aaabc107384924f1bcd5132832162b2a3dc0d2d2bda34"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyyaml = "*"
|
||||
requirements-parser = "*"
|
||||
|
||||
[package.extras]
|
||||
build = ["build", "setuptools", "twine"]
|
||||
|
||||
[[package]]
|
||||
name = "glfw"
|
||||
version = "2.8.0"
|
||||
description = "A ctypes-based wrapper for GLFW3."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "glfw-2.8.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-macosx_10_6_intel.whl", hash = "sha256:28aaef2f022b57cd37525ad1d11ba9049931a273359964fb3cd36762eb093ed1"},
|
||||
{file = "glfw-2.8.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-macosx_11_0_arm64.whl", hash = "sha256:0239e478ff065719064fd1272ad29f8542e8178b11c614674bb930d85aa2d1e7"},
|
||||
{file = "glfw-2.8.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-manylinux2014_aarch64.whl", hash = "sha256:44b5313cffa4a037e8e2988bccba6fa7de0a24123509f4ccf3e49b831cf72abb"},
|
||||
{file = "glfw-2.8.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-manylinux2014_x86_64.whl", hash = "sha256:0fd982cf42a8e3137e05e392280826311961f9e99c82a0ccf22a63a0d2acd143"},
|
||||
{file = "glfw-2.8.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-manylinux_2_28_aarch64.whl", hash = "sha256:523e1fc03898bcb0711f78d6f21eee58c1f865bb764cbd36b9549580a4c43454"},
|
||||
{file = "glfw-2.8.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-manylinux_2_28_x86_64.whl", hash = "sha256:9cd20351b14587c6fe7063afb33cc03152e38dd2bff2b69613cb044bf3bdb635"},
|
||||
{file = "glfw-2.8.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-win32.whl", hash = "sha256:13a75d8d3e8d4bb24595f2354a392ccf7c54ddd20dacb88e1188b760f2a7714b"},
|
||||
{file = "glfw-2.8.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-win_amd64.whl", hash = "sha256:1416f10d2c2748c39910e9d9e6a10a5473743c5a745518061e4051be4c5caaa1"},
|
||||
{file = "glfw-2.8.0.tar.gz", hash = "sha256:90e90d328b0b26fed6e1631d21801e2d8a7a0c5dcb480e733c177567ec9666f0"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
preview = ["glfw-preview"]
|
||||
|
||||
[[package]]
|
||||
name = "imgui-bundle"
|
||||
version = "1.6.2"
|
||||
description = "Dear ImGui Bundle: easily create ImGui applications in Python and C++. Batteries included!"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "imgui_bundle-1.6.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:dbf2a82baf6d8ab4787ed531baa339c2c3f8f76f2c70f3c764f8f3c3e6ed2097"},
|
||||
{file = "imgui_bundle-1.6.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:519194a5b4fd3c1748bcf5b9afb6575b883e68c87947ee1332f4fa27fbcfcd14"},
|
||||
{file = "imgui_bundle-1.6.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80e96d31e1ce56df6de1120c0f0764ab54e6af1efd1752499133cba7e879b1e9"},
|
||||
{file = "imgui_bundle-1.6.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3baefbb7374909541900dec15d4630f06e4f1413c3e6560ec619e210a0f5d101"},
|
||||
{file = "imgui_bundle-1.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:e6ba109dbc788a217d283cb7159391f40f6efacd79d8d1a4fa61962220573fbc"},
|
||||
{file = "imgui_bundle-1.6.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:486e8408d4b0e3c2c8ef1814ddfde0aa87d25e6f8a650b7ccda672a2e1b47062"},
|
||||
{file = "imgui_bundle-1.6.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:1ec8db8526eb6ad7dfe77c38ea4a19d5beaef2f79a0b080515929df77ac0c5fc"},
|
||||
{file = "imgui_bundle-1.6.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5152504650622110559d5039fa60b09da91a5b7f8bcd4065fdf7f053b1967ad7"},
|
||||
{file = "imgui_bundle-1.6.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:40a4f4d1a89b1e5b421077cbdde3f217b0b24468875c930625c9f5749421617f"},
|
||||
{file = "imgui_bundle-1.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:add71c6733b6785e48bb059472e10e49d7ef56108fac7eb2bf05692cc6040b73"},
|
||||
{file = "imgui_bundle-1.6.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f2e4494339209a8417d745658b4d36efe03949536a2b02492a03b2eb8f187850"},
|
||||
{file = "imgui_bundle-1.6.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:2a26dd3475271caf0ea5ca800bde95c9bc4cc889739869d9c6c0335c11d579e4"},
|
||||
{file = "imgui_bundle-1.6.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3316e7227d25a1618205db478a7c8258a6f667f9e6a532e6a1075e26c33ded1e"},
|
||||
{file = "imgui_bundle-1.6.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c76d14d2a270ff8b5de55aa8fafc255402570ebe6f3cbe0a6fd030c26f48b217"},
|
||||
{file = "imgui_bundle-1.6.2-cp313-cp313-win_amd64.whl", hash = "sha256:4bf9c0afb85dfdb9ca49e63d388a2a6e5b8935a5d9552fe7c51b3969caa5d4f8"},
|
||||
{file = "imgui_bundle-1.6.2.tar.gz", hash = "sha256:17b2019a0d4ebb66bc29b234c086ea5a3cccc7c0c540517fe569d84372316970"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
glfw = "*"
|
||||
munch = "*"
|
||||
numpy = "*"
|
||||
pillow = "*"
|
||||
pydantic = "*"
|
||||
PyOpenGL = "*"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "munch"
|
||||
version = "4.0.0"
|
||||
description = "A dot-accessible dictionary (a la JavaScript objects)"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "munch-4.0.0-py2.py3-none-any.whl", hash = "sha256:71033c45db9fb677a0b7eb517a4ce70ae09258490e419b0e7f00d1e386ecb1b4"},
|
||||
{file = "munch-4.0.0.tar.gz", hash = "sha256:542cb151461263216a4e37c3fd9afc425feeaf38aaa3025cd2a981fadb422235"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
testing = ["astroid (>=2.0)", "coverage", "pylint (>=2.3.1,<2.4.0)", "pytest"]
|
||||
yaml = ["PyYAML (>=5.1.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.2.3"
|
||||
description = "Fundamental package for array computing in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "numpy-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbc6472e01952d3d1b2772b720428f8b90e2deea8344e854df22b0618e9cce71"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdfe0c22692a30cd830c0755746473ae66c4a8f2e7bd508b35fb3b6a0813d787"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:e37242f5324ffd9f7ba5acf96d774f9276aa62a966c0bad8dae692deebec7716"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95172a21038c9b423e68be78fd0be6e1b97674cde269b76fe269a5dfa6fadf0b"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b47c440210c5d1d67e1cf434124e0b5c395eee1f5806fdd89b553ed1acd0a3"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0391ea3622f5c51a2e29708877d56e3d276827ac5447d7f45e9bc4ade8923c52"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f6b3dfc7661f8842babd8ea07e9897fe3d9b69a1d7e5fbb743e4160f9387833b"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1ad78ce7f18ce4e7df1b2ea4019b5817a2f6a8a16e34ff2775f646adce0a5027"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-win32.whl", hash = "sha256:5ebeb7ef54a7be11044c33a17b2624abe4307a75893c001a4800857956b41094"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:596140185c7fa113563c67c2e894eabe0daea18cf8e33851738c19f70ce86aeb"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:16372619ee728ed67a2a606a614f56d3eabc5b86f8b615c79d01957062826ca8"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5521a06a3148686d9269c53b09f7d399a5725c47bbb5b35747e1cb76326b714b"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:7c8dde0ca2f77828815fd1aedfdf52e59071a5bae30dac3b4da2a335c672149a"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:77974aba6c1bc26e3c205c2214f0d5b4305bdc719268b93e768ddb17e3fdd636"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d42f9c36d06440e34226e8bd65ff065ca0963aeecada587b937011efa02cdc9d"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2712c5179f40af9ddc8f6727f2bd910ea0eb50206daea75f58ddd9fa3f715bb"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c8b0451d2ec95010d1db8ca733afc41f659f425b7f608af569711097fd6014e2"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9b4a8148c57ecac25a16b0e11798cbe88edf5237b0df99973687dd866f05e1b"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-win32.whl", hash = "sha256:1f45315b2dc58d8a3e7754fe4e38b6fce132dab284a92851e41b2b344f6441c5"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f48ba6f6c13e5e49f3d3efb1b51c8193215c42ac82610a04624906a9270be6f"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12c045f43b1d2915eca6b880a7f4a256f59d62df4f044788c8ba67709412128d"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87eed225fd415bbae787f93a457af7f5990b92a334e346f72070bf569b9c9c95"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:712a64103d97c404e87d4d7c47fb0c7ff9acccc625ca2002848e0d53288b90ea"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a5ae282abe60a2db0fd407072aff4599c279bcd6e9a2475500fc35b00a57c532"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5266de33d4c3420973cf9ae3b98b54a2a6d53a559310e3236c4b2b06b9c07d4e"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:34c1b7e83f94f3b564b35f480f5652a47007dd91f7c839f404d03279cc8dd021"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d8335b5f1b6e2bce120d55fb17064b0262ff29b459e8493d1785c18ae2553b8"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-win32.whl", hash = "sha256:4d9828d25fb246bedd31e04c9e75714a4087211ac348cb39c8c5f99dbb6683fe"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bfdb06b395385ea9b91bf55c1adf1b297c9fdb531552845ff1d3ea6e40d5aba"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23c9f4edbf4c065fddb10a4f6e8b6a244342d95966a48820c614891e5059bb50"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:a0c03b6be48aaf92525cccf393265e02773be8fd9551a2f9adbe7db1fa2b60f1"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:2376e317111daa0a6739e50f7ee2a6353f768489102308b0d98fcf4a04f7f3b5"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fb62fe3d206d72fe1cfe31c4a1106ad2b136fcc1606093aeab314f02930fdf2"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52659ad2534427dffcc36aac76bebdd02b67e3b7a619ac67543bc9bfe6b7cdb1"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b416af7d0ed3271cad0f0a0d0bee0911ed7eba23e66f8424d9f3dfcdcae1304"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1402da8e0f435991983d0a9708b779f95a8c98c6b18a171b9f1be09005e64d9d"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-win32.whl", hash = "sha256:136553f123ee2951bfcfbc264acd34a2fc2f29d7cdf610ce7daf672b6fbaa693"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5b732c8beef1d7bc2d9e476dbba20aaff6167bf205ad9aa8d30913859e82884b"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:435e7a933b9fda8126130b046975a968cc2d833b505475e588339e09f7672890"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7678556eeb0152cbd1522b684dcd215250885993dd00adb93679ec3c0e6e091c"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2e8da03bd561504d9b20e7a12340870dfc206c64ea59b4cfee9fceb95070ee94"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:c9aa4496fd0e17e3843399f533d62857cef5900facf93e735ef65aa4bbc90ef0"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4ca91d61a4bf61b0f2228f24bbfa6a9facd5f8af03759fe2a655c50ae2c6610"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:deaa09cd492e24fd9b15296844c0ad1b3c976da7907e1c1ed3a0ad21dded6f76"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:246535e2f7496b7ac85deffe932896a3577be7af8fb7eebe7146444680297e9a"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:daf43a3d1ea699402c5a850e5313680ac355b4adc9770cd5cfc2940e7861f1bf"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-win32.whl", hash = "sha256:cf802eef1f0134afb81fef94020351be4fe1d6681aadf9c5e862af6602af64ef"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-win_amd64.whl", hash = "sha256:aee2512827ceb6d7f517c8b85aa5d3923afe8fc7a57d028cffcd522f1c6fd082"},
|
||||
{file = "numpy-2.2.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3c2ec8a0f51d60f1e9c0c5ab116b7fc104b165ada3f6c58abf881cb2eb16044d"},
|
||||
{file = "numpy-2.2.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ed2cf9ed4e8ebc3b754d398cba12f24359f018b416c380f577bbae112ca52fc9"},
|
||||
{file = "numpy-2.2.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39261798d208c3095ae4f7bc8eaeb3481ea8c6e03dc48028057d3cbdbdb8937e"},
|
||||
{file = "numpy-2.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:783145835458e60fa97afac25d511d00a1eca94d4a8f3ace9fe2043003c678e4"},
|
||||
{file = "numpy-2.2.3.tar.gz", hash = "sha256:dbdc15f0c81611925f382dfa97b3bd0bc2c1ce19d4fe50482cb0ddc12ba30020"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.2"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
|
||||
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pandas"
|
||||
version = "2.2.3"
|
||||
description = "Powerful data structures for data analysis, time series, and statistics"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"},
|
||||
{file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"},
|
||||
{file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"},
|
||||
{file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"},
|
||||
{file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"},
|
||||
{file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"},
|
||||
{file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"},
|
||||
{file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"},
|
||||
{file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"},
|
||||
{file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"},
|
||||
{file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"},
|
||||
{file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"},
|
||||
{file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"},
|
||||
{file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"},
|
||||
{file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"},
|
||||
{file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"},
|
||||
{file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"},
|
||||
{file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"},
|
||||
{file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"},
|
||||
{file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"},
|
||||
{file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"},
|
||||
{file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"},
|
||||
{file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"},
|
||||
{file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"},
|
||||
{file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"},
|
||||
{file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"},
|
||||
{file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"},
|
||||
{file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"},
|
||||
{file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"},
|
||||
{file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"},
|
||||
{file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"},
|
||||
{file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"},
|
||||
{file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"},
|
||||
{file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"},
|
||||
{file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"},
|
||||
{file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"},
|
||||
{file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"},
|
||||
{file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"},
|
||||
{file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"},
|
||||
{file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"},
|
||||
{file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"},
|
||||
{file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = {version = ">=1.26.0", markers = "python_version >= \"3.12\""}
|
||||
python-dateutil = ">=2.8.2"
|
||||
pytz = ">=2020.1"
|
||||
tzdata = ">=2022.7"
|
||||
|
||||
[package.extras]
|
||||
all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"]
|
||||
aws = ["s3fs (>=2022.11.0)"]
|
||||
clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"]
|
||||
compression = ["zstandard (>=0.19.0)"]
|
||||
computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"]
|
||||
consortium-standard = ["dataframe-api-compat (>=0.1.7)"]
|
||||
excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"]
|
||||
feather = ["pyarrow (>=10.0.1)"]
|
||||
fss = ["fsspec (>=2022.11.0)"]
|
||||
gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"]
|
||||
hdf5 = ["tables (>=3.8.0)"]
|
||||
html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"]
|
||||
mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"]
|
||||
output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"]
|
||||
parquet = ["pyarrow (>=10.0.1)"]
|
||||
performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"]
|
||||
plot = ["matplotlib (>=3.6.3)"]
|
||||
postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"]
|
||||
pyarrow = ["pyarrow (>=10.0.1)"]
|
||||
spss = ["pyreadstat (>=1.2.0)"]
|
||||
sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"]
|
||||
test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
|
||||
xml = ["lxml (>=4.9.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "pdf2image"
|
||||
version = "1.17.0"
|
||||
description = "A wrapper around the pdftoppm and pdftocairo command line tools to convert PDF to a PIL Image list."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pdf2image-1.17.0-py3-none-any.whl", hash = "sha256:ecdd58d7afb810dffe21ef2b1bbc057ef434dabbac6c33778a38a3f7744a27e2"},
|
||||
{file = "pdf2image-1.17.0.tar.gz", hash = "sha256:eaa959bc116b420dd7ec415fcae49b98100dda3dd18cd2fdfa86d09f112f6d57"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pillow = "*"
|
||||
|
||||
[[package]]
|
||||
name = "peewee"
|
||||
version = "3.17.9"
|
||||
description = "a little orm"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "peewee-3.17.9.tar.gz", hash = "sha256:fe15cd001758e324c8e3ca8c8ed900e7397c2907291789e1efc383e66b9bc7a8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "11.1.0"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a"},
|
||||
{file = "pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761"},
|
||||
{file = "pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf"},
|
||||
{file = "pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c"},
|
||||
{file = "pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe"},
|
||||
{file = "pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-win32.whl", hash = "sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f"},
|
||||
{file = "pillow-11.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a"},
|
||||
{file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90"},
|
||||
{file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb"},
|
||||
{file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442"},
|
||||
{file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83"},
|
||||
{file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f"},
|
||||
{file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73"},
|
||||
{file = "pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0"},
|
||||
{file = "pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
|
||||
fpx = ["olefile"]
|
||||
mic = ["olefile"]
|
||||
tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"]
|
||||
typing = ["typing-extensions"]
|
||||
xmp = ["defusedxml"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.6"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"},
|
||||
{file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
annotated-types = ">=0.6.0"
|
||||
pydantic-core = "2.27.2"
|
||||
typing-extensions = ">=4.12.2"
|
||||
|
||||
[package.extras]
|
||||
email = ["email-validator (>=2.0.0)"]
|
||||
timezone = ["tzdata"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.27.2"
|
||||
description = "Core functionality for Pydantic validation and serialization"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"},
|
||||
{file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "pyopengl"
|
||||
version = "3.1.9"
|
||||
description = "Standard OpenGL bindings for Python"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "PyOpenGL-3.1.9-py3-none-any.whl", hash = "sha256:15995fd3b0deb991376805da36137a4ae5aba6ddbb5e29ac1f35462d130a3f77"},
|
||||
{file = "pyopengl-3.1.9.tar.gz", hash = "sha256:28ebd82c5f4491a418aeca9672dffb3adbe7d33b39eada4548a5b4e8c03f60c8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
description = "Extensions to the standard Python datetime module"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
files = [
|
||||
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
|
||||
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2025.1"
|
||||
description = "World timezone definitions, modern and historical"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"},
|
||||
{file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.2"
|
||||
description = "YAML parser and emitter for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
|
||||
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requirements-parser"
|
||||
version = "0.11.0"
|
||||
description = "This is a small Python module for parsing Pip requirement files."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8"
|
||||
files = [
|
||||
{file = "requirements_parser-0.11.0-py3-none-any.whl", hash = "sha256:50379eb50311834386c2568263ae5225d7b9d0867fb55cf4ecc93959de2c2684"},
|
||||
{file = "requirements_parser-0.11.0.tar.gz", hash = "sha256:35f36dc969d14830bf459803da84f314dc3d17c802592e9e970f63d0359e5920"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
packaging = ">=23.2"
|
||||
types-setuptools = ">=69.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
files = [
|
||||
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
|
||||
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-setuptools"
|
||||
version = "75.8.0.20250225"
|
||||
description = "Typing stubs for setuptools"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "types_setuptools-75.8.0.20250225-py3-none-any.whl", hash = "sha256:94c86b439cc60bcc68c1cda3fd2c301f007f8f9502f4fbb54c66cb5ce9b875af"},
|
||||
{file = "types_setuptools-75.8.0.20250225.tar.gz", hash = "sha256:6038f7e983d55792a5f90d8fdbf5d4c186026214a16bb65dd6ae83c624ae9636"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.1"
|
||||
description = "Provider of IANA time zone data"
|
||||
optional = false
|
||||
python-versions = ">=2"
|
||||
files = [
|
||||
{file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"},
|
||||
{file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzlocal"
|
||||
version = "5.3"
|
||||
description = "tzinfo object for the local timezone"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "tzlocal-5.3-py3-none-any.whl", hash = "sha256:3814135a1bb29763c6e4f08fd6e41dbb435c7a60bfbb03270211bcc537187d8c"},
|
||||
{file = "tzlocal-5.3.tar.gz", hash = "sha256:2fafbfc07e9d8b49ade18f898d6bcd37ae88ce3ad6486842a2e4f03af68323d2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
tzdata = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[package.extras]
|
||||
devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "7112b24c85d31e228e464b5c9621e985ae19504e26e1fa6c7f32d8afbf33b86d"
|
31
pyproject.toml
Normal file
31
pyproject.toml
Normal file
@ -0,0 +1,31 @@
|
||||
[tool.poetry]
|
||||
name = "learnlytics"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["DerGrumpf <p.keier@beyerstedt-it.de>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
flatpak-pip-generator = "^24.0.0"
|
||||
pandas = "^2.2.3"
|
||||
|
||||
[project]
|
||||
package-mode = false
|
||||
|
||||
[project.scripts]
|
||||
main = "learnlytics:main.py"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.12"
|
||||
numpy = "^2.2.3"
|
||||
imgui-bundle = "^1.6.2"
|
||||
peewee = "^3.17.9"
|
||||
colour = "^0.1.5"
|
||||
pytz = "^2025.1"
|
||||
tzlocal = "^5.3"
|
||||
pdf2image = "^1.17.0"
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
Loading…
Reference in New Issue
Block a user