This commit is contained in:
Hartmut Seichter 2025-03-18 19:11:56 +01:00
parent 2e1490d98f
commit 1d00963035
4 changed files with 411 additions and 361 deletions

View file

@ -10,10 +10,6 @@ import yaml
from coursebuilder.schema import Schema from coursebuilder.schema import Schema
class LanguageSelector:
context_lang: str
class Course: class Course:
i18n_name: str = "lang.{}.yaml" i18n_name: str = "lang.{}.yaml"
mod_name: str = "mod.yaml" mod_name: str = "mod.yaml"
@ -28,19 +24,19 @@ class Course:
for p in path.parent.glob(Course.i18n_name.format("*")) for p in path.parent.glob(Course.i18n_name.format("*"))
} }
def validate(self, *, schema: Schema, lang: str) -> None:
print(self.__data)
pass
def __getitem__(self, name: str, /) -> Any: def __getitem__(self, name: str, /) -> Any:
return ( """this overload magic function takes a key and a language code like my_key.en or my_key.de"""
self.__data[name] s = name.split(".")
if name in self.__data.keys()
else self.__i18n[LanguageSelector.context_lang][name]
)
def __getattr__(self, name: str, /) -> Any: if len(s) != 2:
return self.__data[name] raise ValueError(
"query with item selector requires form ['key.lang'] alternative is ['key.*']"
)
return (
self.__i18n[s[-1]][s[0]]
if s[-1] in self.__i18n.keys() and s[0] in self.__i18n[s[-1]].keys()
else self.__data[s[0]]
)
def __str__(self): def __str__(self):
return f"data:{self.__data}\ni18n:{self.__i18n}" return f"data:{self.__data}\ni18n:{self.__i18n}"
@ -83,22 +79,52 @@ def main():
type=str, type=str,
help="schema to validate against", help="schema to validate against",
) )
parser.add_argument(
"-f",
"--fields",
help="Fields to be used, the table will be build accordingly",
action="extend",
nargs="+",
type=str,
)
parser.add_argument(
"-c",
"--create",
help="Fields to be used, the table will be build accordingly",
type=str,
)
# get arguments # get arguments
args = parser.parse_args() args = parser.parse_args()
# just input # with input
if args.input and args.schema: if args.input and args.schema:
with open(args.schema) as f_schema: with open(args.schema) as f_schema:
schema = Schema(schema=yaml.load(f_schema, Loader=yaml.Loader)) schema = Schema(schema=yaml.load(f_schema, Loader=yaml.Loader)) # Schema
sc = StudyCourse(path=(Path(".") / args.input).absolute()) sc = StudyCourse(path=(Path(".") / args.input).absolute()) # Database
LanguageSelector.context_lang = "de" actual_fields = args.fields if args.fields else schema.keys()
for k in schema.keys(): for field in actual_fields:
print(k) for lang in sc.languages:
for shortcode, course in sc.courses.items(): for shortcode, course in sc.courses.items():
print(course[k]) print(
field,
"@",
shortcode,
".",
lang,
"\n",
course[f"{field}.{lang}"],
)
elif args.schema and args.create:
schema = Schema(
schema=yaml.load(open(args.schema), Loader=yaml.Loader)
) # Schema
print(schema.fields())
print(schema.types())
print(schema.facets())
# run as main # run as main

View file

