pythonlings 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonlings/__init__.py +0 -0
- pythonlings/__main__.py +4 -0
- pythonlings/app.py +84 -0
- pythonlings/cli.py +318 -0
- pythonlings/core/__init__.py +0 -0
- pythonlings/core/curriculum.py +106 -0
- pythonlings/core/docs.py +68 -0
- pythonlings/core/exercise.py +33 -0
- pythonlings/core/manifest.py +110 -0
- pythonlings/core/reset.py +43 -0
- pythonlings/core/runner.py +114 -0
- pythonlings/core/solutions.py +17 -0
- pythonlings/core/state.py +86 -0
- pythonlings/curriculum/checks/async/async1.py +4 -0
- pythonlings/curriculum/checks/async/async10.py +4 -0
- pythonlings/curriculum/checks/async/async2.py +4 -0
- pythonlings/curriculum/checks/async/async3.py +4 -0
- pythonlings/curriculum/checks/async/async4.py +4 -0
- pythonlings/curriculum/checks/async/async5.py +4 -0
- pythonlings/curriculum/checks/async/async6.py +4 -0
- pythonlings/curriculum/checks/async/async7.py +4 -0
- pythonlings/curriculum/checks/async/async8.py +2 -0
- pythonlings/curriculum/checks/async/async9.py +4 -0
- pythonlings/curriculum/checks/classes/classes1.py +3 -0
- pythonlings/curriculum/checks/classes/classes10.py +5 -0
- pythonlings/curriculum/checks/classes/classes11.py +5 -0
- pythonlings/curriculum/checks/classes/classes12.py +16 -0
- pythonlings/curriculum/checks/classes/classes2.py +3 -0
- pythonlings/curriculum/checks/classes/classes3.py +4 -0
- pythonlings/curriculum/checks/classes/classes4.py +4 -0
- pythonlings/curriculum/checks/classes/classes5.py +5 -0
- pythonlings/curriculum/checks/classes/classes6.py +4 -0
- pythonlings/curriculum/checks/classes/classes7.py +3 -0
- pythonlings/curriculum/checks/classes/classes8.py +3 -0
- pythonlings/curriculum/checks/classes/classes9.py +4 -0
- pythonlings/curriculum/checks/collections/collections1.py +4 -0
- pythonlings/curriculum/checks/collections/collections10.py +15 -0
- pythonlings/curriculum/checks/collections/collections2.py +4 -0
- pythonlings/curriculum/checks/collections/collections3.py +5 -0
- pythonlings/curriculum/checks/collections/collections4.py +4 -0
- pythonlings/curriculum/checks/collections/collections5.py +5 -0
- pythonlings/curriculum/checks/collections/collections6.py +6 -0
- pythonlings/curriculum/checks/collections/collections7.py +4 -0
- pythonlings/curriculum/checks/collections/collections8.py +5 -0
- pythonlings/curriculum/checks/collections/collections9.py +9 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions1.py +3 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions10.py +5 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions2.py +3 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions3.py +3 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions4.py +6 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions5.py +3 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions6.py +3 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions7.py +4 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions8.py +5 -0
- pythonlings/curriculum/checks/comprehensions/comprehensions9.py +3 -0
- pythonlings/curriculum/checks/conditionals/conditionals1.py +2 -0
- pythonlings/curriculum/checks/conditionals/conditionals10.py +10 -0
- pythonlings/curriculum/checks/conditionals/conditionals2.py +5 -0
- pythonlings/curriculum/checks/conditionals/conditionals3.py +9 -0
- pythonlings/curriculum/checks/conditionals/conditionals4.py +7 -0
- pythonlings/curriculum/checks/conditionals/conditionals5.py +9 -0
- pythonlings/curriculum/checks/conditionals/conditionals6.py +7 -0
- pythonlings/curriculum/checks/conditionals/conditionals7.py +7 -0
- pythonlings/curriculum/checks/conditionals/conditionals8.py +9 -0
- pythonlings/curriculum/checks/conditionals/conditionals9.py +7 -0
- pythonlings/curriculum/checks/context_managers/context_managers1.py +15 -0
- pythonlings/curriculum/checks/context_managers/context_managers2.py +10 -0
- pythonlings/curriculum/checks/context_managers/context_managers3.py +2 -0
- pythonlings/curriculum/checks/context_managers/context_managers4.py +3 -0
- pythonlings/curriculum/checks/context_managers/context_managers5.py +5 -0
- pythonlings/curriculum/checks/context_managers/context_managers6.py +4 -0
- pythonlings/curriculum/checks/context_managers/context_managers7.py +16 -0
- pythonlings/curriculum/checks/context_managers/context_managers8.py +15 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses1.py +3 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses2.py +5 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses3.py +4 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses4.py +3 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses5.py +9 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses6.py +6 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses7.py +6 -0
- pythonlings/curriculum/checks/dataclasses/dataclasses8.py +5 -0
- pythonlings/curriculum/checks/datetime/datetime1.py +4 -0
- pythonlings/curriculum/checks/datetime/datetime2.py +4 -0
- pythonlings/curriculum/checks/datetime/datetime3.py +6 -0
- pythonlings/curriculum/checks/datetime/datetime4.py +2 -0
- pythonlings/curriculum/checks/datetime/datetime5.py +2 -0
- pythonlings/curriculum/checks/datetime/datetime6.py +2 -0
- pythonlings/curriculum/checks/datetime/datetime7.py +2 -0
- pythonlings/curriculum/checks/datetime/datetime8.py +5 -0
- pythonlings/curriculum/checks/decorators/decorators1.py +8 -0
- pythonlings/curriculum/checks/decorators/decorators10.py +12 -0
- pythonlings/curriculum/checks/decorators/decorators2.py +5 -0
- pythonlings/curriculum/checks/decorators/decorators3.py +4 -0
- pythonlings/curriculum/checks/decorators/decorators4.py +7 -0
- pythonlings/curriculum/checks/decorators/decorators5.py +5 -0
- pythonlings/curriculum/checks/decorators/decorators6.py +8 -0
- pythonlings/curriculum/checks/decorators/decorators7.py +12 -0
- pythonlings/curriculum/checks/decorators/decorators8.py +20 -0
- pythonlings/curriculum/checks/decorators/decorators9.py +4 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries1.py +3 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries10.py +5 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries2.py +2 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries3.py +5 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries4.py +5 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries5.py +11 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries6.py +4 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries7.py +5 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries8.py +4 -0
- pythonlings/curriculum/checks/dictionaries/dictionaries9.py +4 -0
- pythonlings/curriculum/checks/enums/enums1.py +4 -0
- pythonlings/curriculum/checks/enums/enums2.py +3 -0
- pythonlings/curriculum/checks/enums/enums3.py +3 -0
- pythonlings/curriculum/checks/enums/enums4.py +2 -0
- pythonlings/curriculum/checks/enums/enums5.py +4 -0
- pythonlings/curriculum/checks/enums/enums6.py +3 -0
- pythonlings/curriculum/checks/exceptions/exceptions1.py +4 -0
- pythonlings/curriculum/checks/exceptions/exceptions10.py +39 -0
- pythonlings/curriculum/checks/exceptions/exceptions2.py +5 -0
- pythonlings/curriculum/checks/exceptions/exceptions3.py +8 -0
- pythonlings/curriculum/checks/exceptions/exceptions4.py +5 -0
- pythonlings/curriculum/checks/exceptions/exceptions5.py +10 -0
- pythonlings/curriculum/checks/exceptions/exceptions6.py +17 -0
- pythonlings/curriculum/checks/exceptions/exceptions7.py +15 -0
- pythonlings/curriculum/checks/exceptions/exceptions8.py +6 -0
- pythonlings/curriculum/checks/exceptions/exceptions9.py +16 -0
- pythonlings/curriculum/checks/file_io/file_io1.py +15 -0
- pythonlings/curriculum/checks/file_io/file_io10.py +16 -0
- pythonlings/curriculum/checks/file_io/file_io2.py +10 -0
- pythonlings/curriculum/checks/file_io/file_io3.py +15 -0
- pythonlings/curriculum/checks/file_io/file_io4.py +14 -0
- pythonlings/curriculum/checks/file_io/file_io5.py +15 -0
- pythonlings/curriculum/checks/file_io/file_io6.py +10 -0
- pythonlings/curriculum/checks/file_io/file_io7.py +15 -0
- pythonlings/curriculum/checks/file_io/file_io8.py +12 -0
- pythonlings/curriculum/checks/file_io/file_io9.py +10 -0
- pythonlings/curriculum/checks/functional/functional1.py +4 -0
- pythonlings/curriculum/checks/functional/functional10.py +7 -0
- pythonlings/curriculum/checks/functional/functional2.py +5 -0
- pythonlings/curriculum/checks/functional/functional3.py +3 -0
- pythonlings/curriculum/checks/functional/functional4.py +4 -0
- pythonlings/curriculum/checks/functional/functional5.py +6 -0
- pythonlings/curriculum/checks/functional/functional6.py +8 -0
- pythonlings/curriculum/checks/functional/functional7.py +8 -0
- pythonlings/curriculum/checks/functional/functional8.py +7 -0
- pythonlings/curriculum/checks/functional/functional9.py +8 -0
- pythonlings/curriculum/checks/functions/functions1.py +9 -0
- pythonlings/curriculum/checks/functions/functions10.py +12 -0
- pythonlings/curriculum/checks/functions/functions2.py +4 -0
- pythonlings/curriculum/checks/functions/functions3.py +6 -0
- pythonlings/curriculum/checks/functions/functions4.py +6 -0
- pythonlings/curriculum/checks/functions/functions5.py +6 -0
- pythonlings/curriculum/checks/functions/functions6.py +8 -0
- pythonlings/curriculum/checks/functions/functions7.py +7 -0
- pythonlings/curriculum/checks/functions/functions8.py +6 -0
- pythonlings/curriculum/checks/functions/functions9.py +7 -0
- pythonlings/curriculum/checks/generators/generators1.py +4 -0
- pythonlings/curriculum/checks/generators/generators10.py +7 -0
- pythonlings/curriculum/checks/generators/generators2.py +3 -0
- pythonlings/curriculum/checks/generators/generators3.py +4 -0
- pythonlings/curriculum/checks/generators/generators4.py +9 -0
- pythonlings/curriculum/checks/generators/generators5.py +10 -0
- pythonlings/curriculum/checks/generators/generators6.py +8 -0
- pythonlings/curriculum/checks/generators/generators7.py +11 -0
- pythonlings/curriculum/checks/generators/generators8.py +8 -0
- pythonlings/curriculum/checks/generators/generators9.py +13 -0
- pythonlings/curriculum/checks/itertools/itertools1.py +2 -0
- pythonlings/curriculum/checks/itertools/itertools2.py +2 -0
- pythonlings/curriculum/checks/itertools/itertools3.py +2 -0
- pythonlings/curriculum/checks/itertools/itertools4.py +2 -0
- pythonlings/curriculum/checks/itertools/itertools5.py +2 -0
- pythonlings/curriculum/checks/itertools/itertools6.py +2 -0
- pythonlings/curriculum/checks/itertools/itertools7.py +2 -0
- pythonlings/curriculum/checks/itertools/itertools8.py +3 -0
- pythonlings/curriculum/checks/json/json1.py +3 -0
- pythonlings/curriculum/checks/json/json2.py +2 -0
- pythonlings/curriculum/checks/json/json3.py +2 -0
- pythonlings/curriculum/checks/json/json4.py +2 -0
- pythonlings/curriculum/checks/json/json5.py +2 -0
- pythonlings/curriculum/checks/json/json6.py +2 -0
- pythonlings/curriculum/checks/json/json7.py +3 -0
- pythonlings/curriculum/checks/json/json8.py +4 -0
- pythonlings/curriculum/checks/lists/lists1.py +2 -0
- pythonlings/curriculum/checks/lists/lists10.py +5 -0
- pythonlings/curriculum/checks/lists/lists2.py +3 -0
- pythonlings/curriculum/checks/lists/lists3.py +4 -0
- pythonlings/curriculum/checks/lists/lists4.py +3 -0
- pythonlings/curriculum/checks/lists/lists5.py +4 -0
- pythonlings/curriculum/checks/lists/lists6.py +3 -0
- pythonlings/curriculum/checks/lists/lists7.py +4 -0
- pythonlings/curriculum/checks/lists/lists8.py +7 -0
- pythonlings/curriculum/checks/lists/lists9.py +4 -0
- pythonlings/curriculum/checks/loops/loops1.py +2 -0
- pythonlings/curriculum/checks/loops/loops10.py +6 -0
- pythonlings/curriculum/checks/loops/loops2.py +2 -0
- pythonlings/curriculum/checks/loops/loops3.py +2 -0
- pythonlings/curriculum/checks/loops/loops4.py +3 -0
- pythonlings/curriculum/checks/loops/loops5.py +2 -0
- pythonlings/curriculum/checks/loops/loops6.py +2 -0
- pythonlings/curriculum/checks/loops/loops7.py +2 -0
- pythonlings/curriculum/checks/loops/loops8.py +2 -0
- pythonlings/curriculum/checks/loops/loops9.py +3 -0
- pythonlings/curriculum/checks/modules/modules1.py +2 -0
- pythonlings/curriculum/checks/modules/modules2.py +2 -0
- pythonlings/curriculum/checks/modules/modules3.py +5 -0
- pythonlings/curriculum/checks/modules/modules4.py +8 -0
- pythonlings/curriculum/checks/modules/modules5.py +7 -0
- pythonlings/curriculum/checks/modules/modules6.py +4 -0
- pythonlings/curriculum/checks/modules/modules7.py +4 -0
- pythonlings/curriculum/checks/modules/modules8.py +15 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced1.py +4 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced10.py +3 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced11.py +3 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced12.py +4 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced2.py +4 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced3.py +3 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced4.py +3 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced5.py +3 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced6.py +2 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced7.py +3 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced8.py +3 -0
- pythonlings/curriculum/checks/oop_advanced/oop_advanced9.py +4 -0
- pythonlings/curriculum/checks/pathlib/pathlib1.py +5 -0
- pythonlings/curriculum/checks/pathlib/pathlib2.py +4 -0
- pythonlings/curriculum/checks/pathlib/pathlib3.py +6 -0
- pythonlings/curriculum/checks/pathlib/pathlib4.py +4 -0
- pythonlings/curriculum/checks/pathlib/pathlib5.py +4 -0
- pythonlings/curriculum/checks/pathlib/pathlib6.py +5 -0
- pythonlings/curriculum/checks/recursion/recursion1.py +5 -0
- pythonlings/curriculum/checks/recursion/recursion2.py +5 -0
- pythonlings/curriculum/checks/recursion/recursion3.py +6 -0
- pythonlings/curriculum/checks/recursion/recursion4.py +6 -0
- pythonlings/curriculum/checks/recursion/recursion5.py +7 -0
- pythonlings/curriculum/checks/recursion/recursion6.py +7 -0
- pythonlings/curriculum/checks/recursion/recursion7.py +7 -0
- pythonlings/curriculum/checks/recursion/recursion8.py +15 -0
- pythonlings/curriculum/checks/regex/regex1.py +3 -0
- pythonlings/curriculum/checks/regex/regex10.py +17 -0
- pythonlings/curriculum/checks/regex/regex2.py +3 -0
- pythonlings/curriculum/checks/regex/regex3.py +2 -0
- pythonlings/curriculum/checks/regex/regex4.py +4 -0
- pythonlings/curriculum/checks/regex/regex5.py +4 -0
- pythonlings/curriculum/checks/regex/regex6.py +6 -0
- pythonlings/curriculum/checks/regex/regex7.py +5 -0
- pythonlings/curriculum/checks/regex/regex8.py +6 -0
- pythonlings/curriculum/checks/regex/regex9.py +8 -0
- pythonlings/curriculum/checks/sets/sets1.py +3 -0
- pythonlings/curriculum/checks/sets/sets10.py +15 -0
- pythonlings/curriculum/checks/sets/sets2.py +5 -0
- pythonlings/curriculum/checks/sets/sets3.py +5 -0
- pythonlings/curriculum/checks/sets/sets4.py +3 -0
- pythonlings/curriculum/checks/sets/sets5.py +3 -0
- pythonlings/curriculum/checks/sets/sets6.py +3 -0
- pythonlings/curriculum/checks/sets/sets7.py +5 -0
- pythonlings/curriculum/checks/sets/sets8.py +5 -0
- pythonlings/curriculum/checks/sets/sets9.py +3 -0
- pythonlings/curriculum/checks/strings/strings1.py +3 -0
- pythonlings/curriculum/checks/strings/strings10.py +5 -0
- pythonlings/curriculum/checks/strings/strings2.py +3 -0
- pythonlings/curriculum/checks/strings/strings3.py +3 -0
- pythonlings/curriculum/checks/strings/strings4.py +2 -0
- pythonlings/curriculum/checks/strings/strings5.py +4 -0
- pythonlings/curriculum/checks/strings/strings6.py +3 -0
- pythonlings/curriculum/checks/strings/strings7.py +3 -0
- pythonlings/curriculum/checks/strings/strings8.py +3 -0
- pythonlings/curriculum/checks/strings/strings9.py +3 -0
- pythonlings/curriculum/checks/testing/testing1.py +2 -0
- pythonlings/curriculum/checks/testing/testing10.py +9 -0
- pythonlings/curriculum/checks/testing/testing11.py +7 -0
- pythonlings/curriculum/checks/testing/testing12.py +19 -0
- pythonlings/curriculum/checks/testing/testing2.py +2 -0
- pythonlings/curriculum/checks/testing/testing3.py +12 -0
- pythonlings/curriculum/checks/testing/testing4.py +6 -0
- pythonlings/curriculum/checks/testing/testing5.py +6 -0
- pythonlings/curriculum/checks/testing/testing6.py +14 -0
- pythonlings/curriculum/checks/testing/testing7.py +14 -0
- pythonlings/curriculum/checks/testing/testing8.py +27 -0
- pythonlings/curriculum/checks/testing/testing9.py +14 -0
- pythonlings/curriculum/checks/tuples/tuples1.py +3 -0
- pythonlings/curriculum/checks/tuples/tuples10.py +6 -0
- pythonlings/curriculum/checks/tuples/tuples2.py +3 -0
- pythonlings/curriculum/checks/tuples/tuples3.py +4 -0
- pythonlings/curriculum/checks/tuples/tuples4.py +3 -0
- pythonlings/curriculum/checks/tuples/tuples5.py +4 -0
- pythonlings/curriculum/checks/tuples/tuples6.py +5 -0
- pythonlings/curriculum/checks/tuples/tuples7.py +3 -0
- pythonlings/curriculum/checks/tuples/tuples8.py +4 -0
- pythonlings/curriculum/checks/tuples/tuples9.py +5 -0
- pythonlings/curriculum/checks/type_hints/type_hints1.py +3 -0
- pythonlings/curriculum/checks/type_hints/type_hints2.py +5 -0
- pythonlings/curriculum/checks/type_hints/type_hints3.py +5 -0
- pythonlings/curriculum/checks/type_hints/type_hints4.py +5 -0
- pythonlings/curriculum/checks/type_hints/type_hints5.py +6 -0
- pythonlings/curriculum/checks/type_hints/type_hints6.py +7 -0
- pythonlings/curriculum/checks/type_hints/type_hints7.py +9 -0
- pythonlings/curriculum/checks/type_hints/type_hints8.py +9 -0
- pythonlings/curriculum/checks/variables/variables1.py +7 -0
- pythonlings/curriculum/checks/variables/variables10.py +4 -0
- pythonlings/curriculum/checks/variables/variables2.py +19 -0
- pythonlings/curriculum/checks/variables/variables3.py +4 -0
- pythonlings/curriculum/checks/variables/variables4.py +2 -0
- pythonlings/curriculum/checks/variables/variables5.py +2 -0
- pythonlings/curriculum/checks/variables/variables6.py +4 -0
- pythonlings/curriculum/checks/variables/variables7.py +3 -0
- pythonlings/curriculum/checks/variables/variables8.py +4 -0
- pythonlings/curriculum/checks/variables/variables9.py +3 -0
- pythonlings/curriculum/exercises/async/async1.py +7 -0
- pythonlings/curriculum/exercises/async/async10.py +14 -0
- pythonlings/curriculum/exercises/async/async2.py +10 -0
- pythonlings/curriculum/exercises/async/async3.py +10 -0
- pythonlings/curriculum/exercises/async/async4.py +15 -0
- pythonlings/curriculum/exercises/async/async5.py +13 -0
- pythonlings/curriculum/exercises/async/async6.py +14 -0
- pythonlings/curriculum/exercises/async/async7.py +13 -0
- pythonlings/curriculum/exercises/async/async8.py +11 -0
- pythonlings/curriculum/exercises/async/async9.py +13 -0
- pythonlings/curriculum/exercises/classes/classes1.py +13 -0
- pythonlings/curriculum/exercises/classes/classes10.py +22 -0
- pythonlings/curriculum/exercises/classes/classes11.py +24 -0
- pythonlings/curriculum/exercises/classes/classes12.py +33 -0
- pythonlings/curriculum/exercises/classes/classes2.py +13 -0
- pythonlings/curriculum/exercises/classes/classes3.py +13 -0
- pythonlings/curriculum/exercises/classes/classes4.py +16 -0
- pythonlings/curriculum/exercises/classes/classes5.py +18 -0
- pythonlings/curriculum/exercises/classes/classes6.py +15 -0
- pythonlings/curriculum/exercises/classes/classes7.py +18 -0
- pythonlings/curriculum/exercises/classes/classes8.py +14 -0
- pythonlings/curriculum/exercises/classes/classes9.py +18 -0
- pythonlings/curriculum/exercises/collections/collections1.py +11 -0
- pythonlings/curriculum/exercises/collections/collections10.py +37 -0
- pythonlings/curriculum/exercises/collections/collections2.py +18 -0
- pythonlings/curriculum/exercises/collections/collections3.py +17 -0
- pythonlings/curriculum/exercises/collections/collections4.py +26 -0
- pythonlings/curriculum/exercises/collections/collections5.py +14 -0
- pythonlings/curriculum/exercises/collections/collections6.py +18 -0
- pythonlings/curriculum/exercises/collections/collections7.py +20 -0
- pythonlings/curriculum/exercises/collections/collections8.py +23 -0
- pythonlings/curriculum/exercises/collections/collections9.py +21 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions1.py +10 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions10.py +15 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions2.py +12 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions3.py +12 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions4.py +14 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions5.py +12 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions6.py +12 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions7.py +12 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions8.py +15 -0
- pythonlings/curriculum/exercises/comprehensions/comprehensions9.py +13 -0
- pythonlings/curriculum/exercises/conditionals/conditionals1.py +15 -0
- pythonlings/curriculum/exercises/conditionals/conditionals10.py +18 -0
- pythonlings/curriculum/exercises/conditionals/conditionals2.py +13 -0
- pythonlings/curriculum/exercises/conditionals/conditionals3.py +18 -0
- pythonlings/curriculum/exercises/conditionals/conditionals4.py +20 -0
- pythonlings/curriculum/exercises/conditionals/conditionals5.py +17 -0
- pythonlings/curriculum/exercises/conditionals/conditionals6.py +15 -0
- pythonlings/curriculum/exercises/conditionals/conditionals7.py +18 -0
- pythonlings/curriculum/exercises/conditionals/conditionals8.py +20 -0
- pythonlings/curriculum/exercises/conditionals/conditionals9.py +12 -0
- pythonlings/curriculum/exercises/context_managers/context_managers1.py +18 -0
- pythonlings/curriculum/exercises/context_managers/context_managers2.py +19 -0
- pythonlings/curriculum/exercises/context_managers/context_managers3.py +24 -0
- pythonlings/curriculum/exercises/context_managers/context_managers4.py +30 -0
- pythonlings/curriculum/exercises/context_managers/context_managers5.py +30 -0
- pythonlings/curriculum/exercises/context_managers/context_managers6.py +26 -0
- pythonlings/curriculum/exercises/context_managers/context_managers7.py +30 -0
- pythonlings/curriculum/exercises/context_managers/context_managers8.py +45 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses1.py +14 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses2.py +23 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses3.py +21 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses4.py +25 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses5.py +25 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses6.py +21 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses7.py +27 -0
- pythonlings/curriculum/exercises/dataclasses/dataclasses8.py +30 -0
- pythonlings/curriculum/exercises/datetime/datetime1.py +8 -0
- pythonlings/curriculum/exercises/datetime/datetime2.py +9 -0
- pythonlings/curriculum/exercises/datetime/datetime3.py +9 -0
- pythonlings/curriculum/exercises/datetime/datetime4.py +9 -0
- pythonlings/curriculum/exercises/datetime/datetime5.py +10 -0
- pythonlings/curriculum/exercises/datetime/datetime6.py +10 -0
- pythonlings/curriculum/exercises/datetime/datetime7.py +10 -0
- pythonlings/curriculum/exercises/datetime/datetime8.py +11 -0
- pythonlings/curriculum/exercises/decorators/decorators1.py +19 -0
- pythonlings/curriculum/exercises/decorators/decorators10.py +32 -0
- pythonlings/curriculum/exercises/decorators/decorators2.py +22 -0
- pythonlings/curriculum/exercises/decorators/decorators3.py +18 -0
- pythonlings/curriculum/exercises/decorators/decorators4.py +24 -0
- pythonlings/curriculum/exercises/decorators/decorators5.py +25 -0
- pythonlings/curriculum/exercises/decorators/decorators6.py +19 -0
- pythonlings/curriculum/exercises/decorators/decorators7.py +25 -0
- pythonlings/curriculum/exercises/decorators/decorators8.py +25 -0
- pythonlings/curriculum/exercises/decorators/decorators9.py +26 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries1.py +6 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries10.py +17 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries2.py +9 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries3.py +16 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries4.py +15 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries5.py +15 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries6.py +18 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries7.py +17 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries8.py +17 -0
- pythonlings/curriculum/exercises/dictionaries/dictionaries9.py +25 -0
- pythonlings/curriculum/exercises/enums/enums1.py +12 -0
- pythonlings/curriculum/exercises/enums/enums2.py +11 -0
- pythonlings/curriculum/exercises/enums/enums3.py +14 -0
- pythonlings/curriculum/exercises/enums/enums4.py +13 -0
- pythonlings/curriculum/exercises/enums/enums5.py +14 -0
- pythonlings/curriculum/exercises/enums/enums6.py +15 -0
- pythonlings/curriculum/exercises/exceptions/exceptions1.py +15 -0
- pythonlings/curriculum/exercises/exceptions/exceptions10.py +19 -0
- pythonlings/curriculum/exercises/exceptions/exceptions2.py +12 -0
- pythonlings/curriculum/exercises/exceptions/exceptions3.py +13 -0
- pythonlings/curriculum/exercises/exceptions/exceptions4.py +17 -0
- pythonlings/curriculum/exercises/exceptions/exceptions5.py +20 -0
- pythonlings/curriculum/exercises/exceptions/exceptions6.py +13 -0
- pythonlings/curriculum/exercises/exceptions/exceptions7.py +15 -0
- pythonlings/curriculum/exercises/exceptions/exceptions8.py +17 -0
- pythonlings/curriculum/exercises/exceptions/exceptions9.py +20 -0
- pythonlings/curriculum/exercises/file_io/file_io1.py +18 -0
- pythonlings/curriculum/exercises/file_io/file_io10.py +25 -0
- pythonlings/curriculum/exercises/file_io/file_io2.py +16 -0
- pythonlings/curriculum/exercises/file_io/file_io3.py +18 -0
- pythonlings/curriculum/exercises/file_io/file_io4.py +21 -0
- pythonlings/curriculum/exercises/file_io/file_io5.py +19 -0
- pythonlings/curriculum/exercises/file_io/file_io6.py +22 -0
- pythonlings/curriculum/exercises/file_io/file_io7.py +20 -0
- pythonlings/curriculum/exercises/file_io/file_io8.py +18 -0
- pythonlings/curriculum/exercises/file_io/file_io9.py +19 -0
- pythonlings/curriculum/exercises/functional/functional1.py +7 -0
- pythonlings/curriculum/exercises/functional/functional10.py +19 -0
- pythonlings/curriculum/exercises/functional/functional2.py +8 -0
- pythonlings/curriculum/exercises/functional/functional3.py +11 -0
- pythonlings/curriculum/exercises/functional/functional4.py +11 -0
- pythonlings/curriculum/exercises/functional/functional5.py +11 -0
- pythonlings/curriculum/exercises/functional/functional6.py +14 -0
- pythonlings/curriculum/exercises/functional/functional7.py +12 -0
- pythonlings/curriculum/exercises/functional/functional8.py +13 -0
- pythonlings/curriculum/exercises/functional/functional9.py +15 -0
- pythonlings/curriculum/exercises/functions/functions1.py +8 -0
- pythonlings/curriculum/exercises/functions/functions10.py +19 -0
- pythonlings/curriculum/exercises/functions/functions2.py +9 -0
- pythonlings/curriculum/exercises/functions/functions3.py +8 -0
- pythonlings/curriculum/exercises/functions/functions4.py +8 -0
- pythonlings/curriculum/exercises/functions/functions5.py +11 -0
- pythonlings/curriculum/exercises/functions/functions6.py +16 -0
- pythonlings/curriculum/exercises/functions/functions7.py +11 -0
- pythonlings/curriculum/exercises/functions/functions8.py +10 -0
- pythonlings/curriculum/exercises/functions/functions9.py +16 -0
- pythonlings/curriculum/exercises/generators/generators1.py +13 -0
- pythonlings/curriculum/exercises/generators/generators10.py +16 -0
- pythonlings/curriculum/exercises/generators/generators2.py +17 -0
- pythonlings/curriculum/exercises/generators/generators3.py +19 -0
- pythonlings/curriculum/exercises/generators/generators4.py +12 -0
- pythonlings/curriculum/exercises/generators/generators5.py +24 -0
- pythonlings/curriculum/exercises/generators/generators6.py +20 -0
- pythonlings/curriculum/exercises/generators/generators7.py +14 -0
- pythonlings/curriculum/exercises/generators/generators8.py +17 -0
- pythonlings/curriculum/exercises/generators/generators9.py +26 -0
- pythonlings/curriculum/exercises/itertools/itertools1.py +10 -0
- pythonlings/curriculum/exercises/itertools/itertools2.py +8 -0
- pythonlings/curriculum/exercises/itertools/itertools3.py +10 -0
- pythonlings/curriculum/exercises/itertools/itertools4.py +12 -0
- pythonlings/curriculum/exercises/itertools/itertools5.py +9 -0
- pythonlings/curriculum/exercises/itertools/itertools6.py +9 -0
- pythonlings/curriculum/exercises/itertools/itertools7.py +10 -0
- pythonlings/curriculum/exercises/itertools/itertools8.py +18 -0
- pythonlings/curriculum/exercises/json/json1.py +10 -0
- pythonlings/curriculum/exercises/json/json2.py +9 -0
- pythonlings/curriculum/exercises/json/json3.py +10 -0
- pythonlings/curriculum/exercises/json/json4.py +13 -0
- pythonlings/curriculum/exercises/json/json5.py +9 -0
- pythonlings/curriculum/exercises/json/json6.py +10 -0
- pythonlings/curriculum/exercises/json/json7.py +10 -0
- pythonlings/curriculum/exercises/json/json8.py +12 -0
- pythonlings/curriculum/exercises/lists/lists1.py +7 -0
- pythonlings/curriculum/exercises/lists/lists10.py +28 -0
- pythonlings/curriculum/exercises/lists/lists2.py +13 -0
- pythonlings/curriculum/exercises/lists/lists3.py +16 -0
- pythonlings/curriculum/exercises/lists/lists4.py +16 -0
- pythonlings/curriculum/exercises/lists/lists5.py +16 -0
- pythonlings/curriculum/exercises/lists/lists6.py +15 -0
- pythonlings/curriculum/exercises/lists/lists7.py +13 -0
- pythonlings/curriculum/exercises/lists/lists8.py +15 -0
- pythonlings/curriculum/exercises/lists/lists9.py +19 -0
- pythonlings/curriculum/exercises/loops/loops1.py +8 -0
- pythonlings/curriculum/exercises/loops/loops10.py +22 -0
- pythonlings/curriculum/exercises/loops/loops2.py +10 -0
- pythonlings/curriculum/exercises/loops/loops3.py +10 -0
- pythonlings/curriculum/exercises/loops/loops4.py +12 -0
- pythonlings/curriculum/exercises/loops/loops5.py +13 -0
- pythonlings/curriculum/exercises/loops/loops6.py +10 -0
- pythonlings/curriculum/exercises/loops/loops7.py +13 -0
- pythonlings/curriculum/exercises/loops/loops8.py +11 -0
- pythonlings/curriculum/exercises/loops/loops9.py +11 -0
- pythonlings/curriculum/exercises/modules/modules1.py +10 -0
- pythonlings/curriculum/exercises/modules/modules2.py +10 -0
- pythonlings/curriculum/exercises/modules/modules3.py +13 -0
- pythonlings/curriculum/exercises/modules/modules4.py +18 -0
- pythonlings/curriculum/exercises/modules/modules5.py +17 -0
- pythonlings/curriculum/exercises/modules/modules6.py +12 -0
- pythonlings/curriculum/exercises/modules/modules7.py +16 -0
- pythonlings/curriculum/exercises/modules/modules8.py +23 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced1.py +11 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced10.py +18 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced11.py +13 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced12.py +12 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced2.py +13 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced3.py +22 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced4.py +12 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced5.py +11 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced6.py +12 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced7.py +14 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced8.py +14 -0
- pythonlings/curriculum/exercises/oop_advanced/oop_advanced9.py +9 -0
- pythonlings/curriculum/exercises/pathlib/pathlib1.py +9 -0
- pythonlings/curriculum/exercises/pathlib/pathlib2.py +9 -0
- pythonlings/curriculum/exercises/pathlib/pathlib3.py +11 -0
- pythonlings/curriculum/exercises/pathlib/pathlib4.py +9 -0
- pythonlings/curriculum/exercises/pathlib/pathlib5.py +9 -0
- pythonlings/curriculum/exercises/pathlib/pathlib6.py +10 -0
- pythonlings/curriculum/exercises/recursion/recursion1.py +12 -0
- pythonlings/curriculum/exercises/recursion/recursion2.py +12 -0
- pythonlings/curriculum/exercises/recursion/recursion3.py +12 -0
- pythonlings/curriculum/exercises/recursion/recursion4.py +12 -0
- pythonlings/curriculum/exercises/recursion/recursion5.py +14 -0
- pythonlings/curriculum/exercises/recursion/recursion6.py +13 -0
- pythonlings/curriculum/exercises/recursion/recursion7.py +20 -0
- pythonlings/curriculum/exercises/recursion/recursion8.py +27 -0
- pythonlings/curriculum/exercises/regex/regex1.py +16 -0
- pythonlings/curriculum/exercises/regex/regex10.py +31 -0
- pythonlings/curriculum/exercises/regex/regex2.py +16 -0
- pythonlings/curriculum/exercises/regex/regex3.py +16 -0
- pythonlings/curriculum/exercises/regex/regex4.py +19 -0
- pythonlings/curriculum/exercises/regex/regex5.py +19 -0
- pythonlings/curriculum/exercises/regex/regex6.py +23 -0
- pythonlings/curriculum/exercises/regex/regex7.py +24 -0
- pythonlings/curriculum/exercises/regex/regex8.py +19 -0
- pythonlings/curriculum/exercises/regex/regex9.py +22 -0
- pythonlings/curriculum/exercises/sets/sets1.py +6 -0
- pythonlings/curriculum/exercises/sets/sets10.py +21 -0
- pythonlings/curriculum/exercises/sets/sets2.py +12 -0
- pythonlings/curriculum/exercises/sets/sets3.py +15 -0
- pythonlings/curriculum/exercises/sets/sets4.py +14 -0
- pythonlings/curriculum/exercises/sets/sets5.py +14 -0
- pythonlings/curriculum/exercises/sets/sets6.py +14 -0
- pythonlings/curriculum/exercises/sets/sets7.py +16 -0
- pythonlings/curriculum/exercises/sets/sets8.py +16 -0
- pythonlings/curriculum/exercises/sets/sets9.py +15 -0
- pythonlings/curriculum/exercises/strings/strings1.py +6 -0
- pythonlings/curriculum/exercises/strings/strings10.py +11 -0
- pythonlings/curriculum/exercises/strings/strings2.py +9 -0
- pythonlings/curriculum/exercises/strings/strings3.py +9 -0
- pythonlings/curriculum/exercises/strings/strings4.py +8 -0
- pythonlings/curriculum/exercises/strings/strings5.py +10 -0
- pythonlings/curriculum/exercises/strings/strings6.py +10 -0
- pythonlings/curriculum/exercises/strings/strings7.py +10 -0
- pythonlings/curriculum/exercises/strings/strings8.py +9 -0
- pythonlings/curriculum/exercises/strings/strings9.py +10 -0
- pythonlings/curriculum/exercises/testing/testing1.py +16 -0
- pythonlings/curriculum/exercises/testing/testing10.py +21 -0
- pythonlings/curriculum/exercises/testing/testing11.py +27 -0
- pythonlings/curriculum/exercises/testing/testing12.py +51 -0
- pythonlings/curriculum/exercises/testing/testing2.py +15 -0
- pythonlings/curriculum/exercises/testing/testing3.py +19 -0
- pythonlings/curriculum/exercises/testing/testing4.py +19 -0
- pythonlings/curriculum/exercises/testing/testing5.py +18 -0
- pythonlings/curriculum/exercises/testing/testing6.py +27 -0
- pythonlings/curriculum/exercises/testing/testing7.py +27 -0
- pythonlings/curriculum/exercises/testing/testing8.py +29 -0
- pythonlings/curriculum/exercises/testing/testing9.py +30 -0
- pythonlings/curriculum/exercises/tuples/tuples1.py +7 -0
- pythonlings/curriculum/exercises/tuples/tuples10.py +26 -0
- pythonlings/curriculum/exercises/tuples/tuples2.py +11 -0
- pythonlings/curriculum/exercises/tuples/tuples3.py +9 -0
- pythonlings/curriculum/exercises/tuples/tuples4.py +8 -0
- pythonlings/curriculum/exercises/tuples/tuples5.py +12 -0
- pythonlings/curriculum/exercises/tuples/tuples6.py +15 -0
- pythonlings/curriculum/exercises/tuples/tuples7.py +14 -0
- pythonlings/curriculum/exercises/tuples/tuples8.py +13 -0
- pythonlings/curriculum/exercises/tuples/tuples9.py +18 -0
- pythonlings/curriculum/exercises/type_hints/type_hints1.py +9 -0
- pythonlings/curriculum/exercises/type_hints/type_hints2.py +13 -0
- pythonlings/curriculum/exercises/type_hints/type_hints3.py +13 -0
- pythonlings/curriculum/exercises/type_hints/type_hints4.py +21 -0
- pythonlings/curriculum/exercises/type_hints/type_hints5.py +15 -0
- pythonlings/curriculum/exercises/type_hints/type_hints6.py +16 -0
- pythonlings/curriculum/exercises/type_hints/type_hints7.py +19 -0
- pythonlings/curriculum/exercises/type_hints/type_hints8.py +27 -0
- pythonlings/curriculum/exercises/variables/variables1.py +10 -0
- pythonlings/curriculum/exercises/variables/variables10.py +14 -0
- pythonlings/curriculum/exercises/variables/variables2.py +11 -0
- pythonlings/curriculum/exercises/variables/variables3.py +11 -0
- pythonlings/curriculum/exercises/variables/variables4.py +9 -0
- pythonlings/curriculum/exercises/variables/variables5.py +11 -0
- pythonlings/curriculum/exercises/variables/variables6.py +9 -0
- pythonlings/curriculum/exercises/variables/variables7.py +10 -0
- pythonlings/curriculum/exercises/variables/variables8.py +12 -0
- pythonlings/curriculum/exercises/variables/variables9.py +10 -0
- pythonlings/curriculum/info.toml +1755 -0
- pythonlings/curriculum/solutions/.keep +1 -0
- pythonlings/curriculum/solutions/_answers.py +1654 -0
- pythonlings/curriculum/solutions/async1.py +2 -0
- pythonlings/curriculum/solutions/async10.py +2 -0
- pythonlings/curriculum/solutions/async2.py +2 -0
- pythonlings/curriculum/solutions/async3.py +2 -0
- pythonlings/curriculum/solutions/async4.py +2 -0
- pythonlings/curriculum/solutions/async5.py +2 -0
- pythonlings/curriculum/solutions/async6.py +2 -0
- pythonlings/curriculum/solutions/async7.py +2 -0
- pythonlings/curriculum/solutions/async8.py +2 -0
- pythonlings/curriculum/solutions/async9.py +2 -0
- pythonlings/curriculum/solutions/classes1.py +2 -0
- pythonlings/curriculum/solutions/classes10.py +2 -0
- pythonlings/curriculum/solutions/classes11.py +2 -0
- pythonlings/curriculum/solutions/classes12.py +2 -0
- pythonlings/curriculum/solutions/classes2.py +2 -0
- pythonlings/curriculum/solutions/classes3.py +2 -0
- pythonlings/curriculum/solutions/classes4.py +2 -0
- pythonlings/curriculum/solutions/classes5.py +2 -0
- pythonlings/curriculum/solutions/classes6.py +2 -0
- pythonlings/curriculum/solutions/classes7.py +2 -0
- pythonlings/curriculum/solutions/classes8.py +2 -0
- pythonlings/curriculum/solutions/classes9.py +2 -0
- pythonlings/curriculum/solutions/collections1.py +2 -0
- pythonlings/curriculum/solutions/collections10.py +2 -0
- pythonlings/curriculum/solutions/collections2.py +2 -0
- pythonlings/curriculum/solutions/collections3.py +2 -0
- pythonlings/curriculum/solutions/collections4.py +2 -0
- pythonlings/curriculum/solutions/collections5.py +2 -0
- pythonlings/curriculum/solutions/collections6.py +2 -0
- pythonlings/curriculum/solutions/collections7.py +2 -0
- pythonlings/curriculum/solutions/collections8.py +2 -0
- pythonlings/curriculum/solutions/collections9.py +2 -0
- pythonlings/curriculum/solutions/comprehensions1.py +2 -0
- pythonlings/curriculum/solutions/comprehensions10.py +2 -0
- pythonlings/curriculum/solutions/comprehensions2.py +2 -0
- pythonlings/curriculum/solutions/comprehensions3.py +2 -0
- pythonlings/curriculum/solutions/comprehensions4.py +2 -0
- pythonlings/curriculum/solutions/comprehensions5.py +2 -0
- pythonlings/curriculum/solutions/comprehensions6.py +2 -0
- pythonlings/curriculum/solutions/comprehensions7.py +2 -0
- pythonlings/curriculum/solutions/comprehensions8.py +2 -0
- pythonlings/curriculum/solutions/comprehensions9.py +2 -0
- pythonlings/curriculum/solutions/conditionals1.py +2 -0
- pythonlings/curriculum/solutions/conditionals10.py +2 -0
- pythonlings/curriculum/solutions/conditionals2.py +2 -0
- pythonlings/curriculum/solutions/conditionals3.py +2 -0
- pythonlings/curriculum/solutions/conditionals4.py +2 -0
- pythonlings/curriculum/solutions/conditionals5.py +2 -0
- pythonlings/curriculum/solutions/conditionals6.py +2 -0
- pythonlings/curriculum/solutions/conditionals7.py +2 -0
- pythonlings/curriculum/solutions/conditionals8.py +2 -0
- pythonlings/curriculum/solutions/conditionals9.py +2 -0
- pythonlings/curriculum/solutions/context_managers1.py +2 -0
- pythonlings/curriculum/solutions/context_managers2.py +2 -0
- pythonlings/curriculum/solutions/context_managers3.py +2 -0
- pythonlings/curriculum/solutions/context_managers4.py +2 -0
- pythonlings/curriculum/solutions/context_managers5.py +2 -0
- pythonlings/curriculum/solutions/context_managers6.py +2 -0
- pythonlings/curriculum/solutions/context_managers7.py +2 -0
- pythonlings/curriculum/solutions/context_managers8.py +2 -0
- pythonlings/curriculum/solutions/dataclasses1.py +2 -0
- pythonlings/curriculum/solutions/dataclasses2.py +2 -0
- pythonlings/curriculum/solutions/dataclasses3.py +2 -0
- pythonlings/curriculum/solutions/dataclasses4.py +2 -0
- pythonlings/curriculum/solutions/dataclasses5.py +2 -0
- pythonlings/curriculum/solutions/dataclasses6.py +2 -0
- pythonlings/curriculum/solutions/dataclasses7.py +2 -0
- pythonlings/curriculum/solutions/dataclasses8.py +2 -0
- pythonlings/curriculum/solutions/datetime1.py +2 -0
- pythonlings/curriculum/solutions/datetime2.py +2 -0
- pythonlings/curriculum/solutions/datetime3.py +2 -0
- pythonlings/curriculum/solutions/datetime4.py +2 -0
- pythonlings/curriculum/solutions/datetime5.py +2 -0
- pythonlings/curriculum/solutions/datetime6.py +2 -0
- pythonlings/curriculum/solutions/datetime7.py +2 -0
- pythonlings/curriculum/solutions/datetime8.py +2 -0
- pythonlings/curriculum/solutions/decorators1.py +2 -0
- pythonlings/curriculum/solutions/decorators10.py +2 -0
- pythonlings/curriculum/solutions/decorators2.py +2 -0
- pythonlings/curriculum/solutions/decorators3.py +2 -0
- pythonlings/curriculum/solutions/decorators4.py +2 -0
- pythonlings/curriculum/solutions/decorators5.py +2 -0
- pythonlings/curriculum/solutions/decorators6.py +2 -0
- pythonlings/curriculum/solutions/decorators7.py +2 -0
- pythonlings/curriculum/solutions/decorators8.py +2 -0
- pythonlings/curriculum/solutions/decorators9.py +2 -0
- pythonlings/curriculum/solutions/dictionaries1.py +2 -0
- pythonlings/curriculum/solutions/dictionaries10.py +2 -0
- pythonlings/curriculum/solutions/dictionaries2.py +2 -0
- pythonlings/curriculum/solutions/dictionaries3.py +2 -0
- pythonlings/curriculum/solutions/dictionaries4.py +2 -0
- pythonlings/curriculum/solutions/dictionaries5.py +2 -0
- pythonlings/curriculum/solutions/dictionaries6.py +2 -0
- pythonlings/curriculum/solutions/dictionaries7.py +2 -0
- pythonlings/curriculum/solutions/dictionaries8.py +2 -0
- pythonlings/curriculum/solutions/dictionaries9.py +2 -0
- pythonlings/curriculum/solutions/enums1.py +2 -0
- pythonlings/curriculum/solutions/enums2.py +2 -0
- pythonlings/curriculum/solutions/enums3.py +2 -0
- pythonlings/curriculum/solutions/enums4.py +2 -0
- pythonlings/curriculum/solutions/enums5.py +2 -0
- pythonlings/curriculum/solutions/enums6.py +2 -0
- pythonlings/curriculum/solutions/exceptions1.py +2 -0
- pythonlings/curriculum/solutions/exceptions10.py +2 -0
- pythonlings/curriculum/solutions/exceptions2.py +2 -0
- pythonlings/curriculum/solutions/exceptions3.py +2 -0
- pythonlings/curriculum/solutions/exceptions4.py +2 -0
- pythonlings/curriculum/solutions/exceptions5.py +2 -0
- pythonlings/curriculum/solutions/exceptions6.py +2 -0
- pythonlings/curriculum/solutions/exceptions7.py +2 -0
- pythonlings/curriculum/solutions/exceptions8.py +2 -0
- pythonlings/curriculum/solutions/exceptions9.py +2 -0
- pythonlings/curriculum/solutions/file_io1.py +2 -0
- pythonlings/curriculum/solutions/file_io10.py +2 -0
- pythonlings/curriculum/solutions/file_io2.py +2 -0
- pythonlings/curriculum/solutions/file_io3.py +2 -0
- pythonlings/curriculum/solutions/file_io4.py +2 -0
- pythonlings/curriculum/solutions/file_io5.py +2 -0
- pythonlings/curriculum/solutions/file_io6.py +2 -0
- pythonlings/curriculum/solutions/file_io7.py +2 -0
- pythonlings/curriculum/solutions/file_io8.py +2 -0
- pythonlings/curriculum/solutions/file_io9.py +2 -0
- pythonlings/curriculum/solutions/functional1.py +2 -0
- pythonlings/curriculum/solutions/functional10.py +2 -0
- pythonlings/curriculum/solutions/functional2.py +2 -0
- pythonlings/curriculum/solutions/functional3.py +2 -0
- pythonlings/curriculum/solutions/functional4.py +2 -0
- pythonlings/curriculum/solutions/functional5.py +2 -0
- pythonlings/curriculum/solutions/functional6.py +2 -0
- pythonlings/curriculum/solutions/functional7.py +2 -0
- pythonlings/curriculum/solutions/functional8.py +2 -0
- pythonlings/curriculum/solutions/functional9.py +2 -0
- pythonlings/curriculum/solutions/functions1.py +2 -0
- pythonlings/curriculum/solutions/functions10.py +2 -0
- pythonlings/curriculum/solutions/functions2.py +2 -0
- pythonlings/curriculum/solutions/functions3.py +2 -0
- pythonlings/curriculum/solutions/functions4.py +2 -0
- pythonlings/curriculum/solutions/functions5.py +2 -0
- pythonlings/curriculum/solutions/functions6.py +2 -0
- pythonlings/curriculum/solutions/functions7.py +2 -0
- pythonlings/curriculum/solutions/functions8.py +2 -0
- pythonlings/curriculum/solutions/functions9.py +2 -0
- pythonlings/curriculum/solutions/generators1.py +2 -0
- pythonlings/curriculum/solutions/generators10.py +2 -0
- pythonlings/curriculum/solutions/generators2.py +2 -0
- pythonlings/curriculum/solutions/generators3.py +2 -0
- pythonlings/curriculum/solutions/generators4.py +2 -0
- pythonlings/curriculum/solutions/generators5.py +2 -0
- pythonlings/curriculum/solutions/generators6.py +2 -0
- pythonlings/curriculum/solutions/generators7.py +2 -0
- pythonlings/curriculum/solutions/generators8.py +2 -0
- pythonlings/curriculum/solutions/generators9.py +2 -0
- pythonlings/curriculum/solutions/itertools1.py +2 -0
- pythonlings/curriculum/solutions/itertools2.py +2 -0
- pythonlings/curriculum/solutions/itertools3.py +2 -0
- pythonlings/curriculum/solutions/itertools4.py +2 -0
- pythonlings/curriculum/solutions/itertools5.py +2 -0
- pythonlings/curriculum/solutions/itertools6.py +2 -0
- pythonlings/curriculum/solutions/itertools7.py +2 -0
- pythonlings/curriculum/solutions/itertools8.py +2 -0
- pythonlings/curriculum/solutions/json1.py +2 -0
- pythonlings/curriculum/solutions/json2.py +2 -0
- pythonlings/curriculum/solutions/json3.py +2 -0
- pythonlings/curriculum/solutions/json4.py +2 -0
- pythonlings/curriculum/solutions/json5.py +2 -0
- pythonlings/curriculum/solutions/json6.py +2 -0
- pythonlings/curriculum/solutions/json7.py +2 -0
- pythonlings/curriculum/solutions/json8.py +2 -0
- pythonlings/curriculum/solutions/lists1.py +2 -0
- pythonlings/curriculum/solutions/lists10.py +2 -0
- pythonlings/curriculum/solutions/lists2.py +2 -0
- pythonlings/curriculum/solutions/lists3.py +2 -0
- pythonlings/curriculum/solutions/lists4.py +2 -0
- pythonlings/curriculum/solutions/lists5.py +2 -0
- pythonlings/curriculum/solutions/lists6.py +2 -0
- pythonlings/curriculum/solutions/lists7.py +2 -0
- pythonlings/curriculum/solutions/lists8.py +2 -0
- pythonlings/curriculum/solutions/lists9.py +2 -0
- pythonlings/curriculum/solutions/loops1.py +2 -0
- pythonlings/curriculum/solutions/loops10.py +2 -0
- pythonlings/curriculum/solutions/loops2.py +2 -0
- pythonlings/curriculum/solutions/loops3.py +2 -0
- pythonlings/curriculum/solutions/loops4.py +2 -0
- pythonlings/curriculum/solutions/loops5.py +2 -0
- pythonlings/curriculum/solutions/loops6.py +2 -0
- pythonlings/curriculum/solutions/loops7.py +2 -0
- pythonlings/curriculum/solutions/loops8.py +2 -0
- pythonlings/curriculum/solutions/loops9.py +2 -0
- pythonlings/curriculum/solutions/modules1.py +2 -0
- pythonlings/curriculum/solutions/modules2.py +2 -0
- pythonlings/curriculum/solutions/modules3.py +2 -0
- pythonlings/curriculum/solutions/modules4.py +2 -0
- pythonlings/curriculum/solutions/modules5.py +2 -0
- pythonlings/curriculum/solutions/modules6.py +2 -0
- pythonlings/curriculum/solutions/modules7.py +2 -0
- pythonlings/curriculum/solutions/modules8.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced1.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced10.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced11.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced12.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced2.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced3.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced4.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced5.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced6.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced7.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced8.py +2 -0
- pythonlings/curriculum/solutions/oop_advanced9.py +2 -0
- pythonlings/curriculum/solutions/pathlib1.py +2 -0
- pythonlings/curriculum/solutions/pathlib2.py +2 -0
- pythonlings/curriculum/solutions/pathlib3.py +2 -0
- pythonlings/curriculum/solutions/pathlib4.py +2 -0
- pythonlings/curriculum/solutions/pathlib5.py +2 -0
- pythonlings/curriculum/solutions/pathlib6.py +2 -0
- pythonlings/curriculum/solutions/recursion1.py +2 -0
- pythonlings/curriculum/solutions/recursion2.py +2 -0
- pythonlings/curriculum/solutions/recursion3.py +2 -0
- pythonlings/curriculum/solutions/recursion4.py +2 -0
- pythonlings/curriculum/solutions/recursion5.py +2 -0
- pythonlings/curriculum/solutions/recursion6.py +2 -0
- pythonlings/curriculum/solutions/recursion7.py +2 -0
- pythonlings/curriculum/solutions/recursion8.py +2 -0
- pythonlings/curriculum/solutions/regex1.py +2 -0
- pythonlings/curriculum/solutions/regex10.py +2 -0
- pythonlings/curriculum/solutions/regex2.py +2 -0
- pythonlings/curriculum/solutions/regex3.py +2 -0
- pythonlings/curriculum/solutions/regex4.py +2 -0
- pythonlings/curriculum/solutions/regex5.py +2 -0
- pythonlings/curriculum/solutions/regex6.py +2 -0
- pythonlings/curriculum/solutions/regex7.py +2 -0
- pythonlings/curriculum/solutions/regex8.py +2 -0
- pythonlings/curriculum/solutions/regex9.py +2 -0
- pythonlings/curriculum/solutions/sets1.py +2 -0
- pythonlings/curriculum/solutions/sets10.py +2 -0
- pythonlings/curriculum/solutions/sets2.py +2 -0
- pythonlings/curriculum/solutions/sets3.py +2 -0
- pythonlings/curriculum/solutions/sets4.py +2 -0
- pythonlings/curriculum/solutions/sets5.py +2 -0
- pythonlings/curriculum/solutions/sets6.py +2 -0
- pythonlings/curriculum/solutions/sets7.py +2 -0
- pythonlings/curriculum/solutions/sets8.py +2 -0
- pythonlings/curriculum/solutions/sets9.py +2 -0
- pythonlings/curriculum/solutions/strings1.py +2 -0
- pythonlings/curriculum/solutions/strings10.py +2 -0
- pythonlings/curriculum/solutions/strings2.py +2 -0
- pythonlings/curriculum/solutions/strings3.py +2 -0
- pythonlings/curriculum/solutions/strings4.py +2 -0
- pythonlings/curriculum/solutions/strings5.py +2 -0
- pythonlings/curriculum/solutions/strings6.py +2 -0
- pythonlings/curriculum/solutions/strings7.py +2 -0
- pythonlings/curriculum/solutions/strings8.py +2 -0
- pythonlings/curriculum/solutions/strings9.py +2 -0
- pythonlings/curriculum/solutions/testing1.py +2 -0
- pythonlings/curriculum/solutions/testing10.py +2 -0
- pythonlings/curriculum/solutions/testing11.py +2 -0
- pythonlings/curriculum/solutions/testing12.py +2 -0
- pythonlings/curriculum/solutions/testing2.py +2 -0
- pythonlings/curriculum/solutions/testing3.py +2 -0
- pythonlings/curriculum/solutions/testing4.py +2 -0
- pythonlings/curriculum/solutions/testing5.py +2 -0
- pythonlings/curriculum/solutions/testing6.py +2 -0
- pythonlings/curriculum/solutions/testing7.py +2 -0
- pythonlings/curriculum/solutions/testing8.py +2 -0
- pythonlings/curriculum/solutions/testing9.py +2 -0
- pythonlings/curriculum/solutions/tuples1.py +2 -0
- pythonlings/curriculum/solutions/tuples10.py +2 -0
- pythonlings/curriculum/solutions/tuples2.py +2 -0
- pythonlings/curriculum/solutions/tuples3.py +2 -0
- pythonlings/curriculum/solutions/tuples4.py +2 -0
- pythonlings/curriculum/solutions/tuples5.py +2 -0
- pythonlings/curriculum/solutions/tuples6.py +2 -0
- pythonlings/curriculum/solutions/tuples7.py +2 -0
- pythonlings/curriculum/solutions/tuples8.py +2 -0
- pythonlings/curriculum/solutions/tuples9.py +2 -0
- pythonlings/curriculum/solutions/type_hints1.py +2 -0
- pythonlings/curriculum/solutions/type_hints2.py +2 -0
- pythonlings/curriculum/solutions/type_hints3.py +2 -0
- pythonlings/curriculum/solutions/type_hints4.py +2 -0
- pythonlings/curriculum/solutions/type_hints5.py +2 -0
- pythonlings/curriculum/solutions/type_hints6.py +2 -0
- pythonlings/curriculum/solutions/type_hints7.py +2 -0
- pythonlings/curriculum/solutions/type_hints8.py +2 -0
- pythonlings/curriculum/solutions/variables1.py +2 -0
- pythonlings/curriculum/solutions/variables10.py +2 -0
- pythonlings/curriculum/solutions/variables2.py +2 -0
- pythonlings/curriculum/solutions/variables3.py +2 -0
- pythonlings/curriculum/solutions/variables4.py +2 -0
- pythonlings/curriculum/solutions/variables5.py +2 -0
- pythonlings/curriculum/solutions/variables6.py +2 -0
- pythonlings/curriculum/solutions/variables7.py +2 -0
- pythonlings/curriculum/solutions/variables8.py +2 -0
- pythonlings/curriculum/solutions/variables9.py +2 -0
- pythonlings/docs/NOTICE.md +9 -0
- pythonlings/docs/__init__.py +1 -0
- pythonlings/docs/index.json +160 -0
- pythonlings/docs/topics/__init__.py +1 -0
- pythonlings/docs/topics/async.md +97 -0
- pythonlings/docs/topics/classes.md +97 -0
- pythonlings/docs/topics/collections.md +96 -0
- pythonlings/docs/topics/comprehensions.md +97 -0
- pythonlings/docs/topics/conditionals.md +88 -0
- pythonlings/docs/topics/context_managers.md +97 -0
- pythonlings/docs/topics/dataclasses.md +97 -0
- pythonlings/docs/topics/datetime.md +97 -0
- pythonlings/docs/topics/decorators.md +91 -0
- pythonlings/docs/topics/dictionaries.md +96 -0
- pythonlings/docs/topics/enums.md +96 -0
- pythonlings/docs/topics/exceptions.md +96 -0
- pythonlings/docs/topics/file_io.md +96 -0
- pythonlings/docs/topics/functional.md +96 -0
- pythonlings/docs/topics/functions.md +97 -0
- pythonlings/docs/topics/generators.md +97 -0
- pythonlings/docs/topics/itertools.md +96 -0
- pythonlings/docs/topics/json.md +97 -0
- pythonlings/docs/topics/lists.md +97 -0
- pythonlings/docs/topics/loops.md +95 -0
- pythonlings/docs/topics/modules.md +97 -0
- pythonlings/docs/topics/oop_advanced.md +71 -0
- pythonlings/docs/topics/pathlib.md +79 -0
- pythonlings/docs/topics/recursion.md +73 -0
- pythonlings/docs/topics/regex.md +96 -0
- pythonlings/docs/topics/sets.md +97 -0
- pythonlings/docs/topics/strings.md +96 -0
- pythonlings/docs/topics/testing.md +97 -0
- pythonlings/docs/topics/tuples.md +97 -0
- pythonlings/docs/topics/type_hints.md +97 -0
- pythonlings/docs/topics/variables.md +97 -0
- pythonlings/pythonlings.tcss +123 -0
- pythonlings/screens/__init__.py +0 -0
- pythonlings/screens/docs.py +93 -0
- pythonlings/screens/topic_picker.py +107 -0
- pythonlings/screens/track.py +234 -0
- pythonlings/widgets/__init__.py +0 -0
- pythonlings/widgets/editor_pane.py +33 -0
- pythonlings/widgets/exercise_tree.py +33 -0
- pythonlings/widgets/output_panel.py +180 -0
- pythonlings/widgets/progress.py +15 -0
- pythonlings-0.3.0.dist-info/METADATA +259 -0
- pythonlings-0.3.0.dist-info/RECORD +942 -0
- pythonlings-0.3.0.dist-info/WHEEL +4 -0
- pythonlings-0.3.0.dist-info/entry_points.txt +2 -0
- pythonlings-0.3.0.dist-info/licenses/LICENSE +21 -0
pythonlings/__init__.py
ADDED
|
File without changes
|
pythonlings/__main__.py
ADDED
pythonlings/app.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# pythonlings/app.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from textual.app import App
|
|
7
|
+
|
|
8
|
+
from pythonlings.core.manifest import Manifest, load as load_manifest
|
|
9
|
+
from pythonlings.core.state import State, load as load_state, next_pending
|
|
10
|
+
from pythonlings.screens.topic_picker import TopicPickerScreen
|
|
11
|
+
from pythonlings.screens.track import TrackScreen
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PythonlingsApp(App[int]):
|
|
15
|
+
CSS_PATH = "pythonlings.tcss"
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
root: Path,
|
|
20
|
+
start_topic: str | None = None,
|
|
21
|
+
force_picker: bool = False,
|
|
22
|
+
) -> None:
|
|
23
|
+
super().__init__()
|
|
24
|
+
self.root = root
|
|
25
|
+
self.manifest: Manifest = load_manifest(root)
|
|
26
|
+
self.state: State = load_state(root)
|
|
27
|
+
self._start_topic = start_topic
|
|
28
|
+
self._force_picker = force_picker
|
|
29
|
+
|
|
30
|
+
def on_mount(self) -> None:
|
|
31
|
+
self.push_screen(TopicPickerScreen())
|
|
32
|
+
target = self._startup_target()
|
|
33
|
+
if target is not None:
|
|
34
|
+
topic, exercise = target
|
|
35
|
+
self.push_screen(TrackScreen(topic, start_exercise=exercise))
|
|
36
|
+
|
|
37
|
+
def _startup_target(self) -> tuple[str, str | None] | None:
|
|
38
|
+
if self._start_topic is not None:
|
|
39
|
+
if self._start_topic in self.manifest.topics():
|
|
40
|
+
return self._start_topic, None
|
|
41
|
+
return None
|
|
42
|
+
if self._force_picker:
|
|
43
|
+
return None
|
|
44
|
+
return self._resume_target() or self._first_pending_target()
|
|
45
|
+
|
|
46
|
+
def _resume_target(self) -> tuple[str, str | None] | None:
|
|
47
|
+
topic = self.state.last_topic
|
|
48
|
+
if topic not in self.manifest.topics():
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
exercises = self.manifest.exercises_in(topic)
|
|
52
|
+
names = {ex.name for ex in exercises}
|
|
53
|
+
last = self.state.last_exercise
|
|
54
|
+
if last in names and last not in self.state.completed:
|
|
55
|
+
return topic, last
|
|
56
|
+
|
|
57
|
+
next_name = next_pending(exercises, self.state.completed)
|
|
58
|
+
if next_name is not None:
|
|
59
|
+
return topic, next_name
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
def _first_pending_target(self) -> tuple[str, str | None] | None:
|
|
63
|
+
for topic in self.manifest.topics():
|
|
64
|
+
next_name = next_pending(
|
|
65
|
+
self.manifest.exercises_in(topic), self.state.completed
|
|
66
|
+
)
|
|
67
|
+
if next_name is not None:
|
|
68
|
+
return topic, next_name
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def run_tui(
|
|
73
|
+
root: Path,
|
|
74
|
+
start_topic: str | None = None,
|
|
75
|
+
force_picker: bool = False,
|
|
76
|
+
) -> int:
|
|
77
|
+
return (
|
|
78
|
+
PythonlingsApp(
|
|
79
|
+
root,
|
|
80
|
+
start_topic,
|
|
81
|
+
force_picker=force_picker,
|
|
82
|
+
).run()
|
|
83
|
+
or 0
|
|
84
|
+
)
|
pythonlings/cli.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# pythonlings/cli.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
__version__ = "0.3.0"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
12
|
+
parser = argparse.ArgumentParser(prog="pythonlings")
|
|
13
|
+
parser.add_argument("--version", action="version", version=f"pythonlings {__version__}")
|
|
14
|
+
parser.add_argument(
|
|
15
|
+
"--debug", action="store_true", help="Write debug output to .pythonlings_debug.log."
|
|
16
|
+
)
|
|
17
|
+
parser.add_argument(
|
|
18
|
+
"--root",
|
|
19
|
+
type=Path,
|
|
20
|
+
default=Path.cwd(),
|
|
21
|
+
help="Project root containing info.toml (default: cwd).",
|
|
22
|
+
)
|
|
23
|
+
sub = parser.add_subparsers(dest="command")
|
|
24
|
+
|
|
25
|
+
p_init = sub.add_parser("init", help="Create a pythonlings workspace.")
|
|
26
|
+
p_init.add_argument("--path", type=Path, default=Path.cwd())
|
|
27
|
+
p_init.add_argument(
|
|
28
|
+
"--force", action="store_true", help="Overwrite managed workspace files."
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
p_update = sub.add_parser("update", help="Update an existing pythonlings workspace.")
|
|
32
|
+
p_update.add_argument("--path", type=Path, default=Path.cwd())
|
|
33
|
+
|
|
34
|
+
sub.add_parser("watch", help="Launch the TUI in watch mode (default).")
|
|
35
|
+
sub.add_parser("topics", help="Launch the TUI on the topic picker.")
|
|
36
|
+
|
|
37
|
+
p_run = sub.add_parser("run", help="Run a single exercise.")
|
|
38
|
+
p_run.add_argument("name")
|
|
39
|
+
|
|
40
|
+
p_dry_run = sub.add_parser("dry-run", help="Run one exercise non-interactively.")
|
|
41
|
+
p_dry_run.add_argument("name")
|
|
42
|
+
|
|
43
|
+
p_solution = sub.add_parser(
|
|
44
|
+
"solution", aliases=["sol"], help="Run a reference solution."
|
|
45
|
+
)
|
|
46
|
+
p_solution.add_argument("name")
|
|
47
|
+
|
|
48
|
+
p_hint = sub.add_parser("hint", help="Print the hint for an exercise.")
|
|
49
|
+
p_hint.add_argument("name")
|
|
50
|
+
|
|
51
|
+
p_list = sub.add_parser("list", help="List topics, or one topic's exercises.")
|
|
52
|
+
p_list.add_argument("topic", nargs="?", help="Show exercises of this topic.")
|
|
53
|
+
|
|
54
|
+
p_start = sub.add_parser("start", help="Launch the TUI on a topic's track.")
|
|
55
|
+
p_start.add_argument("topic")
|
|
56
|
+
|
|
57
|
+
p_reset = sub.add_parser("reset", help="Restore an exercise from its snapshot.")
|
|
58
|
+
p_reset.add_argument("name")
|
|
59
|
+
p_reset.add_argument("--yes", action="store_true", help="Skip the confirmation prompt.")
|
|
60
|
+
|
|
61
|
+
p_verify = sub.add_parser(
|
|
62
|
+
"verify", help="Run every exercise, or just one topic's."
|
|
63
|
+
)
|
|
64
|
+
p_verify.add_argument("topic", nargs="?", help="Verify only this topic.")
|
|
65
|
+
|
|
66
|
+
return parser
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _resolve_topic(manifest, topic: str):
|
|
70
|
+
"""Return the topic name if valid, else write an error and return None."""
|
|
71
|
+
if topic in manifest.topics():
|
|
72
|
+
return topic
|
|
73
|
+
sys.stderr.write(
|
|
74
|
+
f"pythonlings: no topic named {topic!r}. "
|
|
75
|
+
f"Topics: {', '.join(manifest.topics())}\n"
|
|
76
|
+
)
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _cmd_init(path: Path, force: bool) -> int:
|
|
81
|
+
from pythonlings.core.curriculum import WorkspaceError, init_workspace
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
root = init_workspace(path, force=force)
|
|
85
|
+
except WorkspaceError as e:
|
|
86
|
+
sys.stderr.write(f"pythonlings: {e}\n")
|
|
87
|
+
return 1
|
|
88
|
+
print(f"initialized: {root}")
|
|
89
|
+
return 0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _cmd_update(path: Path) -> int:
|
|
93
|
+
from pythonlings.core.curriculum import WorkspaceError, update_workspace
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
root = update_workspace(path)
|
|
97
|
+
except WorkspaceError as e:
|
|
98
|
+
sys.stderr.write(f"pythonlings: {e}\n")
|
|
99
|
+
return 1
|
|
100
|
+
print(f"updated: {root}")
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _cmd_verify(root: Path, topic: str | None) -> int:
|
|
105
|
+
from pythonlings.core.manifest import load as load_manifest
|
|
106
|
+
from pythonlings.core.runner import run_verify
|
|
107
|
+
|
|
108
|
+
manifest = load_manifest(root)
|
|
109
|
+
if topic is not None:
|
|
110
|
+
if _resolve_topic(manifest, topic) is None:
|
|
111
|
+
return 2
|
|
112
|
+
exercises = manifest.exercises_in(topic)
|
|
113
|
+
else:
|
|
114
|
+
exercises = manifest.exercises
|
|
115
|
+
for ex in exercises:
|
|
116
|
+
result = run_verify(ex)
|
|
117
|
+
status = "✓" if result.passed else "✗"
|
|
118
|
+
print(f"{status} {ex.name}")
|
|
119
|
+
if not result.passed:
|
|
120
|
+
sys.stderr.write(result.stderr or result.stdout)
|
|
121
|
+
return 1
|
|
122
|
+
return 0
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _cmd_list(root: Path, topic: str | None) -> int:
|
|
126
|
+
from pythonlings.core.manifest import load as load_manifest
|
|
127
|
+
from pythonlings.core.state import load as load_state, next_pending
|
|
128
|
+
|
|
129
|
+
manifest = load_manifest(root)
|
|
130
|
+
state = load_state(root)
|
|
131
|
+
|
|
132
|
+
if topic is None:
|
|
133
|
+
for name in manifest.topics():
|
|
134
|
+
exs = manifest.exercises_in(name)
|
|
135
|
+
done = sum(1 for ex in exs if ex.name in state.completed)
|
|
136
|
+
mark = "✓" if done == len(exs) else ("●" if done else " ")
|
|
137
|
+
print(f" {mark} {name} {done}/{len(exs)}")
|
|
138
|
+
return 0
|
|
139
|
+
|
|
140
|
+
if _resolve_topic(manifest, topic) is None:
|
|
141
|
+
return 2
|
|
142
|
+
exs = manifest.exercises_in(topic)
|
|
143
|
+
current = next_pending(exs, state.completed)
|
|
144
|
+
for ex in exs:
|
|
145
|
+
if ex.name in state.completed:
|
|
146
|
+
marker = "✓"
|
|
147
|
+
elif ex.name == current:
|
|
148
|
+
marker = "●"
|
|
149
|
+
else:
|
|
150
|
+
marker = "🔒"
|
|
151
|
+
print(f" {marker} {ex.name}")
|
|
152
|
+
return 0
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _cmd_hint(root: Path, name: str) -> int:
|
|
156
|
+
from pythonlings.core.manifest import load as load_manifest
|
|
157
|
+
|
|
158
|
+
manifest = load_manifest(root)
|
|
159
|
+
try:
|
|
160
|
+
ex = manifest.by_name(name)
|
|
161
|
+
except KeyError:
|
|
162
|
+
sys.stderr.write(f"pythonlings: no exercise named {name!r}\n")
|
|
163
|
+
return 1
|
|
164
|
+
print(ex.hint.strip() or "(no hint provided)")
|
|
165
|
+
if ex.docs:
|
|
166
|
+
print(f"Docs: {ex.docs}")
|
|
167
|
+
return 0
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _cmd_run(root: Path, name: str) -> int:
|
|
171
|
+
from pythonlings.core.manifest import load as load_manifest
|
|
172
|
+
from pythonlings.core.runner import run as run_exercise
|
|
173
|
+
|
|
174
|
+
manifest = load_manifest(root)
|
|
175
|
+
try:
|
|
176
|
+
ex = manifest.by_name(name)
|
|
177
|
+
except KeyError:
|
|
178
|
+
sys.stderr.write(f"pythonlings: no exercise named {name!r}\n")
|
|
179
|
+
return 1
|
|
180
|
+
|
|
181
|
+
result = run_exercise(ex)
|
|
182
|
+
if result.stdout:
|
|
183
|
+
sys.stdout.write(result.stdout)
|
|
184
|
+
if result.stderr:
|
|
185
|
+
sys.stderr.write(result.stderr)
|
|
186
|
+
if result.timed_out:
|
|
187
|
+
sys.stderr.write(f"pythonlings: {name} timed out after {result.duration_s:.1f}s\n")
|
|
188
|
+
return 1
|
|
189
|
+
if result.exit_code != 0:
|
|
190
|
+
return 1
|
|
191
|
+
if ex.is_pending():
|
|
192
|
+
sys.stderr.write(
|
|
193
|
+
f"pythonlings: tests pass but the '# I AM NOT DONE' marker is still in {name}.\n"
|
|
194
|
+
)
|
|
195
|
+
return 1
|
|
196
|
+
return 0
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _cmd_solution(root: Path, name: str) -> int:
|
|
200
|
+
from pythonlings.core.manifest import load as load_manifest
|
|
201
|
+
from pythonlings.core.runner import run_verify
|
|
202
|
+
from pythonlings.core.solutions import SolutionError, solution_exercise
|
|
203
|
+
|
|
204
|
+
manifest = load_manifest(root)
|
|
205
|
+
try:
|
|
206
|
+
ex = solution_exercise(root, manifest.by_name(name))
|
|
207
|
+
except KeyError:
|
|
208
|
+
sys.stderr.write(f"pythonlings: no exercise named {name!r}\n")
|
|
209
|
+
return 1
|
|
210
|
+
except SolutionError as e:
|
|
211
|
+
sys.stderr.write(f"pythonlings: {e}\n")
|
|
212
|
+
return 1
|
|
213
|
+
|
|
214
|
+
result = run_verify(ex)
|
|
215
|
+
if result.stdout:
|
|
216
|
+
sys.stdout.write(result.stdout)
|
|
217
|
+
if result.stderr:
|
|
218
|
+
sys.stderr.write(result.stderr)
|
|
219
|
+
return 0 if result.passed else 1
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _cmd_reset(root: Path, name: str, yes: bool) -> int:
|
|
223
|
+
from pythonlings.core.manifest import load as load_manifest
|
|
224
|
+
from pythonlings.core.reset import ResetError, restore
|
|
225
|
+
from pythonlings.core.state import load as load_state, save as save_state
|
|
226
|
+
|
|
227
|
+
manifest = load_manifest(root)
|
|
228
|
+
try:
|
|
229
|
+
ex = manifest.by_name(name)
|
|
230
|
+
except KeyError:
|
|
231
|
+
sys.stderr.write(f"pythonlings: no exercise named {name!r}\n")
|
|
232
|
+
return 1
|
|
233
|
+
|
|
234
|
+
if not yes:
|
|
235
|
+
sys.stdout.write(f"Reset {name}? (y/N) ")
|
|
236
|
+
sys.stdout.flush()
|
|
237
|
+
if sys.stdin.readline().strip().lower() != "y":
|
|
238
|
+
return 0
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
restore(root, ex)
|
|
242
|
+
except ResetError as e:
|
|
243
|
+
sys.stderr.write(f"pythonlings: {e}\n")
|
|
244
|
+
return 1
|
|
245
|
+
|
|
246
|
+
state = load_state(root)
|
|
247
|
+
state.completed.discard(name)
|
|
248
|
+
save_state(root, state)
|
|
249
|
+
print(f"reset: {name}")
|
|
250
|
+
return 0
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def main(argv: list[str] | None = None) -> int:
|
|
254
|
+
parser = _build_parser()
|
|
255
|
+
args = parser.parse_args(argv if argv is not None else sys.argv[1:])
|
|
256
|
+
|
|
257
|
+
# Migrate any legacy .pylings/ state dir. init/update target --path; the
|
|
258
|
+
# in-workspace commands target --root. Both are no-ops without a legacy dir.
|
|
259
|
+
from pythonlings.core.curriculum import migrate_legacy_state_dir
|
|
260
|
+
|
|
261
|
+
for attr in ("path", "root"):
|
|
262
|
+
workspace = getattr(args, attr, None)
|
|
263
|
+
if workspace is not None:
|
|
264
|
+
migrate_legacy_state_dir(Path(workspace))
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
if getattr(args, "debug", False):
|
|
268
|
+
try:
|
|
269
|
+
(args.root / ".pythonlings_debug.log").write_text(
|
|
270
|
+
f"argv={argv if argv is not None else sys.argv[1:]!r}\n",
|
|
271
|
+
encoding="utf-8",
|
|
272
|
+
)
|
|
273
|
+
except OSError:
|
|
274
|
+
pass
|
|
275
|
+
|
|
276
|
+
if args.command == "init":
|
|
277
|
+
return _cmd_init(args.path, args.force)
|
|
278
|
+
if args.command == "update":
|
|
279
|
+
return _cmd_update(args.path)
|
|
280
|
+
|
|
281
|
+
if args.command == "verify":
|
|
282
|
+
return _cmd_verify(args.root, args.topic)
|
|
283
|
+
if args.command == "list":
|
|
284
|
+
return _cmd_list(args.root, args.topic)
|
|
285
|
+
if args.command == "hint":
|
|
286
|
+
return _cmd_hint(args.root, args.name)
|
|
287
|
+
if args.command == "run":
|
|
288
|
+
return _cmd_run(args.root, args.name)
|
|
289
|
+
if args.command == "dry-run":
|
|
290
|
+
return _cmd_run(args.root, args.name)
|
|
291
|
+
if args.command in {"solution", "sol"}:
|
|
292
|
+
return _cmd_solution(args.root, args.name)
|
|
293
|
+
if args.command == "reset":
|
|
294
|
+
return _cmd_reset(args.root, args.name, args.yes)
|
|
295
|
+
|
|
296
|
+
if args.command in (None, "watch", "start", "topics"):
|
|
297
|
+
start_topic = getattr(args, "topic", None)
|
|
298
|
+
if start_topic is not None:
|
|
299
|
+
from pythonlings.core.manifest import load as load_manifest
|
|
300
|
+
if _resolve_topic(load_manifest(args.root), start_topic) is None:
|
|
301
|
+
return 2
|
|
302
|
+
from pythonlings.app import run_tui # lazy: Textual is heavy
|
|
303
|
+
return run_tui(
|
|
304
|
+
args.root,
|
|
305
|
+
start_topic,
|
|
306
|
+
force_picker=args.command == "topics",
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# Other subcommands wired in later tasks.
|
|
310
|
+
sys.stderr.write(f"pythonlings: '{args.command}' not implemented yet\n")
|
|
311
|
+
return 1
|
|
312
|
+
except Exception as e:
|
|
313
|
+
# Manifest errors and other startup failures use exit code 2.
|
|
314
|
+
from pythonlings.core.manifest import ManifestError
|
|
315
|
+
if isinstance(e, ManifestError):
|
|
316
|
+
sys.stderr.write(f"pythonlings: {e}\n")
|
|
317
|
+
return 2
|
|
318
|
+
raise
|
|
File without changes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from importlib import resources
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WorkspaceError(RuntimeError):
|
|
9
|
+
"""Workspace init or update failed."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
CURRICULUM_DIRS = ("exercises", "checks", "solutions")
|
|
13
|
+
GITIGNORE_LINES = [
|
|
14
|
+
".pythonlings/state.json",
|
|
15
|
+
".pythonlings_debug.log",
|
|
16
|
+
"__pycache__/",
|
|
17
|
+
"*.pyc",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def migrate_legacy_state_dir(root: Path) -> None:
|
|
22
|
+
"""Rename a pre-rename `.pylings/` state dir to `.pythonlings/`.
|
|
23
|
+
|
|
24
|
+
Workspaces created before the project was renamed from pylings to
|
|
25
|
+
pythonlings keep their progress and reset snapshots this way.
|
|
26
|
+
"""
|
|
27
|
+
legacy = root / ".pylings"
|
|
28
|
+
current = root / ".pythonlings"
|
|
29
|
+
if legacy.is_dir() and not current.exists():
|
|
30
|
+
legacy.rename(current)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def source_root() -> Path:
|
|
34
|
+
"""Return the curriculum source root for editable and wheel installs."""
|
|
35
|
+
packaged = resources.files("pythonlings").joinpath("curriculum")
|
|
36
|
+
if packaged.joinpath("info.toml").is_file():
|
|
37
|
+
return Path(str(packaged))
|
|
38
|
+
|
|
39
|
+
repo_root = Path(__file__).resolve().parents[2]
|
|
40
|
+
if (repo_root / "info.toml").exists():
|
|
41
|
+
return repo_root
|
|
42
|
+
|
|
43
|
+
raise WorkspaceError("packaged curriculum not found")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _copy_path(src: Path, dst: Path, *, overwrite: bool) -> None:
|
|
47
|
+
if src.is_dir():
|
|
48
|
+
if dst.exists() and overwrite:
|
|
49
|
+
shutil.rmtree(dst)
|
|
50
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
for child in src.iterdir():
|
|
52
|
+
_copy_path(child, dst / child.name, overwrite=overwrite)
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
if dst.exists() and not overwrite:
|
|
56
|
+
return
|
|
57
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
shutil.copy2(src, dst)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _write_workspace_gitignore(root: Path) -> None:
|
|
62
|
+
(root / ".gitignore").write_text(
|
|
63
|
+
"\n".join(GITIGNORE_LINES) + "\n",
|
|
64
|
+
encoding="utf-8",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _sync_originals(root: Path, src_root: Path) -> None:
|
|
69
|
+
originals = root / ".pythonlings" / "originals"
|
|
70
|
+
if originals.exists():
|
|
71
|
+
shutil.rmtree(originals)
|
|
72
|
+
for exercise in (src_root / "exercises").rglob("*.py"):
|
|
73
|
+
rel = exercise.relative_to(src_root / "exercises")
|
|
74
|
+
target = originals / rel
|
|
75
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
shutil.copy2(exercise, target)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def init_workspace(path: Path, *, force: bool = False) -> Path:
|
|
80
|
+
path = path.expanduser().resolve()
|
|
81
|
+
if path.exists() and any(path.iterdir()) and not force:
|
|
82
|
+
raise WorkspaceError(f"{path} already exists and is not empty")
|
|
83
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
|
|
85
|
+
src_root = source_root()
|
|
86
|
+
_copy_path(src_root / "info.toml", path / "info.toml", overwrite=True)
|
|
87
|
+
for dirname in CURRICULUM_DIRS:
|
|
88
|
+
_copy_path(src_root / dirname, path / dirname, overwrite=True)
|
|
89
|
+
_sync_originals(path, src_root)
|
|
90
|
+
_write_workspace_gitignore(path)
|
|
91
|
+
return path
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def update_workspace(path: Path) -> Path:
|
|
95
|
+
path = path.expanduser().resolve()
|
|
96
|
+
if not (path / "info.toml").exists():
|
|
97
|
+
raise WorkspaceError(f"{path} is not a pythonlings workspace")
|
|
98
|
+
|
|
99
|
+
src_root = source_root()
|
|
100
|
+
_copy_path(src_root / "info.toml", path / "info.toml", overwrite=True)
|
|
101
|
+
_copy_path(src_root / "checks", path / "checks", overwrite=True)
|
|
102
|
+
_copy_path(src_root / "solutions", path / "solutions", overwrite=True)
|
|
103
|
+
_copy_path(src_root / "exercises", path / "exercises", overwrite=False)
|
|
104
|
+
_sync_originals(path, src_root)
|
|
105
|
+
_write_workspace_gitignore(path)
|
|
106
|
+
return path
|
pythonlings/core/docs.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# pythonlings/core/docs.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from importlib import resources
|
|
7
|
+
from typing import Any
|
|
8
|
+
from urllib.parse import urldefrag
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class DocSnippet:
|
|
13
|
+
topic: str
|
|
14
|
+
title: str
|
|
15
|
+
source_url: str
|
|
16
|
+
text: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_snippet(topic: str, source_url: str = "") -> DocSnippet | None:
|
|
20
|
+
"""Load a bundled snippet by topic, falling back to an official docs URL."""
|
|
21
|
+
index = _load_index()
|
|
22
|
+
if index is None:
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
entry = index["topics"].get(topic)
|
|
26
|
+
if entry is not None:
|
|
27
|
+
return _load_entry(topic, entry)
|
|
28
|
+
|
|
29
|
+
normalized_source = _normalize_url(source_url)
|
|
30
|
+
if not normalized_source:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
for candidate_topic, candidate_entry in index["topics"].items():
|
|
34
|
+
candidate_source = candidate_entry["source_url"]
|
|
35
|
+
if (
|
|
36
|
+
candidate_source == source_url
|
|
37
|
+
or _normalize_url(candidate_source) == normalized_source
|
|
38
|
+
):
|
|
39
|
+
return _load_entry(candidate_topic, candidate_entry)
|
|
40
|
+
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _load_index() -> dict[str, Any] | None:
|
|
45
|
+
try:
|
|
46
|
+
docs_root = resources.files("pythonlings.docs")
|
|
47
|
+
return json.loads((docs_root / "index.json").read_text(encoding="utf-8"))
|
|
48
|
+
except (FileNotFoundError, json.JSONDecodeError, ModuleNotFoundError):
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _load_entry(topic: str, entry: dict[str, Any]) -> DocSnippet | None:
|
|
53
|
+
try:
|
|
54
|
+
docs_root = resources.files("pythonlings.docs")
|
|
55
|
+
text = (docs_root / entry["file"]).read_text(encoding="utf-8").strip()
|
|
56
|
+
except (FileNotFoundError, KeyError, ModuleNotFoundError):
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
return DocSnippet(
|
|
60
|
+
topic=topic,
|
|
61
|
+
title=entry["title"],
|
|
62
|
+
source_url=entry["source_url"],
|
|
63
|
+
text=text,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _normalize_url(url: str) -> str:
|
|
68
|
+
return urldefrag(url.strip()).url
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# pythonlings/core/exercise.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class Exercise:
|
|
10
|
+
name: str
|
|
11
|
+
path: Path
|
|
12
|
+
check_path: Path
|
|
13
|
+
topic: str
|
|
14
|
+
hint: str
|
|
15
|
+
docs: str = ""
|
|
16
|
+
root: Path | None = None
|
|
17
|
+
rel_path: Path | None = None
|
|
18
|
+
check_rel_path: Path | None = None
|
|
19
|
+
|
|
20
|
+
DONE_MARKER = "# I AM NOT DONE"
|
|
21
|
+
|
|
22
|
+
def is_pending(self) -> bool:
|
|
23
|
+
return self.DONE_MARKER in self.path.read_text(encoding="utf-8")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class RunResult:
|
|
28
|
+
passed: bool
|
|
29
|
+
exit_code: int
|
|
30
|
+
stdout: str
|
|
31
|
+
stderr: str
|
|
32
|
+
duration_s: float
|
|
33
|
+
timed_out: bool
|