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 (
return self.__schema[field] Schema.__yes_vals in self.__schema[field][Schema.__unique].lower()
else: if Schema.__unique in self.__schema[field].keys()
return False else True
)
def get_value(self, meta: dict, field: str, lang: str): def needs_spec(self, field: str) -> bool:
""" return (
treats receiving the value like a variant, Schema.__yes_vals in self.__schema[field][Schema.__spec].lower()
returns values with their language specific representations if Schema.__spec in self.__schema[field].keys()
""" else False
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): # def get_value(self, meta: dict, field: str, lang: str):
""" # """
generates a list of dict which can easily be converted # treats receiving the value like a variant,
to a pandas dataframe # returns values with their language specific representations
""" # """
# list comprehension for rows # match self.__schema[field]["type"]:
return [ # case "str":
{ # return (
"field": field, # field name # meta[field][lang]
"lang": lang, # language shortcode # if self.is_translatable(field)
"type": self.__schema[field]["type"], # datatype # else meta[field]["value"]
"label": self.__schema[field]["label"][lang], # label # )
"value": self.get_value(meta, field, lang), # actual value # case "enum" | "int" | "num" | "multikey":
"template": self.__schema[field]["template"][lang] # return meta[field]["value"]
if "template" in self.__schema[field] # case "multinum":
else None, # return (
# getting crazy with nested dict comprehension # meta[field]["value"]
"enum_values": { # if hasattr(meta[field]["value"], "__iter__")
k: v[lang] for (k, v) in self.__schema[field]["values"].items() # else [
} # meta[field]["value"],
if "enum" in self.__schema[field]["type"] # ]
else None, # ) # force list!
"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): # def to_list_of_dict(self, meta, fields, lang):
""" # """
generates a short version of dict which can easily be converted # generates a list of dict which can easily be converted
to a pandas dataframe # to a pandas dataframe
""" # """
# dict comprehension for whole meta part # # list comprehension for rows
return {field: self.get_value(meta, field, lang) for field in fields} # 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_list_of_tuple(self, meta, fields, lang): # def to_short_dict(self, meta, fields, lang):
""" # """
generates a list of tuples with a label and value (text) # generates a short version of dict which can easily be converted
this is usually consumed by a Markdown generator # to a pandas dataframe
# """
# # dict comprehension for whole meta part
# return {field: self.get_value(meta, field, lang) for field in fields}
todo: needs deuglyfication of free standing loop, templates are possible for all # def to_list_of_tuple(self, meta, fields, lang):
""" # """
list = [] # generates a list of tuples with a label and value (text)
for r in self.to_list_of_dict(meta, fields, lang): # this is usually consumed by a Markdown generator
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 # 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,231 +1,239 @@
# 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}
type: str
label:
de: "Modulname"
en: "name of course"
# fields:
# Modulverantwortliche:r #
# # Kürzel / ID
instructor: #
type: str id:
label: type: str
de: "Modulverantwortlicher / Modulverantwortliche" translatable: false
en: "module instructor" label: { de: "Kürzel", en: "code" }
#
# Modulname
#
name:
type: str
label:
de: "Modulname"
en: "name of course"
# #
# Kürzel / ID # Modulverantwortliche:r
# #
id: instructor:
type: str type: str
translatable: false label:
label: { de: "Kürzel", en: "code" } de: "Modulverantwortlicher / Modulverantwortliche"
en: "module instructor"
# #
# Qualifikationsziele # Qualifikationsziele
# #
# Welche fachbezogenen, methodischen, fachübergreifende Kompetenzen, # Welche fachbezogenen, methodischen, fachübergreifende Kompetenzen,
# Schlüsselqualifikationen - werden erzielt (erworben)? Diese sind # Schlüsselqualifikationen - werden erzielt (erworben)? Diese sind
# an der zu definierenden Gesamtqualifikation (angestrebter Abschluss) auszurichten. # an der zu definierenden Gesamtqualifikation (angestrebter Abschluss) auszurichten.
# #
# Lernergebnisse sind Aussagen darüber, was ein Studierender nach Abschluss des Moduls weiß, # 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 # versteht und in der Lage ist zu tun. Die Formulierung sollte sich am Qualifikationsrahmen
# für Deutsche Hochschulabschlüsse orientieren und Inhaltswiederholungen vermeiden. # für Deutsche Hochschulabschlüsse orientieren und Inhaltswiederholungen vermeiden.
# #
# Des Weiteren finden Sie im QM-Portal die „Handreichung zur Beschreibung von Lernzielen“ # Des Weiteren finden Sie im QM-Portal die „Handreichung zur Beschreibung von Lernzielen“
# als Formulierungshilfe. # als Formulierungshilfe.
goal: 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" }, {
"seminar": { de: "Seminar", en: "seminar" }, de: "Seminaristische Vorlesung",
"exersise": { de: "Übung", en: "lab exersise" }, en: "lecture and seminar",
"pc_lab": { de: "Rechnergestütztes Praktikum", en: "PC exersise" }, },
"project": { de: "Project", en: "project" }, "seminar": { de: "Seminar", en: "seminar" },
} "exersise": { de: "Übung", en: "lab exersise" },
template: "pc_lab":
de: "{key} ({value}SWS)" { de: "Rechnergestütztes Praktikum", en: "PC exersise" },
en: "{key} ({value}SWS)" "project": { de: "Project", en: "project" },
}
template:
de: "{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:
{ {
de: "Literatur und multimediale Lehr- und Lernprogramme", de: "Literatur und multimediale Lehr- und Lernprogramme",
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:
{ {
en: "credits and weight of mark", en: "credits and weight of mark",
de: "Kreditpunkte und Gewichtung der Note in der Gesamtnote", de: "Kreditpunkte und Gewichtung der Note in der Gesamtnote",
} }
template: template:
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:
{ {
"written": { de: "Schriftliche Prüfung", en: "written exam" }, "written": { de: "Schriftliche Prüfung", en: "written exam" },
"oral": { de: "Mündliche Prüfung", en: "oral exam" }, "oral": { de: "Mündliche Prüfung", en: "oral exam" },
"alternative": "alternative":
{ {
de: "Alternative Prüfungunsleistung", de: "Alternative Prüfungunsleistung",
en: "alternative examination", en: "alternative examination",
}, },
} }
spec: true spec: true
template: template:
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" }