@ -1,142 +1,159 @@
class Schema: class Schema:
__yes_vals: list[str] = ["true", "yes", "on"]
__unique: str = "unique"
__spec: str = "spec"
__facets: str = "facets"
__fields: str = "fields"
__type: str = "type"
__label: str = "label"
def __init__(self, *, schema: dict) -> None: def __init__(self, *, schema: dict) -> None:
self.__schema = schema self.__schema = schema
def __getitem__(self, field): def facets(self) -> list[str]:
return self.__schema[field] return list(self.__schema[Schema.__facets])
def keys(self): def fields(self) -> list[str]:
return self.__schema.keys() return list(self.__schema[Schema.__fields].keys())
def is_translatable(self, field): def types(self) -> dict:
if "translatable" in self.__schema[field]: return {
return self.__schema[field]["translatable"] field: a[Schema.__type]
else: for field, a in self.__schema[Schema.__fields].items()
return True }
def needs_spec(self, field): def is_unique(self, field: str) -> bool:
if "spec" in self.__schema[field]:
return self.__schema[field]
else:
return False
def get_value(self, meta: dict, field: str, lang: str):
"""
treats receiving the value like a variant,
returns values with their language specific representations
"""
match self.__schema[field]["type"]:
case "str":
return ( return (
meta[field][lang] Schema.__yes_vals in self.__schema[field][Schema.__unique].lower()
if self.is_translatable(field) if Schema.__unique in self.__schema[field].keys()
else meta[field]["value"] else True
) )
case "enum" | "int" | "num" | "multikey":
return meta[field]["value"] def needs_spec(self, field: str) -> bool:
case "multinum":
return ( return (
meta[field]["value"] Schema.__yes_vals in self.__schema[field][Schema.__spec].lower()
if hasattr(meta[field]["value"], "__iter__") if Schema.__spec in self.__schema[field].keys()
else [ else False
meta[field]["value"],
]
) # force list!
def to_list_of_dict(self, meta, fields, lang):
"""
generates a list of dict which can easily be converted
to a pandas dataframe
"""
# list comprehension for rows
return [
{
"field": field, # field name
"lang": lang, # language shortcode
"type": self.__schema[field]["type"], # datatype
"label": self.__schema[field]["label"][lang], # label
"value": self.get_value(meta, field, lang), # actual value
"template": self.__schema[field]["template"][lang]
if "template" in self.__schema[field]
else None,
# getting crazy with nested dict comprehension
"enum_values": {
k: v[lang] for (k, v) in self.__schema[field]["values"].items()
}
if "enum" in self.__schema[field]["type"]
else None,
"key_values": {
k: v[lang] for (k, v) in self.__schema[field]["keys"].items()
}
if "multikey" in self.__schema[field]["type"]
else None,
"spec": meta[field]["spec"][lang] if "spec" in meta[field] else None,
}
for field in fields
]
def to_short_dict(self, meta, fields, lang):
"""
generates a short version of dict which can easily be converted
to a pandas dataframe
"""
# dict comprehension for whole meta part
return {field: self.get_value(meta, field, lang) for field in fields}
def to_list_of_tuple(self, meta, fields, lang):
"""
generates a list of tuples with a label and value (text)
this is usually consumed by a Markdown generator
todo: needs deuglyfication of free standing loop, templates are possible for all
"""
list = []
for r in self.to_list_of_dict(meta, fields, lang):
match r["type"]:
case "str":
list.append((r["label"], r["value"]))
case "int" | "num":
list.append(
(
r["label"],
r["template"].format(value=r["value"], spec=r["spec"])
if r["template"]
else r["value"],
)
)
case "enum":
list.append(
(
r["label"],
r["template"].format(
value=r["enum_values"][r["value"]], spec=r["spec"]
)
if r["template"]
else r["enum_values"][r["value"]],
)
)
case "multikey":
list.append(
(
r["label"],
", ".join(
[
r["template"].format(
key=r["key_values"][k], value=v
)
for k, v in r["value"].items()
]
),
)
)
case "multinum":
list.append(
(
r["label"],
", ".join(
r["template"].format(value=v) for v in r["value"]
),
)
) )
return list # def get_value(self, meta: dict, field: str, lang: str):
# """
# treats receiving the value like a variant,
# returns values with their language specific representations
# """
# match self.__schema[field]["type"]:
# case "str":
# return (
# meta[field][lang]
# if self.is_translatable(field)
# else meta[field]["value"]
# )
# case "enum" | "int" | "num" | "multikey":
# return meta[field]["value"]
# case "multinum":
# return (
# meta[field]["value"]
# if hasattr(meta[field]["value"], "__iter__")
# else [
# meta[field]["value"],
# ]
# ) # force list!
# def to_list_of_dict(self, meta, fields, lang):
# """
# generates a list of dict which can easily be converted
# to a pandas dataframe
# """
# # list comprehension for rows
# return [
# {
# "field": field, # field name
# "lang": lang, # language shortcode
# "type": self.__schema[field]["type"], # datatype
# "label": self.__schema[field]["label"][lang], # label
# "value": self.get_value(meta, field, lang), # actual value
# "template": self.__schema[field]["template"][lang]
# if "template" in self.__schema[field]
# else None,
# # getting crazy with nested dict comprehension
# "enum_values": {
# k: v[lang] for (k, v) in self.__schema[field]["values"].items()
# }
# if "enum" in self.__schema[field]["type"]
# else None,
# "key_values": {
# k: v[lang] for (k, v) in self.__schema[field]["keys"].items()
# }
# if "multikey" in self.__schema[field]["type"]
# else None,
# "spec": meta[field]["spec"][lang] if "spec" in meta[field] else None,
# }
# for field in fields
# ]
# def to_short_dict(self, meta, fields, lang):
# """
# generates a short version of dict which can easily be converted
# to a pandas dataframe
# """
# # dict comprehension for whole meta part
# return {field: self.get_value(meta, field, lang) for field in fields}
# def to_list_of_tuple(self, meta, fields, lang):
# """
# generates a list of tuples with a label and value (text)
# this is usually consumed by a Markdown generator
# todo: needs deuglyfication of free standing loop, templates are possible for all
# """
# list = []
# for r in self.to_list_of_dict(meta, fields, lang):
# match r["type"]:
# case "str":
# list.append((r["label"], r["value"]))
# case "int" | "num":
# list.append(
# (
# r["label"],
# r["template"].format(value=r["value"], spec=r["spec"])
# if r["template"]
# else r["value"],
# )
# )
# case "enum":
# list.append(
# (
# r["label"],
# r["template"].format(
# value=r["enum_values"][r["value"]], spec=r["spec"]
# )
# if r["template"]
# else r["enum_values"][r["value"]],
# )
# )
# case "multikey":
# list.append(
# (
# r["label"],
# ", ".join(
# [
# r["template"].format(
# key=r["key_values"][k], value=v
# )
# for k, v in r["value"].items()
# ]
# ),
# )
# )
# case "multinum":
# list.append(
# (
# r["label"],
# ", ".join(
# r["template"].format(value=v) for v in r["value"]
# ),
# )
# )
# return list

