1 Commits

Author SHA1 Message Date
dependabot[bot] 1d89770f31 [UPDATE] Update mkdocstrings-python requirement
Updates the requirements on [mkdocstrings-python](https://github.com/mkdocstrings/python) to permit the latest version.
- [Release notes](https://github.com/mkdocstrings/python/releases)
- [Changelog](https://github.com/mkdocstrings/python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/python/compare/2.0.4...2.0.5)

---
updated-dependencies:
- dependency-name: mkdocstrings-python
  dependency-version: 2.0.5
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-29 08:23:04 +00:00
5 changed files with 55 additions and 110 deletions
@@ -19,8 +19,6 @@ class Command(BaseCommand):
"This may take a few minutes to complete."
)
for ue in client.fetch_ues():
# this is a N+1 query, but it isn't a problem,
# as fetching a UE from the UTBM API is taking most of the time anyway
db_ue = UE.objects.filter(code=ue.code).first()
if db_ue is None:
db_ue = UE(code=ue.code, author=root_user)
+17 -28
View File
@@ -11,16 +11,6 @@ from pydantic.alias_generators import to_camel
from pedagogy.models import UE
class FormationSchema(Schema):
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
code: str
label: str = Field(alias="libelle")
FormationListSchema = TypeAdapter(list[FormationSchema])
class UtbmShortUeSchema(Schema):
"""Short representation of an UE in the UTBM API.
@@ -29,17 +19,19 @@ class UtbmShortUeSchema(Schema):
The UTBM API returns more data than that.
"""
model_config = ConfigDict(validate_by_name=True)
model_config = ConfigDict(alias_generator=to_camel)
code: str
category: str = Field(alias="codeCategorie")
formation: FormationSchema
lang: str = Field(alias="codeLangue")
open_autumn: bool = Field(alias="ouvertAutomne")
open_spring: bool = Field(alias="ouvertPrintemps")
code_formation: str
code_categorie: str | None
code_langue: str
ouvert_automne: bool
ouvert_printemps: bool
class WorkloadSchema(Schema):
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
code: Literal["TD", "TP", "CM", "THE", "TE"]
nbh: int
@@ -56,25 +48,22 @@ class SemesterUeState(Schema):
ShortUeList = TypeAdapter(list[UtbmShortUeSchema])
class SyllabusItemSchema(Schema):
model_config = ConfigDict(validate_by_name=True)
label: str = Field(alias="libelle")
value: str | None = Field(alias="valeur")
class UtbmFullUeSchema(Schema):
"""Long representation of an UE in the UTBM API."""
model_config = ConfigDict(validate_by_name=True, alias_generator=to_camel)
model_config = ConfigDict(alias_generator=to_camel)
code: str
departement: str = "NA"
label: str | None = Field(alias="libelle")
syllabus: list[SyllabusItemSchema] = Field(alias="listeSaisieSyllabus")
lang: str = Field(None, validation_alias=AliasPath("langueEnseignement", "code"))
libelle: str | None
objectifs: str | None
programme: str | None
acquisition_competences: str | None
acquisition_notions: str | None
langue: str
code_langue: str
credits_ects: int
activites: list[WorkloadSchema] = Field(alias="listeActivite")
activites: list[WorkloadSchema]
respo_automne: str | None = Field(
None, validation_alias=AliasPath("automne", "responsable")
)
+36 -78
View File
@@ -5,16 +5,8 @@ from typing import Iterator
import requests
from django.conf import settings
from django.utils.functional import cached_property
from pydantic import TypeAdapter
from pedagogy.schemas import (
FormationSchema,
UeSchema,
UtbmFullUeSchema,
UtbmShortUeSchema,
)
FormationListSchema = TypeAdapter(list[FormationSchema])
from pedagogy.schemas import ShortUeList, UeSchema, UtbmFullUeSchema, UtbmShortUeSchema
class UtbmApiClient(requests.Session):
@@ -26,44 +18,28 @@ class UtbmApiClient(requests.Session):
@cached_property
def current_year(self) -> int:
"""Fetch from the API the latest existing year"""
url = f"{self.BASE_URL}/guide/"
response = self.get(url, {"langue": "fr"})
return max(i["annee"] for i in response.json())
url = f"{self.BASE_URL}/guides/fr"
response = self.get(url)
return response.json()[-1]["annee"]
@cached_property
def formations(self) -> list[FormationSchema]:
response = self.get(
f"{self.BASE_URL}/formation/",
{"langue": "fr", "annee_univ": self.current_year, "typeFormation": "ING"},
)
return FormationListSchema.validate_json(response.text, by_alias=True)
def fetch_short_ues(self, year: int | None = None) -> list[UtbmShortUeSchema]:
def fetch_short_ues(
self, lang: str = "fr", year: int | None = None
) -> list[UtbmShortUeSchema]:
"""Get the list of UEs in their short format from the UTBM API"""
if year is None:
year = self.current_year
if year not in self._cache["short_ues"]:
ues = []
for formation in self.formations:
response = self.get(
f"{self.BASE_URL}/ue/",
{
"langue": "fr",
"annee_univ": year,
"codeFormation": formation.code,
},
)
ues.extend(
[
UtbmShortUeSchema.model_validate({**ue, "formation": formation})
for ue in response.json()
if ue["codeCategorie"] is not None
]
)
self._cache["short_ues"][year] = ues
return self._cache["short_ues"][year]
if lang not in self._cache["short_ues"]:
self._cache["short_ues"][lang] = {}
if year not in self._cache["short_ues"][lang]:
url = f"{self.BASE_URL}/uvs/{lang}/{year}"
response = self.get(url)
ues = ShortUeList.validate_json(response.content)
self._cache["short_ues"][lang][year] = ues
return self._cache["short_ues"][lang][year]
def fetch_ues(self, year: int | None = None) -> Iterator[UeSchema]:
def fetch_ues(
self, lang: str = "fr", year: int | None = None
) -> Iterator[UeSchema]:
"""Fetch all UEs from the UTBM API, parsed in a format that we can use.
Warning:
@@ -76,7 +52,7 @@ class UtbmApiClient(requests.Session):
"""
if year is None:
year = self.current_year
shorts_ues = self.fetch_short_ues(year)
shorts_ues = self.fetch_short_ues(lang, year)
# When UEs are common to multiple branches (like most HUMA)
# the UTBM API duplicates them for every branch.
# We have no way in our db to link a UE to multiple formations,
@@ -89,38 +65,27 @@ class UtbmApiClient(requests.Session):
if ue.code not in unique_short_ues:
unique_short_ues[ue.code] = ue
for ue in unique_short_ues.values():
response = requests.get(
f"{self.BASE_URL}/ue/{ue.code}",
{
"langue": ue.lang,
"annee_univ": year,
"codeFormation": ue.formation.code,
},
)
ue_url = f"{self.BASE_URL}/uv/{lang}/{year}/{ue.code}/{ue.code_formation}"
response = requests.get(ue_url)
full_ue = UtbmFullUeSchema.model_validate_json(response.content)
yield make_clean_ue(ue, full_ue)
def find_ue(self, code: str, year: int | None = None) -> UeSchema | None:
"""Find a UE from the UTBM API."""
def find_uu(self, lang: str, code: str, year: int | None = None) -> UeSchema | None:
"""Find an UE from the UTBM API."""
# query the UE list
if not year:
year = self.current_year
# the UTBM API has no way to fetch a single short ue,
# and short ues contain infos that we need and are not
# in the full ue schema, so we must fetch everything.
short_ues = self.fetch_short_ues(year)
short_ues = self.fetch_short_ues(lang, year)
short_ue = next((ue for ue in short_ues if ue.code == code), None)
if short_ue is None:
return None
# get detailed information about the UE
response = requests.get(
f"{self.BASE_URL}/ue/{code}",
{
"langue": "fr",
"annee_univ": year,
"codeFormation": short_ue.formation.code,
},
)
ue_url = f"{self.BASE_URL}/uv/{lang}/{year}/{code}/{short_ue.code_formation}"
response = requests.get(ue_url)
full_ue = UtbmFullUeSchema.model_validate_json(response.content)
return make_clean_ue(short_ue, full_ue)
@@ -147,9 +112,9 @@ def make_clean_ue(short_ue: UtbmShortUeSchema, full_ue: UtbmFullUeSchema) -> UeS
"ED": "EDIM",
"AI": "GI",
"AM": "MC",
}.get(short_ue.formation.code, "NA")
}.get(short_ue.code_formation, "NA")
match short_ue.open_spring, short_ue.open_autumn:
match short_ue.ouvert_printemps, short_ue.ouvert_automne:
case True, True:
semester = "AUTUMN_AND_SPRING"
case True, False:
@@ -160,11 +125,11 @@ def make_clean_ue(short_ue: UtbmShortUeSchema, full_ue: UtbmFullUeSchema) -> UeS
semester = "CLOSED"
return UeSchema(
title=full_ue.label or "",
title=full_ue.libelle or "",
code=full_ue.code,
credit_type=short_ue.code or "FREE",
credit_type=short_ue.code_categorie or "FREE",
semester=semester,
language=short_ue.lang.upper(),
language=short_ue.code_langue.upper(),
credits=full_ue.credits_ects,
department=department,
hours_THE=next((i.nbh for i in full_ue.activites if i.code == "THE"), 0) // 60,
@@ -173,15 +138,8 @@ def make_clean_ue(short_ue: UtbmShortUeSchema, full_ue: UtbmFullUeSchema) -> UeS
hours_TE=next((i.nbh for i in full_ue.activites if i.code == "TE"), 0) // 60,
hours_CM=next((i.nbh for i in full_ue.activites if i.code == "CM"), 0) // 60,
manager=full_ue.respo_automne or full_ue.respo_printemps or "",
objectives=next(
(i.value for i in full_ue.syllabus if i.label == "Objectifs"), ""
),
program=next((i.value for i in full_ue.syllabus if i.label == "Programme"), ""),
skills=next(
(i.value for i in full_ue.syllabus if i.label == "Notions-clefs"), ""
),
key_concepts=next(
(i.value for i in full_ue.syllabus if i.label == "Acquis d'apprentissage"),
"",
),
objectives=full_ue.objectifs or "",
program=full_ue.programme or "",
skills=full_ue.acquisition_competences or "",
key_concepts=full_ue.acquisition_notions or "",
)
+1 -1
View File
@@ -84,7 +84,7 @@ docs = [
"mkdocs>=1.6.1,<2.0.0",
"mkdocs-material>=9.7.6,<10.0.0",
"mkdocstrings>=1.0.4,<2.0.0",
"mkdocstrings-python>=2.0.4,<3.0.0",
"mkdocstrings-python>=2.0.5,<3.0.0",
"mkdocs-include-markdown-plugin>=7.3.0,<8.0.0",
]
+1 -1
View File
@@ -493,7 +493,7 @@ SITH_LOG_OPERATION_TYPE = [
("REFILLING_DELETION", _("Refilling deletion")),
]
SITH_PEDAGOGY_UTBM_API = "https://api.utbm.fr/offre-formation/public"
SITH_PEDAGOGY_UTBM_API = "https://extranet1.utbm.fr/gpedago/api/guide"
# Defines pagination for cash summary
SITH_COUNTER_CASH_SUMMARY_LENGTH = 50