From 10061cd0c46a7eb032e4e7f5fc4c9c21a2230d3f Mon Sep 17 00:00:00 2001 From: Marioneq Date: Sat, 8 Feb 2025 21:43:06 +0100 Subject: [PATCH] SDK: Move from dataclasses to pydantic, add serialization/deserialization section in docs --- src/sdk/src/README.md | 67 +++++++++++++++++++- src/sdk/src/apis/common/models.py | 8 +-- src/sdk/src/apis/efeb/client.py | 9 ++- src/sdk/src/apis/hebe/certificate.py | 7 +- src/sdk/src/apis/hebe/student.py | 9 +-- src/sdk/src/apis/prometheus_web/client.py | 3 +- src/sdk/src/interfaces/core/interface.py | 4 ++ src/sdk/src/interfaces/prometheus/context.py | 13 ++-- src/sdk/src/models/exam.py | 6 +- src/sdk/src/models/grade.py | 6 +- src/sdk/src/models/note.py | 5 +- src/sdk/src/models/student.py | 9 ++- 12 files changed, 100 insertions(+), 46 deletions(-) diff --git a/src/sdk/src/README.md b/src/sdk/src/README.md index 906103a..18c59d4 100644 --- a/src/sdk/src/README.md +++ b/src/sdk/src/README.md @@ -92,6 +92,70 @@ No możesz pobrać na przykład oceny, możesz to zrobić (gdy jesteś zalogowan grades = interface.get_grades() ``` +### Serializacja i wczytywanie danych + +SDK używa modeli pydanic, nie są to zwykłe dict'y czy JSON. + +#### Dict + +`Model -> dict` + +```py +model.model_dump() +``` + +`dict -> model` + +```py +model.model_validate() +``` + +#### Lista dictów + +`list[Model] -> list[dict]` + +```py +ModelList = pydantic.TypeAdapter(list[Model]) +lista_dictow = ModelList.dump_python(lista_modeli) +``` + +`list[dict] -> list[Model]` + +```py +ModelList = pydantic.TypeAdapter(list[Model]) +lista_modeli = ModelList.validate_python(lista_dictow) +``` + +#### JSON (pojedyńczy obiekt) + +`Model -> str` + +```py +Model.model_dump_json() +``` + +`str -> model` + +```py +Model.model_validate_json() +``` + +#### JSON (lista) + +`list[Model] -> str` + +```py +ModelList = pydantic.TypeAdapter(list[Model]) +json_ = ModelList.dump_json(lista_modeli) +``` + +`str -> list[Model]` + +```py +ModelList = pydantic.TypeAdapter(list[Model]) +lista_modeli = ModelList.validate_json(json_) +``` + ## Jak dodawać funkcjonalności: 1. Zrób model(e) do danych, nie dawaj zbyt dużo danych, bo pamiętaj, że ma być to uniwersalny model dla wielu dzienników. @@ -122,8 +186,7 @@ class ExamType(Enum): return ExamType.OTHER -@dataclass -class Exam: +class Exam(BaseModel): deadline: date subject: str type: ExamType diff --git a/src/sdk/src/apis/common/models.py b/src/sdk/src/apis/common/models.py index b1ba47f..7ba5773 100644 --- a/src/sdk/src/apis/common/models.py +++ b/src/sdk/src/apis/common/models.py @@ -1,15 +1,13 @@ -from dataclasses import dataclass +from pydantic import BaseModel -@dataclass -class FsLsQuery: +class FsLsQuery(BaseModel): wa: str wtrealm: str wctx: str -@dataclass -class FsLsResponse: +class FsLsResponse(BaseModel): wa: str wresult: str wctx: str diff --git a/src/sdk/src/apis/efeb/client.py b/src/sdk/src/apis/efeb/client.py index ca1e2bb..9e2da5c 100644 --- a/src/sdk/src/apis/efeb/client.py +++ b/src/sdk/src/apis/efeb/client.py @@ -1,4 +1,3 @@ -import dataclasses import requests from sdk.src.apis.common.models import FsLsResponse, FsLsQuery @@ -33,9 +32,9 @@ class EfebClient: method="POST" if prometheus_response else "GET", url=f"{BASE_LOGIN}/{self._symbol}/{ENDPOINT_LOGIN_FS_LS}", data=( - dataclasses.asdict(prometheus_response) if prometheus_response else None + dict(prometheus_response) if prometheus_response else None ), - params=dataclasses.asdict(query), + params=dict(query), ) return parse_fs_ls_response_form(response.text) @@ -43,7 +42,7 @@ class EfebClient: response = self._session.request( method="POST" if login_response else "GET", url=f"{BASE_STUDENT_CE if self._is_ce else BASE_STUDENT}/{self._symbol}/{ENDPOINT_STUDENT_APP}", - data=dataclasses.asdict(login_response) if login_response else None, + data=dict(login_response) if login_response else None, ) return parse_app_html(response.text) @@ -51,6 +50,6 @@ class EfebClient: response = self._session.request( method="POST" if login_response else "GET", url=f"{BASE_MESSAGES_CE if self._is_ce else BASE_MESSAGES}/{self._symbol}/{ENDPOINT_MESSAGES_APP}", - data=dataclasses.asdict(login_response) if login_response else None, + data=dict(login_response) if login_response else None, ) return parse_app_html(response.text) diff --git a/src/sdk/src/apis/hebe/certificate.py b/src/sdk/src/apis/hebe/certificate.py index 9ab34ec..96c702c 100644 --- a/src/sdk/src/apis/hebe/certificate.py +++ b/src/sdk/src/apis/hebe/certificate.py @@ -1,9 +1,8 @@ -from dataclasses import dataclass +from pydantic import BaseModel from sdk.src.apis.hebe.signer import generate_key_pair -@dataclass -class Certificate: +class Certificate(BaseModel): certificate: str fingerprint: str private_key: str @@ -12,4 +11,4 @@ class Certificate: @staticmethod def generate(): certificate, fingerprint, private_key = generate_key_pair() - return Certificate(certificate, fingerprint, private_key, "X509") + return Certificate(certificate=certificate, fingerprint=fingerprint, private_key=private_key, type="X509") diff --git a/src/sdk/src/apis/hebe/student.py b/src/sdk/src/apis/hebe/student.py index 73721d4..e3d1b00 100644 --- a/src/sdk/src/apis/hebe/student.py +++ b/src/sdk/src/apis/hebe/student.py @@ -1,9 +1,7 @@ -from dataclasses import dataclass from datetime import date +from pydantic import BaseModel - -@dataclass -class HebePeriod: +class HebePeriod(BaseModel): id: int number: int current: bool @@ -21,8 +19,7 @@ class HebePeriod: ) -@dataclass -class HebeStudent: +class HebeStudent(BaseModel): id: int full_name: str unit_id: int diff --git a/src/sdk/src/apis/prometheus_web/client.py b/src/sdk/src/apis/prometheus_web/client.py index 5bba668..687bd2e 100644 --- a/src/sdk/src/apis/prometheus_web/client.py +++ b/src/sdk/src/apis/prometheus_web/client.py @@ -1,4 +1,3 @@ -import dataclasses import json from bs4 import BeautifulSoup import requests @@ -97,7 +96,7 @@ class PrometheusWebClient: def fs_ls(self, query: FsLsQuery): response = self._session.get( f"{PROMETHEUS_WEB_BASE}/{ENDPOINT_FS_LS}", - params=dataclasses.asdict(query), + params=dict(query), ) if "Logowanie" in response.text: diff --git a/src/sdk/src/interfaces/core/interface.py b/src/sdk/src/interfaces/core/interface.py index 03bdb78..89bed18 100644 --- a/src/sdk/src/interfaces/core/interface.py +++ b/src/sdk/src/interfaces/core/interface.py @@ -1,6 +1,7 @@ from datetime import date from sdk.src.models.exam import Exam from sdk.src.models.grade import Grade +from sdk.src.models.note import Note from sdk.src.models.student import Student @@ -22,3 +23,6 @@ class CoreInterface: def get_exams(from_: date, to: date) -> list[Exam]: pass + + def get_notes() -> list[Note]: + pass diff --git a/src/sdk/src/interfaces/prometheus/context.py b/src/sdk/src/interfaces/prometheus/context.py index af40590..422bdae 100644 --- a/src/sdk/src/interfaces/prometheus/context.py +++ b/src/sdk/src/interfaces/prometheus/context.py @@ -1,12 +1,11 @@ -from dataclasses import dataclass +from pydantic import BaseModel from sdk.src.apis.hebe import HebeCertificate from sdk.src.apis.hebe.student import HebeStudent from sdk.src.interfaces.prometheus.utils import get_context_periods_from_hebe_periods -@dataclass -class PrometheusStudentContext: +class PrometheusStudentContext(BaseModel): student_id: int unit_id: int constituent_id: int @@ -32,16 +31,14 @@ class PrometheusStudentContext: ) -@dataclass -class PrometheusWebCredentials: +class PrometheusWebCredentials(BaseModel): username: str password: str -@dataclass -class PrometheusAuthContext: +class PrometheusAuthContext(BaseModel): prometheus_web_credentials: PrometheusWebCredentials symbols: list[str] | None = None hebe_certificate: HebeCertificate | None = None prometheus_web_cookies: dict[str, str] | None = None - efeb_web_cookies: dict[str, str | None] | None = None + efeb_web_cookies: dict[str, dict[str, str] | None] | None = None diff --git a/src/sdk/src/models/exam.py b/src/sdk/src/models/exam.py index e892aa2..f1ad1e5 100644 --- a/src/sdk/src/models/exam.py +++ b/src/sdk/src/models/exam.py @@ -1,7 +1,8 @@ -from dataclasses import dataclass from datetime import date, datetime from enum import Enum +from pydantic import BaseModel + class ExamType(Enum): TEST = 0 @@ -22,8 +23,7 @@ class ExamType(Enum): return ExamType.OTHER -@dataclass -class Exam: +class Exam(BaseModel): deadline: date subject: str type: ExamType diff --git a/src/sdk/src/models/grade.py b/src/sdk/src/models/grade.py index 8f5e144..a7d9b6a 100644 --- a/src/sdk/src/models/grade.py +++ b/src/sdk/src/models/grade.py @@ -1,9 +1,9 @@ -from dataclasses import dataclass from datetime import datetime +from pydantic import BaseModel -@dataclass -class Grade: + +class Grade(BaseModel): value: str is_point: bool point_numerator: int diff --git a/src/sdk/src/models/note.py b/src/sdk/src/models/note.py index 0e69a6f..5671a6c 100644 --- a/src/sdk/src/models/note.py +++ b/src/sdk/src/models/note.py @@ -1,9 +1,8 @@ -from dataclasses import dataclass from datetime import datetime +from pydantic import BaseModel -@dataclass -class Note: +class Note(BaseModel): name: str | None content: str points: str | None diff --git a/src/sdk/src/models/student.py b/src/sdk/src/models/student.py index e5e26cb..e4889b6 100644 --- a/src/sdk/src/models/student.py +++ b/src/sdk/src/models/student.py @@ -1,12 +1,12 @@ -from dataclasses import dataclass from datetime import date from typing import Generic, TypeVar +from pydantic import BaseModel + from sdk.src.apis.hebe.student import HebePeriod, HebeStudent -@dataclass -class Period: +class Period(BaseModel): id: int number: int current: bool @@ -27,8 +27,7 @@ class Period: T = TypeVar("T") -@dataclass -class Student(Generic[T]): +class Student(BaseModel, Generic[T]): full_name: str is_parent: bool class_name: str