View file

@ -13,8 +13,7 @@ credits: 5
form-of-exam: form-of-exam:
type: alternative type: alternative
duration: duration: 1
value: 1
author-of-indenture: author-of-indenture:

View file

@ -1,114 +1,122 @@
# fields in curricular description facets: ["de", "en"]
# leaning on methods in OpenAPI 3.0
# templates:
# Modulname name:
# de: Modulname {value}
name: en: name of course {value}
fields:
#
# Kürzel / ID
#
id:
type: str
translatable: false
label: { de: "Kürzel", en: "code" }
#
# Modulname
#
name:
type: str type: str
label: label:
de: "Modulname" de: "Modulname"
en: "name of course" en: "name of course"
# #
# Modulverantwortliche:r # Modulverantwortliche:r
# #
instructor: instructor:
type: str type: str
label: label:
de: "Modulverantwortlicher / Modulverantwortliche" de: "Modulverantwortlicher / Modulverantwortliche"
en: "module instructor" en: "module instructor"
# #
# Kürzel / ID # Qualifikationsziele
# #
id:
type: str
translatable: false
label: { de: "Kürzel", en: "code" }
# # Welche fachbezogenen, methodischen, fachübergreifende Kompetenzen,
# Qualifikationsziele # Schlüsselqualifikationen - werden erzielt (erworben)? Diese sind
# # an der zu definierenden Gesamtqualifikation (angestrebter Abschluss) auszurichten.
#
# Lernergebnisse sind Aussagen darüber, was ein Studierender nach Abschluss des Moduls weiß,
# versteht und in der Lage ist zu tun. Die Formulierung sollte sich am Qualifikationsrahmen
# für Deutsche Hochschulabschlüsse orientieren und Inhaltswiederholungen vermeiden.
#
# Des Weiteren finden Sie im QM-Portal die „Handreichung zur Beschreibung von Lernzielen“
# als Formulierungshilfe.
# Welche fachbezogenen, methodischen, fachübergreifende Kompetenzen, goal:
# Schlüsselqualifikationen - werden erzielt (erworben)? Diese sind
# an der zu definierenden Gesamtqualifikation (angestrebter Abschluss) auszurichten.
#
# Lernergebnisse sind Aussagen darüber, was ein Studierender nach Abschluss des Moduls weiß,
# versteht und in der Lage ist zu tun. Die Formulierung sollte sich am Qualifikationsrahmen
# für Deutsche Hochschulabschlüsse orientieren und Inhaltswiederholungen vermeiden.
#
# Des Weiteren finden Sie im QM-Portal die „Handreichung zur Beschreibung von Lernzielen“
# als Formulierungshilfe.
goal:
type: str type: str
label: { de: "Qualifikationsziele", en: "educational goal" } label: { de: "Qualifikationsziele", en: "educational goal" }
# #
# Modulinhalte # Modulinhalte
# #
# Welche fachlichen, methodischen, fachpraktischen und fächerübergreifenden # Welche fachlichen, methodischen, fachpraktischen und fächerübergreifenden
# Inhalte sollen vermittelt werden? # Inhalte sollen vermittelt werden?
# #
# Es ist ein stichpunktartiges Inhaltsverzeichnis zu erstellen. # Es ist ein stichpunktartiges Inhaltsverzeichnis zu erstellen.
content: content:
type: str type: str
label: { de: "Modulinhalte", en: "content" } label: { de: "Modulinhalte", en: "content" }
# #
# Lehrform # Lehrform
# #
# #
# Welche Lehr- und Lernformen werden angewendet? # Welche Lehr- und Lernformen werden angewendet?
# (Vorlesungen, Übungen, Seminare, Praktika, # (Vorlesungen, Übungen, Seminare, Praktika,
# Projektarbeit, Selbststudium) # Projektarbeit, Selbststudium)
# #
# Es sind nur Werte aus der Prüfungsordung zugelassen # Es sind nur Werte aus der Prüfungsordung zugelassen
# #
form-of-instruction: form-of-instruction:
type: multikey type: multikey
label: { de: "Lehrform(en)", en: "form of instruction" } label: { de: "Lehrform(en)", en: "form of instruction" }
keys: keys:
{ {
"lecture": { de: "Vorlesung", en: "lecture" }, "lecture": { de: "Vorlesung", en: "lecture" },
"lecture_seminar": "lecture_seminar":
{ de: "Seminaristische Vorlesung", en: "lecture and seminar" }, {
de: "Seminaristische Vorlesung",
en: "lecture and seminar",
},
"seminar": { de: "Seminar", en: "seminar" }, "seminar": { de: "Seminar", en: "seminar" },
"exersise": { de: "Übung", en: "lab exersise" }, "exersise": { de: "Übung", en: "lab exersise" },
"pc_lab": { de: "Rechnergestütztes Praktikum", en: "PC exersise" }, "pc_lab":
{ de: "Rechnergestütztes Praktikum", en: "PC exersise" },
"project": { de: "Project", en: "project" }, "project": { de: "Project", en: "project" },
} }
template: template:
de: "{key} ({value}SWS)" de: "{key} ({value}SWS)"
en: "{key} ({value}SWS)" en: "{key} ({value}SWS)"
# #
# Voraussetzungen für die Teilnahme # Voraussetzungen für die Teilnahme
# #
# Für jedes Modul sind die Voraussetzungen für die Teilnahme zu beschreiben. # Für jedes Modul sind die Voraussetzungen für die Teilnahme zu beschreiben.
# Welche Kenntnisse, Fähigkeiten und Fertigkeiten sind für eine # Welche Kenntnisse, Fähigkeiten und Fertigkeiten sind für eine
# erfolgreiche Teilnahme vorauszusetzen? # erfolgreiche Teilnahme vorauszusetzen?
# #
# Alternativ können die Module benannt werden welche für die erfolgreiche # Alternativ können die Module benannt werden welche für die erfolgreiche
# Teilnahme im Vorfeld zu belegen sind. # Teilnahme im Vorfeld zu belegen sind.
prerequisites: prerequisites:
type: str type: str
label: { de: "Voraussetzungen für die Teilnahme", en: "prerequisites" } label: { de: "Voraussetzungen für die Teilnahme", en: "prerequisites" }
# #
# Literatur und multimediale Lehr- und Lernprogramme # Literatur und multimediale Lehr- und Lernprogramme
# #
# #
# Wie können die Studierenden sich auf die Teilnahme an diesem Modul vorbereiten? # Wie können die Studierenden sich auf die Teilnahme an diesem Modul vorbereiten?
# #
teaching-material: teaching-material:
type: str type: str
label: label:
{ {
@ -116,30 +124,30 @@ teaching-material:
en: "media of instruction", en: "media of instruction",
} }
# #
# Lehrbriefautor # Lehrbriefautor
# #
author-of-indenture: author-of-indenture:
type: str type: str
label: { de: "Lehrbriefautor", en: "author of indenture" } label: { de: "Lehrbriefautor", en: "author of indenture" }
# #
# Verwendung in (Studienprogramm) # Verwendung in (Studienprogramm)
# #
# used-in: # used-in:
# type: str # type: str
# label: { de: "Verwendung", en: "used in study programs" } # label: { de: "Verwendung", en: "used in study programs" }
# #
# Arbeitsaufwand # Arbeitsaufwand
# #
workload: workload:
type: str type: str
label: { de: "Arbeitsaufwand / Gesamtworkload", en: "workload" } label: { de: "Arbeitsaufwand / Gesamtworkload", en: "workload" }
# #
# credits/ECTS # credits/ECTS
# #
credits: credits:
type: num type: num
unit: ECTS unit: ECTS
label: label:
@ -151,10 +159,10 @@ credits:
de: "{value}CP, Gewichtung: {value}CP von 120CP " de: "{value}CP, Gewichtung: {value}CP von 120CP "
en: "{value}CP, weight: {value} / 120 " en: "{value}CP, weight: {value} / 120 "
# #
# Leistungsnachweis # Leistungsnachweis
# #
form-of-exam: form-of-exam:
type: enum type: enum
label: { de: "Leistungsnachweis", en: "form of examination" } label: { de: "Leistungsnachweis", en: "form of examination" }
values: values:
@ -172,60 +180,60 @@ form-of-exam:
de: "{value} ({spec})" de: "{value} ({spec})"
en: "{value} ({spec})" en: "{value} ({spec})"
# #
# Semester # Semester
# #
# term: # term:
# type: multinum # type: multinum
# label: { de: "Semester", en: "term" } # label: { de: "Semester", en: "term" }
# template: # template:
# de: "{value}\\. Semester" # de: "{value}\\. Semester"
# en: "{value}\\. semester" # en: "{value}\\. semester"
# #
# Häufigkeit des Angebots # Häufigkeit des Angebots
# #
# frequency: # frequency:
# type: enum # type: enum
# label: { de: "Häufigkeit des Angebots", en: "frequency of Offer" } # label: { de: "Häufigkeit des Angebots", en: "frequency of Offer" }
# values: # values:
# { # {
# "once_per_term": { de: "jedes Semester", en: "every semester" }, # "once_per_term": { de: "jedes Semester", en: "every semester" },
# "once_per_year": # "once_per_year":
# { de: "einmal im Studienjahr", en: "once per study year" }, # { de: "einmal im Studienjahr", en: "once per study year" },
# } # }
# #
# Dauer des Angebots # Dauer des Angebots
# #
# duration: # duration:
# type: int # type: int
# label: # label:
# de: Dauer # de: Dauer
# en: duration # en: duration
# template: # template:
# de: "{value} Semester" # de: "{value} Semester"
# en: "{value} term(s)" # en: "{value} term(s)"
# #
# Art der Veranstaltung # Art der Veranstaltung
# #
kind: # kind:
type: enum # type: enum
label: # label:
{ # {
de: "Art der Veranstaltung (Pflicht, Wahl, etc.)", # de: "Art der Veranstaltung (Pflicht, Wahl, etc.)",
en: "kind of module (compulsory, elective)", # en: "kind of module (compulsory, elective)",
} # }
values: # values:
{ # {
"compulsory": { de: "Pflicht", en: "compulsory" }, # "compulsory": { de: "Pflicht", en: "compulsory" },
"elective": { de: "Wahl/Wahlpflicht", en: "elective" }, # "elective": { de: "Wahl/Wahlpflicht", en: "elective" },
} # }
# #
# Freiform Bemerkungen # Freiform Bemerkungen
# #
remarks: remarks:
type: str type: str
label: { de: "Besonderes", en: "remarks" } label: { de: "Besonderes", en: "remarks" }