Changeset e70dd79 in flexograder


Ignore:
Timestamp:
11/25/25 19:01:53 (4 months ago)
Author:
Enrico Schwass <ennoausberlin@…>
Branches:
fake-data, main, master
Children:
9db70f7
Parents:
21f0ba8
Message:

domain fixes

Files:
1 added
1 deleted
8 edited

Legend:

Unmodified
Added
Removed
  • builder/exam_elements.py

    r21f0ba8 re70dd79  
    405405        return obj
    406406
    407 @dataclass
    408 class Signature(FlexoEntity):
    409     """
    410     I represent a digital or procedural signature for another entity.
    411 
    412     This is a stub version: I only carry logical metadata, not cryptographic proof.
    413     Later, platform-specific implementations (e.g. Windows certificate signing)
    414     can fill the 'signature_data' field with real data.
    415 
    416     Lifecycle:
    417       - Created in DRAFT → becomes APPROVED once verified
    418       - Optionally moves to PUBLISHED if distributed externally
    419     """
    420 
    421     ENTITY_TYPE = EntityType.OUTPUT  # or define a dedicated SIGNATURE type later
    422 
    423     signed_entity: Optional[FlexOID] = None
    424     signer_id: Optional[UUID] = None
    425     signature_data: str = ""
    426     certificate_ref: Optional[str] = None
    427     comment: Optional[str] = None
    428 
    429     # Required by base class
    430     @property
    431     def text_seed(self) -> str:
    432         """Deterministic content used for ID and fingerprint generation."""
    433         return f"{self.signed_entity or ''}:{self.signer_id or ''}:{self.signature_data}"
    434 
    435     @classmethod
    436     def default(cls):
    437         """Create an empty draft signature."""
    438         return cls()
    439 
    440     # Optional helper: mark as verified
    441     def approve(self, comment: str = ""):
    442         """Mark this signature as verified or trusted."""
    443         self.comment = comment or self.comment
    444         super().approve()
    445         return self
  • builder/question_catalog.py

    r21f0ba8 re70dd79  
    44from .exam_elements import ExamElement
    55from builder.question_factory import question_factory
    6 from flexoentity import FlexoEntity, EntityType, EntityState, FlexOID
     6from flexoentity import FlexoEntity, EntityType, EntityState, FlexOID, Domain
    77
    88
     
    1414    title: str = ""
    1515    author: str = "unknown"
     16    domains: Dict[str, Dict[str, Any]] = field(default_factory=dict)
    1617    questions: List[ExamElement] = field(default_factory=list)
    1718
     
    3435    def __post_init__(self):
    3536        super().__post_init__()
    36 
    37     @property
    38     def domains(self) -> set[str]:
    39         """Return a set of all unique domains present in questions."""
    40         return {q.domain for q in self.questions if hasattr(q, "domain")}
    4137
    4238    @property
     
    140136
    141137        # Restore questions
     138       
     139        domains =  data.get("domains", {})
     140        obj.domains = [Domain.from_dict(v)for k, v in domains.items()]
     141           
    142142        obj.questions = [question_factory(qd) for qd in data.get("questions", [])]
    143143
  • examples/KILE_EXAM.json

    r21f0ba8 re70dd79  
    1717    "GENERAL": {
    1818      "flexo_id": "GENERAL-D253110-E0C3C8A5A8A4@001D",
    19       "domain_id": "GENERAL",
    2019      "fullname": "General Knowledge",
    2120      "description": "Domain for general-purpose questions not tied to a specific specialty.",
     
    2423    "CS_ALGO": {
    2524      "flexo_id": "CS_ALGO-D253110-BF6AFD1A0CBD@001D",
    26       "domain_id": "CS_ALGO",
    2725      "fullname": "Computer Science – Algorithms",
    2826      "description": "Covers algorithmic thinking, complexity and computational procedures.",
     
    3129    "AI_BASICS": {
    3230      "flexo_id": "AI_BASICS-D253110-1748EBF9E2EF@001D",
    33       "domain_id": "AI_BASICS",
    3431      "fullname": "Artificial Intelligence – Basics",
    3532      "description": "Introductory concepts of artificial intelligence.",
     
    3835    "AI_ML": {
    3936      "flexo_id": "AI_ML-D253110-7E6E4A52B1D8@001D",
    40       "domain_id": "AI_ML",
    4137      "fullname": "Machine Learning",
    4238      "description": "Covers supervised, unsupervised and reinforcement learning methods.",
     
    4541    "AI_NN": {
    4642      "flexo_id": "AI_NN-D253110-E1F07C1B8D9A@001D",
    47       "domain_id": "AI_NN",
    4843      "fullname": "Neural Networks",
    4944      "description": "Structure, training and interpretation of neural networks.",
     
    5247    "AI_NLP": {
    5348      "flexo_id": "AI_NLP-D253110-3FA94F2557C6@001D",
    54       "domain_id": "AI_NLP",
    5549      "fullname": "Natural Language Processing",
    5650      "description": "Natural language understanding, processing and generation.",
     
    5953    "AI_TEXT": {
    6054      "flexo_id": "AI_TEXT-D253110-C16D4E72F06A@001D",
    61       "domain_id": "AI_TEXT",
    6255      "fullname": "AI – Text Processing",
    6356      "description": "Text classification, generation, text models and related topics.",
     
    6659    "AI_DTREE": {
    6760      "flexo_id": "AI_DTREE-D253110-A3E1E21FE26A@001D",
    68       "domain_id": "AI_DTREE",
    6961      "fullname": "Decision Trees",
    7062      "description": "Tree-based models, including classification and regression trees.",
     
    7365    "AI_ETHICS": {
    7466      "flexo_id": "AI_ETHICS-D253110-9B4A4B2F82C8@001D",
    75       "domain_id": "AI_ETHICS",
    7667      "fullname": "AI Ethics",
    7768      "description": "Ethical considerations, fairness, transparency and accountability.",
     
    8071    "WEB_DEV": {
    8172      "flexo_id": "WEB_DEV-D253110-1F2B89B5837D@001D",
    82       "domain_id": "WEB_DEV",
    8373      "fullname": "Web Development",
    8474      "description": "General principles, languages and environments in web development.",
     
    8777    "WEB_HTML": {
    8878      "flexo_id": "WEB_HTML-D253110-4C814DD1C4A9@001D",
    89       "domain_id": "WEB_HTML",
    9079      "fullname": "HTML / Markup",
    9180      "description": "Hypertext Markup Language, basic structure and semantics.",
     
    9483    "WEB_CSS": {
    9584      "flexo_id": "WEB_CSS-D253110-25C4F283F5AB@001D",
    96       "domain_id": "WEB_CSS",
    9785      "fullname": "CSS / Styling",
    9886      "description": "Layout, styling and formatting of web content.",
     
    10189    "WEB_FRAMEWORKS": {
    10290      "flexo_id": "WEB_FRAMEWORKS-D253110-6BF01E4DC83C@001D",
    103       "domain_id": "WEB_FRAMEWORKS",
    10491      "fullname": "Web Frameworks",
    10592      "description": "Tools and libraries for building complex web applications.",
     
    10895    "WEB_ARCH": {
    10996      "flexo_id": "WEB_ARCH-D253110-89F23A2B9E2C@001D",
    110       "domain_id": "WEB_ARCH",
    11197      "fullname": "Web Architecture",
    11298      "description": "Client/server models, APIs, scalability and distributed systems.",
     
    115101    "DATA_BIG": {
    116102      "flexo_id": "DATA_BIG-D253110-7B381C4A22C1@001D",
    117       "domain_id": "DATA_BIG",
    118103      "fullname": "Big Data",
    119104      "description": "Handling, analyzing and processing very large datasets.",
     
    122107    "DATA_FORMATS": {
    123108      "flexo_id": "DATA_FORMATS-D253110-3C5527DE7A19@001D",
    124       "domain_id": "DATA_FORMATS",
    125109      "fullname": "Data Formats",
    126110      "description": "Structured and unstructured data formats such as JSON, XML, CSV.",
     
    129113    "DB_SQL": {
    130114      "flexo_id": "DB_SQL-D253110-E7A88B21A5F6@001D",
    131       "domain_id": "DB_SQL",
    132115      "fullname": "Databases / SQL",
    133116      "description": "Relational databases, SQL queries and data organization.",
     
    136119    "PROG_LANG": {
    137120      "flexo_id": "PROG_LANG-D253110-0C786A1F55DA@001D",
    138       "domain_id": "PROG_LANG",
    139121      "fullname": "Programming Languages",
    140122      "description": "Languages, syntax, semantics and usage contexts.",
     
    143125    "SOFT_ENG": {
    144126      "flexo_id": "SOFT_ENG-D253110-A98C1C3FD19D@001D",
    145       "domain_id": "SOFT_ENG",
    146127      "fullname": "Software Engineering",
    147128      "description": "Processes, architecture, patterns and engineering methods.",
     
    150131    "SOFT_METHODS": {
    151132      "flexo_id": "SOFT_METHODS-D253110-1252B2BE1339@001D",
    152       "domain_id": "SOFT_METHODS",
    153133      "fullname": "Software Development Methods",
    154134      "description": "Agile, XP, TDD and iterative development practices.",
     
    157137    "API": {
    158138      "flexo_id": "API-D253110-4F0AEC61A8F3@001D",
    159       "domain_id": "API",
    160139      "fullname": "APIs / Interfaces",
    161140      "description": "Design and use of application programming interfaces.",
  • examples/python/beginner.json

    r21f0ba8 re70dd79  
    11{
    2   "catalog": "Python Absolute Beginner Questions",
     2  "flexo_id": "PY_BEGINNER-C251122-1A2B3C4D5E6F@001D",
     3  "title": "Python Absolute Beginner Questions",
     4  "author": "Python Team",
    35  "version": "1.0",
    4   "generated_at": "2025-10-20",
     6  "generated_at": "2025-11-22",
     7  "domains": {
     8      "PY_BEGINNER": {
     9          "flexo_id": "PY_BEGINNER-D251125-9EEB0FCC0CFA@001D",
     10          "domain_id": "PY_BEGINNER",
     11          "name": "Python Beginner Essentials",
     12          "description": "Einsteigerfragen zu Python: Grundlagen, Datentypen, Dateien, Schleifen, Kontrollstrukturen."
     13      }
     14  },
    515  "questions": [
    616    {
    7       "domain_id": "PY_ARITHM",
     17      "flexo_id": "PY_BEGINNER-I251122-9C2FA6D10E93@001D",
     18      "domain_id": "PY_BEGINNER",
    819      "qtype": "single_choice",
    920      "text": "Welcher Operator führt die Ganzzahl-Division aus (abrunden Richtung −∞)?",
    1021      "options": [
    1122        {
     23          "id": "A",
    1224          "text": "//",
    13           "points": 1,
    14           "id": "A"
    15         },
    16         {
     25          "points": 1
     26        },
     27        {
     28          "id": "B",
    1729          "text": "/",
    18           "points": 0,
    19           "id": "B"
    20         },
    21         {
     30          "points": 0
     31        },
     32        {
     33          "id": "C",
    2234          "text": "%",
    23           "points": 0,
    24           "id": "C"
    25         }
    26       ]
    27     },
    28     {
    29       "domain_id": "PY_ARITHM",
     35          "points": 0
     36        }
     37      ]
     38    },
     39    {
     40      "flexo_id": "PY_BEGINNER-I251122-041CBAB1D8EF@001D",
     41      "domain_id": "PY_BEGINNER",
    3042      "qtype": "multiple_choice",
    3143      "text": "Welche Aussagen zu int und float sind richtig?",
    3244      "options": [
    3345        {
     46          "id": "A",
    3447          "text": "int ist in der Größe nur durch den Speicher begrenzt.",
    35           "points": 1,
    36           "id": "A"
    37         },
    38         {
     48          "points": 1
     49        },
     50        {
     51          "id": "B",
    3952          "text": "0.1 + 0.2 kann wegen Binär-Floats leicht von 0.3 abweichen.",
    40           "points": 1,
    41           "id": "B"
    42         },
    43         {
     53          "points": 1
     54        },
     55        {
     56          "id": "C",
    4457          "text": "int hat immer exakt 64 Bit in Python.",
    45           "points": 0,
    46           "id": "C"
    47         },
    48         {
     58          "points": 0
     59        },
     60        {
     61          "id": "D",
    4962          "text": "math.isclose hilft beim Vergleichen von Floats.",
    50           "points": 1,
    51           "id": "D"
    52         }
    53       ]
    54     },
    55     {
    56       "domain_id": "PY_ARITHM",
     63          "points": 1
     64        }
     65      ]
     66    },
     67    {
     68      "flexo_id": "PY_BEGINNER-I251122-682F4D78BB02@001D",
     69      "domain_id": "PY_BEGINNER",
    5770      "qtype": "single_choice",
    5871      "text": "Welches Ergebnis hat 2 ** 3 ** 2 in Python?",
    5972      "options": [
    6073        {
     74          "id": "A",
    6175          "text": "512 (rechtsassoziativ)",
    62           "points": 1,
    63           "id": "A"
    64         },
    65         {
     76          "points": 1
     77        },
     78        {
     79          "id": "B",
    6680          "text": "64",
    67           "points": 0,
    68           "id": "B"
    69         },
    70         {
     81          "points": 0
     82        },
     83        {
     84          "id": "C",
    7185          "text": "Fehler",
    72           "points": 0,
    73           "id": "C"
    74         }
    75       ]
    76     },
    77     {
    78       "domain_id": "PY_ARITHM",
     86          "points": 0
     87        }
     88      ]
     89    },
     90    {
     91      "flexo_id": "PY_BEGINNER-I251122-59E4F519207C@001D",
     92      "domain_id": "PY_BEGINNER",
    7993      "qtype": "multiple_choice",
    8094      "text": "Welche Funktionen wandeln Zahlen oder runden sinnvoll?",
    8195      "options": [
    8296        {
     97          "id": "A",
    8398          "text": "int('42')",
    84           "points": 1,
    85           "id": "A"
    86         },
    87         {
     99          "points": 1
     100        },
     101        {
     102          "id": "B",
    88103          "text": "float('3.14')",
    89           "points": 1,
    90           "id": "B"
    91         },
    92         {
     104          "points": 1
     105        },
     106        {
     107          "id": "C",
    93108          "text": "round(3.5)",
    94           "points": 1,
    95           "id": "C"
    96         },
    97         {
     109          "points": 1
     110        },
     111        {
     112          "id": "D",
    98113          "text": "toint(3.2)",
    99           "points": 0,
    100           "id": "D"
    101         }
    102       ]
    103     },
    104     {
    105       "domain_id": "PY_ARITHM",
     114          "points": 0
     115        }
     116      ]
     117    },
     118    {
     119      "flexo_id": "PY_BEGINNER-I251122-7DA6E91C299B@001D",
     120      "domain_id": "PY_BEGINNER",
    106121      "qtype": "single_choice",
    107122      "text": "Welches Paar gibt Quotient und Rest gemeinsam zurück?",
    108123      "options": [
    109124        {
     125          "id": "A",
    110126          "text": "divmod(a, b)",
    111           "points": 1,
    112           "id": "A"
    113         },
    114         {
     127          "points": 1
     128        },
     129        {
     130          "id": "B",
    115131          "text": "split(a, b)",
    116           "points": 0,
    117           "id": "B"
    118         },
    119         {
     132          "points": 0
     133        },
     134        {
     135          "id": "C",
    120136          "text": "quot(a, b)",
    121           "points": 0,
    122           "id": "C"
    123         }
    124       ]
    125     },
    126     {
    127       "domain_id": "PY_CNTRLSTRCT",
     137          "points": 0
     138        }
     139      ]
     140    },
     141    {
     142      "flexo_id": "PY_BEGINNER-I251122-19A961778C0E@001D",
     143      "domain_id": "PY_BEGINNER",
    128144      "qtype": "single_choice",
    129145      "text": "Welche Schleife wiederholt Code, solange eine Bedingung wahr ist?",
    130146      "options": [
    131147        {
     148          "id": "A",
    132149          "text": "while",
    133           "points": 1,
    134           "id": "A"
    135         },
    136         {
     150          "points": 1
     151        },
     152        {
     153          "id": "B",
    137154          "text": "if",
    138           "points": 0,
    139           "id": "B"
    140         },
    141         {
     155          "points": 0
     156        },
     157        {
     158          "id": "C",
    142159          "text": "match/case",
    143           "points": 0,
    144           "id": "C"
    145         }
    146       ]
    147     },
    148     {
    149       "domain_id": "PY_CNTRLSTRCT",
     160          "points": 0
     161        }
     162      ]
     163    },
     164    {
     165      "flexo_id": "PY_BEGINNER-I251122-9565317037EF@001D",
     166      "domain_id": "PY_BEGINNER",
    150167      "qtype": "multiple_choice",
    151168      "text": "Welche Schlüsselwörter passen zu Schleifen?",
    152169      "options": [
    153170        {
     171          "id": "A",
    154172          "text": "break beendet die Schleife vorzeitig.",
    155           "points": 1,
    156           "id": "A"
    157         },
    158         {
     173          "points": 1
     174        },
     175        {
     176          "id": "B",
    159177          "text": "continue springt zur nächsten Iteration.",
    160           "points": 1,
    161           "id": "B"
    162         },
    163         {
     178          "points": 1
     179        },
     180        {
     181          "id": "C",
    164182          "text": "redo startet die Schleife neu.",
    165           "points": 0,
    166           "id": "C"
    167         },
    168         {
     183          "points": 0
     184        },
     185        {
     186          "id": "D",
    169187          "text": "else-Teil läuft, wenn kein break passierte.",
    170           "points": 1,
    171           "id": "D"
    172         }
    173       ]
    174     },
    175     {
    176       "domain_id": "PY_CNTRLSTRCT",
     188          "points": 1
     189        }
     190      ]
     191    },
     192    {
     193      "flexo_id": "PY_BEGINNER-I251122-F16429E8C6A1@001D",
     194      "domain_id": "PY_BEGINNER",
    177195      "qtype": "single_choice",
    178196      "text": "Wie iteriert man idiomatisch über Index UND Wert?",
    179197      "options": [
    180198        {
     199          "id": "A",
    181200          "text": "for i, x in enumerate(seq):",
    182           "points": 1,
    183           "id": "A"
    184         },
    185         {
     201          "points": 1
     202        },
     203        {
     204          "id": "B",
    186205          "text": "for i in range(seq): x = seq[i]",
    187           "points": 0,
    188           "id": "B"
    189         },
    190         {
     206          "points": 0
     207        },
     208        {
     209          "id": "C",
    191210          "text": "for (i, x) in seq.items()",
    192           "points": 0,
    193           "id": "C"
    194         }
    195       ]
    196     },
    197     {
    198       "domain_id": "PY_CNTRLSTRCT",
     211          "points": 0
     212        }
     213      ]
     214    },
     215    {
     216      "flexo_id": "PY_BEGINNER-I251122-76743FD15104@001D",
     217      "domain_id": "PY_BEGINNER",
    199218      "qtype": "multiple_choice",
    200219      "text": "Welche gehören zur if-Syntax?",
    201220      "options": [
    202221        {
     222          "id": "A",
    203223          "text": "if …:",
    204           "points": 1,
    205           "id": "A"
    206         },
    207         {
     224          "points": 1
     225        },
     226        {
     227          "id": "B",
    208228          "text": "elif …:",
    209           "points": 1,
    210           "id": "B"
    211         },
    212         {
     229          "points": 1
     230        },
     231        {
     232          "id": "C",
    213233          "text": "elseif …:",
    214           "points": 0,
    215           "id": "C"
    216         },
    217         {
     234          "points": 0
     235        },
     236        {
     237          "id": "D",
    218238          "text": "else:",
    219           "points": 1,
    220           "id": "D"
    221         }
    222       ]
    223     },
    224     {
    225       "domain_id": "PY_CNTRLSTRCT",
     239          "points": 1
     240        }
     241      ]
     242    },
     243    {
     244      "flexo_id": "PY_BEGINNER-I251122-3DFA621A61F7@001D",
     245      "domain_id": "PY_BEGINNER",
    226246      "qtype": "single_choice",
    227247      "text": "Wofür steht der Walrus-Operator (:=) auf Einsteiger-Niveau?",
    228248      "options": [
    229249        {
     250          "id": "A",
    230251          "text": "Zuweisung in einem Ausdruck, z. B. while (s := input()) != ''",
    231           "points": 1,
    232           "id": "A"
    233         },
    234         {
     252          "points": 1
     253        },
     254        {
     255          "id": "B",
    235256          "text": "Vergleichsoperator",
    236           "points": 0,
    237           "id": "B"
    238         },
    239         {
     257          "points": 0
     258        },
     259        {
     260          "id": "C",
    240261          "text": "Importoperator",
    241           "points": 0,
    242           "id": "C"
    243         }
    244       ]
    245     },
    246     {
    247       "domain_id": "PY_FILES",
     262          "points": 0
     263        }
     264      ]
     265    },
     266    {
     267      "flexo_id": "PY_BEGINNER-I251122-660F17B704AC@001D",
     268      "domain_id": "PY_BEGINNER",
    248269      "qtype": "single_choice",
    249270      "text": "Wie öffnet man eine Textdatei sicher zum Lesen in UTF-8?",
    250271      "options": [
    251272        {
     273          "id": "A",
    252274          "text": "open(path, 'r', encoding='utf-8')",
    253           "points": 1,
    254           "id": "A"
    255         },
    256         {
     275          "points": 1
     276        },
     277        {
     278          "id": "B",
    257279          "text": "open(path)",
    258           "points": 0,
    259           "id": "B"
    260         },
    261         {
     280          "points": 0
     281        },
     282        {
     283          "id": "C",
    262284          "text": "open('utf-8', path)",
    263           "points": 0,
    264           "id": "C"
    265         }
    266       ]
    267     },
    268     {
    269       "domain_id": "PY_FILES",
     285          "points": 0
     286        }
     287      ]
     288    },
     289    {
     290      "flexo_id": "PY_BEGINNER-I251122-1EE87A1F35B1@001D",
     291      "domain_id": "PY_BEGINNER",
    270292      "qtype": "multiple_choice",
    271293      "text": "Warum with beim Datei-I/O nutzen?",
    272294      "options": [
    273295        {
     296          "id": "A",
    274297          "text": "Datei wird automatisch geschlossen.",
    275           "points": 1,
    276           "id": "A"
    277         },
    278         {
     298          "points": 1
     299        },
     300        {
     301          "id": "B",
    279302          "text": "Weniger Risiko für Leaks/Fehler.",
    280           "points": 1,
    281           "id": "B"
    282         },
    283         {
     303          "points": 1
     304        },
     305        {
     306          "id": "C",
    284307          "text": "Code wird automatisch schneller.",
    285           "points": 0,
    286           "id": "C"
    287         },
    288         {
     308          "points": 0
     309        },
     310        {
     311          "id": "D",
    289312          "text": "Kompaktere Schreibweise.",
    290           "points": 1,
    291           "id": "D"
    292         }
    293       ]
    294     },
    295     {
    296       "domain_id": "PY_FILES",
     313          "points": 1
     314        }
     315      ]
     316    },
     317    {
     318      "flexo_id": "PY_BEGINNER-I251122-56FD1E83422E@001D",
     319      "domain_id": "PY_BEGINNER",
    297320      "qtype": "single_choice",
    298321      "text": "Welcher Modus hängt Text an (ohne zu überschreiben)?",
    299322      "options": [
    300323        {
     324          "id": "A",
    301325          "text": "'a'",
    302           "points": 1,
    303           "id": "A"
    304         },
    305         {
     326          "points": 1
     327        },
     328        {
     329          "id": "B",
    306330          "text": "'w'",
    307           "points": 0,
    308           "id": "B"
    309         },
    310         {
     331          "points": 0
     332        },
     333        {
     334          "id": "C",
    311335          "text": "'x'",
    312           "points": 0,
    313           "id": "C"
    314         }
    315       ]
    316     },
    317     {
    318       "domain_id": "PY_FILES",
     336          "points": 0
     337        }
     338      ]
     339    },
     340    {
     341      "flexo_id": "PY_BEGINNER-I251122-9233B52C30D3@001D",
     342      "domain_id": "PY_BEGINNER",
    319343      "qtype": "multiple_choice",
    320344      "text": "Welche Pfad-Utilities aus der Stdlib sind nützlich?",
    321345      "options": [
    322346        {
     347          "id": "A",
    323348          "text": "pathlib.Path",
    324           "points": 1,
    325           "id": "A"
    326         },
    327         {
     349          "points": 1
     350        },
     351        {
     352          "id": "B",
    328353          "text": "os.path",
    329           "points": 1,
    330           "id": "B"
    331         },
    332         {
     354          "points": 1
     355        },
     356        {
     357          "id": "C",
    333358          "text": "glob",
    334           "points": 1,
    335           "id": "C"
    336         },
    337         {
     359          "points": 1
     360        },
     361        {
     362          "id": "D",
    338363          "text": "sys.path zum Schreiben von Dateien",
    339           "points": 0,
    340           "id": "D"
    341         }
    342       ]
    343     },
    344     {
    345       "domain_id": "PY_FILES",
     364          "points": 0
     365        }
     366      ]
     367    },
     368    {
     369      "flexo_id": "PY_BEGINNER-I251122-1D819850078F@001D",
     370      "domain_id": "PY_BEGINNER",
    346371      "qtype": "single_choice",
    347372      "text": "Wie liest man große Textdateien speicherschonend?",
    348373      "options": [
    349374        {
     375          "id": "A",
    350376          "text": "for line in f:",
    351           "points": 1,
    352           "id": "A"
    353         },
    354         {
     377          "points": 1
     378        },
     379        {
     380          "id": "B",
    355381          "text": "text = f.read()",
    356           "points": 0,
    357           "id": "B"
    358         },
    359         {
     382          "points": 0
     383        },
     384        {
     385          "id": "C",
    360386          "text": "lines = f.readlines()",
    361           "points": 0,
    362           "id": "C"
    363         }
    364       ]
    365     },
    366     {
    367       "domain_id": "PY_TYPES",
     387          "points": 0
     388        }
     389      ]
     390    },
     391    {
     392      "flexo_id": "PY_BEGINNER-I251122-C6D7F5428D92@001D",
     393      "domain_id": "PY_BEGINNER",
    368394      "qtype": "single_choice",
    369395      "text": "Wozu dienen Typannotationen in Python?",
    370396      "options": [
    371397        {
     398          "id": "A",
    372399          "text": "Für Tools/IDE/Typchecker; Laufzeit bleibt dynamisch.",
    373           "points": 1,
    374           "id": "A"
    375         },
    376         {
     400          "points": 1
     401        },
     402        {
     403          "id": "B",
    377404          "text": "Erzwingen strikt die Typen zur Laufzeit.",
    378           "points": 0,
    379           "id": "B"
    380         },
    381         {
     405          "points": 0
     406        },
     407        {
     408          "id": "C",
    382409          "text": "Ersetzen Docstrings vollständig.",
    383           "points": 0,
    384           "id": "C"
    385         }
    386       ]
    387     },
    388     {
    389       "domain_id": "PY_TYPES",
     410          "points": 0
     411        }
     412      ]
     413    },
     414    {
     415      "flexo_id": "PY_BEGINNER-I251122-21BF1EB15CC4@001D",
     416      "domain_id": "PY_BEGINNER",
    390417      "qtype": "multiple_choice",
    391418      "text": "Welche sind gültige Container-Annotationen (>=3.9)?",
    392419      "options": [
    393420        {
     421          "id": "A",
    394422          "text": "list[int]",
    395           "points": 1,
    396           "id": "A"
    397         },
    398         {
     423          "points": 1
     424        },
     425        {
     426          "id": "B",
    399427          "text": "dict[str, int]",
    400           "points": 1,
    401           "id": "B"
    402         },
    403         {
     428          "points": 1
     429        },
     430        {
     431          "id": "C",
    404432          "text": "set[float]",
    405           "points": 1,
    406           "id": "C"
    407         },
    408         {
     433          "points": 1
     434        },
     435        {
     436          "id": "D",
    409437          "text": "tuple<int>",
    410           "points": 0,
    411           "id": "D"
    412         }
    413       ]
    414     },
    415     {
    416       "domain_id": "PY_TYPES",
     438          "points": 0
     439        }
     440      ]
     441    },
     442    {
     443      "flexo_id": "PY_BEGINNER-I251122-1798CB33B11A@001D",
     444      "domain_id": "PY_BEGINNER",
    417445      "qtype": "single_choice",
    418446      "text": "Wie annotiert man 'oder None' präzise?",
    419447      "options": [
    420448        {
     449          "id": "A",
    421450          "text": "T | None (Optional[T])",
    422           "points": 1,
    423           "id": "A"
    424         },
    425         {
     451          "points": 1
     452        },
     453        {
     454          "id": "B",
    426455          "text": "Any",
    427           "points": 0,
    428           "id": "B"
    429         },
    430         {
     456          "points": 0
     457        },
     458        {
     459          "id": "C",
    431460          "text": "object",
    432           "points": 0,
    433           "id": "C"
    434         }
    435       ]
    436     },
    437     {
    438       "domain_id": "PY_TYPES",
     461          "points": 0
     462        }
     463      ]
     464    },
     465    {
     466      "flexo_id": "PY_BEGINNER-I251122-071E3FA28B05@001D",
     467      "domain_id": "PY_BEGINNER",
    439468      "qtype": "multiple_choice",
    440469      "text": "Welche Stdlib-Hilfen definieren einfache Datenobjekte?",
    441470      "options": [
    442471        {
     472          "id": "A",
    443473          "text": "dataclasses.dataclass",
    444           "points": 1,
    445           "id": "A"
    446         },
    447         {
     474          "points": 1
     475        },
     476        {
     477          "id": "B",
    448478          "text": "collections.namedtuple",
    449           "points": 1,
    450           "id": "B"
    451         },
    452         {
     479          "points": 1
     480        },
     481        {
     482          "id": "C",
    453483          "text": "typing.TypedDict",
    454           "points": 1,
    455           "id": "C"
    456         },
    457         {
     484          "points": 1
     485        },
     486        {
     487          "id": "D",
    458488          "text": "functools.singledispatch",
    459           "points": 0,
    460           "id": "D"
    461         }
    462       ]
    463     },
    464     {
    465       "domain_id": "PY_TYPES",
    466       "qtype": "single_choice",
    467       "text": "Welche Annotation beschreibt eine Funktion 'nimmt int, gibt bool'?",
    468       "options": [
    469         {
    470           "text": "Callable[[int], bool]",
    471           "points": 1,
    472           "id": "A"
    473         },
    474         {
    475           "text": "function(int)->bool",
    476           "points": 0,
    477           "id": "B"
    478         },
    479         {
    480           "text": "call[int->bool]",
    481           "points": 0,
    482           "id": "C"
    483         }
    484       ]
    485     },
    486     {
    487       "domain_id": "PY_STREAMS",
    488       "qtype": "single_choice",
    489       "text": "Was liefert eine Listen-Comprehension?",
    490       "options": [
    491         {
    492           "text": "Eine neue Liste",
    493           "points": 1,
    494           "id": "A"
    495         },
    496         {
    497           "text": "Einen Generator",
    498           "points": 0,
    499           "id": "B"
    500         },
    501         {
    502           "text": "Ein Set",
    503           "points": 0,
    504           "id": "C"
    505         }
    506       ]
    507     },
    508     {
    509       "domain_id": "PY_STREAMS",
    510       "qtype": "multiple_choice",
    511       "text": "Welche Konstrukte sind lazy (werten Elemente erst bei Bedarf aus)?",
    512       "options": [
    513         {
    514           "text": "Generator-Expression: (x for x in xs)",
    515           "points": 1,
    516           "id": "A"
    517         },
    518         {
    519           "text": "map(func, xs)",
    520           "points": 1,
    521           "id": "B"
    522         },
    523         {
    524           "text": "filter(func, xs)",
    525           "points": 1,
    526           "id": "C"
    527         },
    528         {
    529           "text": "[x for x in xs]",
    530           "points": 0,
    531           "id": "D"
    532         }
    533       ]
    534     },
    535     {
    536       "domain_id": "PY_STREAMS",
    537       "qtype": "single_choice",
    538       "text": "Wofür steht yield in einer Funktion?",
    539       "options": [
    540         {
    541           "text": "Macht die Funktion zum Generator (liefert Werte schrittweise).",
    542           "points": 1,
    543           "id": "A"
    544         },
    545         {
    546           "text": "Beendet das Programm.",
    547           "points": 0,
    548           "id": "B"
    549         },
    550         {
    551           "text": "Importiert ein Modul.",
    552           "points": 0,
    553           "id": "C"
    554         }
    555       ]
    556     },
    557     {
    558       "domain_id": "PY_STREAMS",
    559       "qtype": "multiple_choice",
    560       "text": "Welche Tools sind nützlich für Iterables?",
    561       "options": [
    562         {
    563           "text": "itertools (z. B. chain, islice)",
    564           "points": 1,
    565           "id": "A"
    566         },
    567         {
    568           "text": "enumerate",
    569           "points": 1,
    570           "id": "B"
    571         },
    572         {
    573           "text": "sum zum Listen-Konkatenieren",
    574           "points": 0,
    575           "id": "C"
    576         },
    577         {
    578           "text": "re als Iterable-Ersatz",
    579           "points": 0,
    580           "id": "D"
    581         }
    582       ]
    583     },
    584     {
    585       "domain_id": "PY_STREAMS",
    586       "qtype": "single_choice",
    587       "text": "Welcher Ausdruck ist am speicherschonendsten?",
    588       "options": [
    589         {
    590           "text": "sum(x for x in range(1_000_000))",
    591           "points": 1,
    592           "id": "A"
    593         },
    594         {
    595           "text": "sum([x for x in range(1_000_000)])",
    596           "points": 0,
    597           "id": "B"
    598         },
    599         {
    600           "text": "list(range(1_000_000))",
    601           "points": 0,
    602           "id": "C"
    603         }
    604       ]
    605     },
    606     {
    607       "domain_id": "PY_COLLECTIONS",
    608       "qtype": "single_choice",
    609       "text": "Welche Struktur eignet sich für schnelles Anhängen am Ende?",
    610       "options": [
    611         {
    612           "text": "list",
    613           "points": 1,
    614           "id": "A"
    615         },
    616         {
    617           "text": "tuple",
    618           "points": 0,
    619           "id": "B"
    620         },
    621         {
    622           "text": "str",
    623           "points": 0,
    624           "id": "C"
    625         }
    626       ]
    627     },
    628     {
    629       "domain_id": "PY_COLLECTIONS",
    630       "qtype": "multiple_choice",
    631       "text": "Welche Aussagen zu set sind korrekt?",
    632       "options": [
    633         {
    634           "text": "Speichert eindeutige, hashbare Elemente.",
    635           "points": 1,
    636           "id": "A"
    637         },
    638         {
    639           "text": "Unterstützt Vereinigung/Schnittmenge/Differenz.",
    640           "points": 1,
    641           "id": "B"
    642         },
    643         {
    644           "text": "Erhält garantiert die Einfügereihenfolge.",
    645           "points": 0,
    646           "id": "C"
    647         },
    648         {
    649           "text": "Akzeptiert Duplikate.",
    650           "points": 0,
    651           "id": "D"
    652         }
    653       ]
    654     },
    655     {
    656       "domain_id": "PY_COLLECTIONS",
    657       "qtype": "single_choice",
    658       "text": "Wofür steht collections.Counter?",
    659       "options": [
    660         {
    661           "text": "Zum Zählen von Element-Häufigkeiten.",
    662           "points": 1,
    663           "id": "A"
    664         },
    665         {
    666           "text": "Zum Öffnen von Dateien.",
    667           "points": 0,
    668           "id": "B"
    669         },
    670         {
    671           "text": "Zum Runden von Zahlen.",
    672           "points": 0,
    673           "id": "C"
    674         }
    675       ]
    676     },
    677     {
    678       "domain_id": "PY_COLLECTIONS",
    679       "qtype": "multiple_choice",
    680       "text": "Welche Strukturen sind unveränderlich (immutable)?",
    681       "options": [
    682         {
    683           "text": "tuple",
    684           "points": 1,
    685           "id": "A"
    686         },
    687         {
    688           "text": "frozenset",
    689           "points": 1,
    690           "id": "B"
    691         },
    692         {
    693           "text": "list",
    694           "points": 0,
    695           "id": "C"
    696         },
    697         {
    698           "text": "dict",
    699           "points": 0,
    700           "id": "D"
    701         }
    702       ]
    703     },
    704     {
    705       "domain_id": "PY_COLLECTIONS",
    706       "qtype": "single_choice",
    707       "text": "Was bewirkt defaultdict(list)?",
    708       "options": [
    709         {
    710           "text": "Fehlende Schlüssel bekommen automatisch eine leere Liste.",
    711           "points": 1,
    712           "id": "A"
    713         },
    714         {
    715           "text": "Fehlende Schlüssel werfen KeyError.",
    716           "points": 0,
    717           "id": "B"
    718         },
    719         {
    720           "text": "Es unterscheidet sich nicht von dict.",
    721           "points": 0,
    722           "id": "C"
    723         }
    724       ]
    725     },
    726     {
    727       "domain_id": "PY_DATETIME",
    728       "qtype": "single_choice",
    729       "text": "Welche Stdlib liefert Zeitzonen (ab 3.9)?",
    730       "options": [
    731         {
    732           "text": "zoneinfo",
    733           "points": 1,
    734           "id": "A"
    735         },
    736         {
    737           "text": "pytz",
    738           "points": 0,
    739           "id": "B"
    740         },
    741         {
    742           "text": "tzlocal",
    743           "points": 0,
    744           "id": "C"
    745         }
    746       ]
    747     },
    748     {
    749       "domain_id": "PY_DATETIME",
    750       "qtype": "multiple_choice",
    751       "text": "Welche Aussagen sind korrekt?",
    752       "options": [
    753         {
    754           "text": "Naive datetime hat keine Zeitzone.",
    755           "points": 1,
    756           "id": "A"
    757         },
    758         {
    759           "text": "Aware datetime enthält tzinfo.",
    760           "points": 1,
    761           "id": "B"
    762         },
    763         {
    764           "text": "UTC ist eine gute interne Referenz.",
    765           "points": 1,
    766           "id": "C"
    767         },
    768         {
    769           "text": "time.time() ist garantiert monoton.",
    770           "points": 0,
    771           "id": "D"
    772         }
    773       ]
    774     },
    775     {
    776       "domain_id": "PY_DATETIME",
    777       "qtype": "single_choice",
    778       "text": "Wie misst man kurze Zeitdauern zuverlässig?",
    779       "options": [
    780         {
    781           "text": "time.perf_counter()",
    782           "points": 1,
    783           "id": "A"
    784         },
    785         {
    786           "text": "datetime.now()",
    787           "points": 0,
    788           "id": "B"
    789         },
    790         {
    791           "text": "time.sleep()",
    792           "points": 0,
    793           "id": "C"
    794         }
    795       ]
    796     },
    797     {
    798       "domain_id": "PY_DATETIME",
    799       "qtype": "multiple_choice",
    800       "text": "Welche Formate sind für Logs/Datenaustausch robust?",
    801       "options": [
    802         {
    803           "text": "ISO-8601 (z. B. 2025-10-20T12:00:00Z)",
    804           "points": 1,
    805           "id": "A"
    806         },
    807         {
    808           "text": "Unix-Timestamp",
    809           "points": 1,
    810           "id": "B"
    811         },
    812         {
    813           "text": "Locale-abhängige Strings",
    814           "points": 0,
    815           "id": "C"
    816         },
    817         {
    818           "text": "Freitext ohne Norm",
    819           "points": 0,
    820           "id": "D"
    821         }
    822       ]
    823     },
    824     {
    825       "domain_id": "PY_DATETIME",
    826       "qtype": "single_choice",
    827       "text": "Wie erhält man 'jetzt in UTC' als aware datetime?",
    828       "options": [
    829         {
    830           "text": "datetime.now(timezone.utc)",
    831           "points": 1,
    832           "id": "A"
    833         },
    834         {
    835           "text": "datetime.utcnow()",
    836           "points": 0,
    837           "id": "B"
    838         },
    839         {
    840           "text": "datetime.now()",
    841           "points": 0,
    842           "id": "C"
    843         }
    844       ]
    845     },
    846     {
    847       "domain_id": "PY_NAMESPACES",
    848       "qtype": "single_choice",
    849       "text": "Wofür steht LEGB?",
    850       "options": [
    851         {
    852           "text": "Local, Enclosing, Global, Builtins (Namensauflösung)",
    853           "points": 1,
    854           "id": "A"
    855         },
    856         {
    857           "text": "Library, Env, Git, Bytecode",
    858           "points": 0,
    859           "id": "B"
    860         },
    861         {
    862           "text": "List, Eval, Generator, Bytes",
    863           "points": 0,
    864           "id": "C"
    865         }
    866       ]
    867     },
    868     {
    869       "domain_id": "PY_NAMESPACES",
    870       "qtype": "multiple_choice",
    871       "text": "Welche Aussagen zu global/nonlocal sind korrekt?",
    872       "options": [
    873         {
    874           "text": "global bezieht sich auf das Modul-Namespace.",
    875           "points": 1,
    876           "id": "A"
    877         },
    878         {
    879           "text": "nonlocal greift auf die nächst-äußere Funktionsvariable zu.",
    880           "points": 1,
    881           "id": "B"
    882         },
    883         {
    884           "text": "nonlocal kann globale Variablen binden.",
    885           "points": 0,
    886           "id": "C"
    887         },
    888         {
    889           "text": "global wirkt nur in Klassenmethoden.",
    890           "points": 0,
    891           "id": "D"
    892         }
    893       ]
    894     },
    895     {
    896       "domain_id": "PY_NAMESPACES",
    897       "qtype": "single_choice",
    898       "text": "Wie markiert man interne Modulnamen konventionell?",
    899       "options": [
    900         {
    901           "text": "Mit führendem Unterstrich: _name",
    902           "points": 1,
    903           "id": "A"
    904         },
    905         {
    906           "text": "Mit Großschreibung",
    907           "points": 0,
    908           "id": "B"
    909         },
    910         {
    911           "text": "Mit @internal",
    912           "points": 0,
    913           "id": "C"
    914         }
    915       ]
    916     },
    917     {
    918       "domain_id": "PY_NAMESPACES",
    919       "qtype": "multiple_choice",
    920       "text": "Gute Praktiken gegen Namenskonflikte:",
    921       "options": [
    922         {
    923           "text": "Keine Wildcards: kein `from x import *`.",
    924           "points": 1,
    925           "id": "A"
    926         },
    927         {
    928           "text": "Aussagekräftige Modul-/Paketnamen.",
    929           "points": 1,
    930           "id": "B"
    931         },
    932         {
    933           "text": "Konstante Namen in UPPER_CASE.",
    934           "points": 1,
    935           "id": "C"
    936         },
    937         {
    938           "text": "Alles in eine einzige Datei packen.",
    939           "points": 0,
    940           "id": "D"
    941         }
    942       ]
    943     },
    944     {
    945       "domain_id": "PY_NAMESPACES",
    946       "qtype": "single_choice",
    947       "text": "Wozu dient __all__ in einem Modul?",
    948       "options": [
    949         {
    950           "text": "Steuert Exporte bei `from mod import *`.",
    951           "points": 1,
    952           "id": "A"
    953         },
    954         {
    955           "text": "Definiert die Versionsnummer.",
    956           "points": 0,
    957           "id": "B"
    958         },
    959         {
    960           "text": "Erzeugt Docstrings automatisch.",
    961           "points": 0,
    962           "id": "C"
    963         }
    964       ]
    965     },
    966     {
    967       "domain_id": "PY_MODULES",
    968       "qtype": "single_choice",
    969       "text": "Was passiert mit Top-Level-Code beim Import?",
    970       "options": [
    971         {
    972           "text": "Er wird genau einmal ausgeführt (Initialisierung).",
    973           "points": 1,
    974           "id": "A"
    975         },
    976         {
    977           "text": "Er wird bei jedem Attributzugriff neu ausgeführt.",
    978           "points": 0,
    979           "id": "B"
    980         },
    981         {
    982           "text": "Er wird ignoriert.",
    983           "points": 0,
    984           "id": "C"
    985         }
    986       ]
    987     },
    988     {
    989       "domain_id": "PY_MODULES",
    990       "qtype": "multiple_choice",
    991       "text": "Welche Aussagen zu Imports stimmen?",
    992       "options": [
    993         {
    994           "text": "Module werden in sys.modules gecacht.",
    995           "points": 1,
    996           "id": "A"
    997         },
    998         {
    999           "text": "Relative Importe funktionieren nur innerhalb von Paketen.",
    1000           "points": 1,
    1001           "id": "B"
    1002         },
    1003         {
    1004           "text": "`if __name__ == '__main__':` trennt Skript-Start vom Import.",
    1005           "points": 1,
    1006           "id": "C"
    1007         },
    1008         {
    1009           "text": "`from x import *` ist immer empfohlen.",
    1010           "points": 0,
    1011           "id": "D"
    1012         }
    1013       ]
    1014     },
    1015     {
    1016       "domain_id": "PY_MODULES",
    1017       "qtype": "single_choice",
    1018       "text": "Wie führt man ein Modul als Skript aus?",
    1019       "options": [
    1020         {
    1021           "text": "python -m paket.modul",
    1022           "points": 1,
    1023           "id": "A"
    1024         },
    1025         {
    1026           "text": "python paket/modul",
    1027           "points": 0,
    1028           "id": "B"
    1029         },
    1030         {
    1031           "text": "run paket.modul",
    1032           "points": 0,
    1033           "id": "C"
    1034         }
    1035       ]
    1036     },
    1037     {
    1038       "domain_id": "PY_MODULES",
    1039       "qtype": "multiple_choice",
    1040       "text": "Welche Werkzeuge sind für Paket-Ressourcen nützlich?",
    1041       "options": [
    1042         {
    1043           "text": "importlib.resources",
    1044           "points": 1,
    1045           "id": "A"
    1046         },
    1047         {
    1048           "text": "pkgutil",
    1049           "points": 1,
    1050           "id": "B"
    1051         },
    1052         {
    1053           "text": "random.resources",
    1054           "points": 0,
    1055           "id": "C"
    1056         },
    1057         {
    1058           "text": "sys.path zum Lesen beliebiger Dateien",
    1059           "points": 0,
    1060           "id": "D"
    1061         }
    1062       ]
    1063     },
    1064     {
    1065       "domain_id": "PY_MODULES",
    1066       "qtype": "single_choice",
    1067       "text": "Wie bindet man einen Modul-Logger korrekt?",
    1068       "options": [
    1069         {
    1070           "text": "logger = logging.getLogger(__name__)",
    1071           "points": 1,
    1072           "id": "A"
    1073         },
    1074         {
    1075           "text": "logger = logging.getLogger()",
    1076           "points": 0,
    1077           "id": "B"
    1078         },
    1079         {
    1080           "text": "logger = print",
    1081           "points": 0,
    1082           "id": "C"
    1083         }
    1084       ]
    1085     },
    1086     {
    1087       "domain_id": "PY_OOP",
    1088       "qtype": "single_choice",
    1089       "text": "Wofür steht @property?",
    1090       "options": [
    1091         {
    1092           "text": "Berechnetes Attribut wie ein Getter aufrufbar.",
    1093           "points": 1,
    1094           "id": "A"
    1095         },
    1096         {
    1097           "text": "Markiert eine Klassenmethode.",
    1098           "points": 0,
    1099           "id": "B"
    1100         },
    1101         {
    1102           "text": "Erstellt automatisch __init__.",
    1103           "points": 0,
    1104           "id": "C"
    1105         }
    1106       ]
    1107     },
    1108     {
    1109       "domain_id": "PY_OOP",
    1110       "qtype": "multiple_choice",
    1111       "text": "Welche Aussagen zu Klassen-/staticmethods sind korrekt?",
    1112       "options": [
    1113         {
    1114           "text": "@classmethod erhält die Klasse (cls).",
    1115           "points": 1,
    1116           "id": "A"
    1117         },
    1118         {
    1119           "text": "@staticmethod erhält weder self noch cls.",
    1120           "points": 1,
    1121           "id": "B"
    1122         },
    1123         {
    1124           "text": "@classmethod == @staticmethod",
    1125           "points": 0,
    1126           "id": "C"
    1127         },
    1128         {
    1129           "text": "Beide können über die Klasse aufgerufen werden.",
    1130           "points": 1,
    1131           "id": "D"
    1132         }
    1133       ]
    1134     },
    1135     {
    1136       "domain_id": "PY_OOP",
    1137       "qtype": "single_choice",
    1138       "text": "Wie vergleicht man zwei Instanzen auf Wertgleichheit sinnvoll?",
    1139       "options": [
    1140         {
    1141           "text": "__eq__ implementieren (und __hash__ bei Immutables).",
    1142           "points": 1,
    1143           "id": "A"
    1144         },
    1145         {
    1146           "text": "Nur __repr__ implementieren.",
    1147           "points": 0,
    1148           "id": "B"
    1149         },
    1150         {
    1151           "text": "Mit 'is' statt '==' vergleichen.",
    1152           "points": 0,
    1153           "id": "C"
    1154         }
    1155       ]
    1156     },
    1157     {
    1158       "domain_id": "PY_OOP",
    1159       "qtype": "multiple_choice",
    1160       "text": "Welche Methoden sind für Containerklassen nützlich?",
    1161       "options": [
    1162         {
    1163           "text": "__len__",
    1164           "points": 1,
    1165           "id": "A"
    1166         },
    1167         {
    1168           "text": "__iter__",
    1169           "points": 1,
    1170           "id": "B"
    1171         },
    1172         {
    1173           "text": "__getitem__",
    1174           "points": 1,
    1175           "id": "C"
    1176         },
    1177         {
    1178           "text": "__cmp__",
    1179           "points": 0,
    1180           "id": "D"
    1181         }
    1182       ]
    1183     },
    1184     {
    1185       "domain_id": "PY_OOP",
    1186       "qtype": "single_choice",
    1187       "text": "Welche Basisklasse nutzt man für abstrakte Klassen?",
    1188       "options": [
    1189         {
    1190           "text": "abc.ABC",
    1191           "points": 1,
    1192           "id": "A"
    1193         },
    1194         {
    1195           "text": "typing.Any",
    1196           "points": 0,
    1197           "id": "B"
    1198         },
    1199         {
    1200           "text": "objectonly",
    1201           "points": 0,
    1202           "id": "C"
    1203         }
    1204       ]
    1205     },
    1206     {
    1207       "domain_id": "PY_EXCEPTIONS",
    1208       "qtype": "single_choice",
    1209       "text": "Wie fängt man eine konkrete Exception?",
    1210       "options": [
    1211         {
    1212           "text": "try: ... except ValueError:",
    1213           "points": 1,
    1214           "id": "A"
    1215         },
    1216         {
    1217           "text": "try: ... catch ValueError:",
    1218           "points": 0,
    1219           "id": "B"
    1220         },
    1221         {
    1222           "text": "except(ValueError): ... ohne try",
    1223           "points": 0,
    1224           "id": "C"
    1225         }
    1226       ]
    1227     },
    1228     {
    1229       "domain_id": "PY_EXCEPTIONS",
    1230       "qtype": "multiple_choice",
    1231       "text": "Welche Blöcke kann ein try-Statement enthalten?",
    1232       "options": [
    1233         {
    1234           "text": "except",
    1235           "points": 1,
    1236           "id": "A"
    1237         },
    1238         {
    1239           "text": "else",
    1240           "points": 1,
    1241           "id": "B"
    1242         },
    1243         {
    1244           "text": "finally",
    1245           "points": 1,
    1246           "id": "C"
    1247         },
    1248         {
    1249           "text": "then",
    1250           "points": 0,
    1251           "id": "D"
    1252         }
    1253       ]
    1254     },
    1255     {
    1256       "domain_id": "PY_EXCEPTIONS",
    1257       "qtype": "single_choice",
    1258       "text": "Wie re-raiset man dieselbe Exception innerhalb des except-Blocks unverändert?",
    1259       "options": [
    1260         {
    1261           "text": "raise  (ohne Argumente)",
    1262           "points": 1,
    1263           "id": "A"
    1264         },
    1265         {
    1266           "text": "return e",
    1267           "points": 0,
    1268           "id": "B"
    1269         },
    1270         {
    1271           "text": "throw e",
    1272           "points": 0,
    1273           "id": "C"
    1274         }
    1275       ]
    1276     },
    1277     {
    1278       "domain_id": "PY_EXCEPTIONS",
    1279       "qtype": "multiple_choice",
    1280       "text": "Gute Praktiken beim Fehler-Handling:",
    1281       "options": [
    1282         {
    1283           "text": "Spezifische Exceptions abfangen.",
    1284           "points": 1,
    1285           "id": "A"
    1286         },
    1287         {
    1288           "text": "Kontext mit `raise ... from e` erhalten.",
    1289           "points": 1,
    1290           "id": "B"
    1291         },
    1292         {
    1293           "text": "Bare `except:` nur in seltenen Fällen.",
    1294           "points": 1,
    1295           "id": "C"
    1296         },
    1297         {
    1298           "text": "Alle Fehler still ignorieren.",
    1299           "points": 0,
    1300           "id": "D"
    1301         }
    1302       ]
    1303     },
    1304     {
    1305       "domain_id": "PY_EXCEPTIONS",
    1306       "qtype": "single_choice",
    1307       "text": "Welche Ausnahmen sind KEINE Unterklassen von Exception?",
    1308       "options": [
    1309         {
    1310           "text": "SystemExit / KeyboardInterrupt / GeneratorExit",
    1311           "points": 1,
    1312           "id": "A"
    1313         },
    1314         {
    1315           "text": "RuntimeError",
    1316           "points": 0,
    1317           "id": "B"
    1318         },
    1319         {
    1320           "text": "ValueError",
    1321           "points": 0,
    1322           "id": "C"
     489          "points": 0
    1323490        }
    1324491      ]
  • gui/domain_editor_dialog.py

    r21f0ba8 re70dd79  
    4040
    4141        self.transient(parent)
    42         self.grab_set()
    43         parent.wait_window(self)
     42        self.protocol("WM_DELETE_WINDOW", self.on_close)
    4443
    4544    def on_ok(self):
     
    5150            messagebox.showerror("Missing data", "Domain ID and Full name are required.")
    5251            return
    53 
    5452        self.result = {"domain_id": domain_id, "fullname": fullname,
    5553                       "description": desc}
    5654        self.destroy()
     55
     56    def on_close(self):
     57        self.result = None
     58        self.destroy()
  • gui/domain_management_dialog.py

    r21f0ba8 re70dd79  
    11import tkinter as tk
    22from tkinter import ttk, messagebox
    3 from dataclasses import dataclass
     3
     4from flexoentity import Domain
    45
    56from .domain_editor_dialog import DomainEditDialog
    67
    7 from flexoentity import Domain
    88
    99class DomainManagementDialog(tk.Toplevel):
     
    1818
    1919        self.result = None
    20        
     20
    2121        self._build_ui()
    2222        self._populate()
     
    2424        self.transient(parent)
    2525        self.grab_set()
    26         parent.wait_window(self)
     26        self.protocol("WM_DELETE_WINDOW", self.on_close)
    2727
    28     # ──────────────────────────────────────────────────────────────────────
    2928    def _build_ui(self):
    3029        # Treeview
     
    4039
    4140        # Buttons
    42         ttk.Button(self, text="Add", command=self.on_add).grid(row=1, column=0, padx=4, pady=6, sticky="ew")
    43         ttk.Button(self, text="Edit", command=self.on_edit).grid(row=1, column=1, padx=4, pady=6, sticky="ew")
    44         ttk.Button(self, text="Delete", command=self.on_delete).grid(row=1, column=2, padx=4, pady=6, sticky="ew")
    45         ttk.Button(self, text="Close", command=self.on_close).grid(row=1, column=3, padx=4, pady=6, sticky="ew")
     41        ttk.Button(self, text="Add", command=self.on_add).grid(row=1, column=0,
     42                                                               padx=4, pady=6,
     43                                                               sticky="ew")
     44        ttk.Button(self, text="Edit", command=self.on_edit).grid(row=1, column=1,
     45                                                                 padx=4, pady=6,
     46                                                                 sticky="ew")
     47        ttk.Button(self, text="Delete", command=self.on_delete).grid(row=1, column=2,
     48                                                                     padx=4, pady=6,
     49                                                                     sticky="ew")
     50        ttk.Button(self, text="Close", command=self.on_close).grid(row=1, column=3,
     51                                                                   padx=4, pady=6,
     52                                                                   sticky="ew")
    4653
    47     # ──────────────────────────────────────────────────────────────────────
    4854    def _populate(self):
    4955        self.tree.delete(*self.tree.get_children())
     
    5258                             values=(dom.domain_id, dom.fullname, dom.description))
    5359
    54     # ──────────────────────────────────────────────────────────────────────
    5560    def _get_selected(self):
    5661        sel = self.tree.selection()
     
    5863            return None
    5964        return self.domain_manager.get(sel[0])
    60     # ──────────────────────────────────────────────────────────────────────
     65
    6166    def on_add(self):
     67        self.grab_release()
    6268        dlg = DomainEditDialog(self)
     69        self.wait_window(dlg)
     70        self.grab_set()
    6371        if dlg.result:
    6472            if dlg.result["domain_id"] in self.domain_manager.all_domain_ids():
    65                 messagebox.showerror("Duplicate", f"Domain {dlg.result["domain_id"]} already exists.")
     73                messagebox.showerror("Duplicate",
     74                                     f"Domain {dlg.result['domain_id']} already exists.")
    6675                return
    6776
    6877            try:
    69                 Domain.with_domain_id(domain_id=dlg.result["domain_id"],
    70                                       fullname=dlg.result["fullname"],
    71                                       description=dlg.result["description"])
     78                self.domain_manager.register(
     79                    Domain.with_domain_id(domain_id=dlg.result["domain_id"],
     80                                          fullname=dlg.result["fullname"],
     81                                          description=dlg.result["description"]))
    7282            except ValueError as e:
    7383                messagebox.showerror("Format error", f"The values you entered are invalid {e}" )
     
    7585            self._populate()
    7686
    77     # ──────────────────────────────────────────────────────────────────────
    7887    def on_edit(self):
    7988        dom = self._get_selected()
     
    8190            messagebox.showinfo("No selection", "Select a domain first.")
    8291            return
     92        self.grab_release()
    8393        dlg = DomainEditDialog(self, dom, editing=True)
     94        self.wait_window(dlg)
     95        self.grab_set()
    8496        if dlg.result:
    8597            dom.fullname = dlg.result["fullname"]
     
    8799            self._populate()
    88100
    89     # ──────────────────────────────────────────────────────────────────────
    90101    def on_delete(self):
    91102        dom = self._get_selected()
     
    104115    # FIXME: Return a real result like current list of domains
    105116    def on_close(self):
    106         self.result = "Whatever"
     117        self.result = self.domain_manager
     118        self.grab_release()
    107119        self.destroy()
     120       
  • gui/gui.py

    r21f0ba8 re70dd79  
    199199
    200200        catalog = QuestionCatalog.from_dict(data)
     201        for each in catalog.domains:
     202            self.domain_manager.register(each)
    201203        self.catalog_manager.add_catalog(catalog)
    202204        self.catalog_manager.set_active(catalog.flexo_id)
     
    632634        self.log_action(f"Pasted {len(transferred)} question(s) to '{target.title}'.")
    633635
    634     def add_domain(self):
     636    def manage_domain(self):
    635637        active = self.require_active_catalog()
    636638
     
    639641
    640642        dlg = DomainManagementDialog(self, self.domain_manager)
    641         self.wait_window()
    642         if not dlg:
    643             return
    644 
    645         try:
    646             self.domain_manager.register(Domain.with_domain_id(dlg.result))
    647             self.log_action(f"Added - Domain '{dlg.result}' added to catalog '{active.catalog_id}'.")
    648         except ValueError as e:
    649             messagebox.showerror("Invalid Input", str(e))
    650             return
     643        self.wait_window(dlg)
     644        return
    651645
    652646    def on_quit(self):
  • gui/menu.py

    r21f0ba8 re70dd79  
    2323
    2424        domain_menu = tk.Menu(self, tearoff=0)
    25         domain_menu.add_command(label="Add domain", command=parent.add_domain)
     25        domain_menu.add_command(label="Manage domain", command=parent.manage_domain)
    2626        self.add_cascade(label="Domains", menu=domain_menu)
    2727
Note: See TracChangeset for help on using the changeset viewer.