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.
Files changed (942) hide show
  1. pythonlings/__init__.py +0 -0
  2. pythonlings/__main__.py +4 -0
  3. pythonlings/app.py +84 -0
  4. pythonlings/cli.py +318 -0
  5. pythonlings/core/__init__.py +0 -0
  6. pythonlings/core/curriculum.py +106 -0
  7. pythonlings/core/docs.py +68 -0
  8. pythonlings/core/exercise.py +33 -0
  9. pythonlings/core/manifest.py +110 -0
  10. pythonlings/core/reset.py +43 -0
  11. pythonlings/core/runner.py +114 -0
  12. pythonlings/core/solutions.py +17 -0
  13. pythonlings/core/state.py +86 -0
  14. pythonlings/curriculum/checks/async/async1.py +4 -0
  15. pythonlings/curriculum/checks/async/async10.py +4 -0
  16. pythonlings/curriculum/checks/async/async2.py +4 -0
  17. pythonlings/curriculum/checks/async/async3.py +4 -0
  18. pythonlings/curriculum/checks/async/async4.py +4 -0
  19. pythonlings/curriculum/checks/async/async5.py +4 -0
  20. pythonlings/curriculum/checks/async/async6.py +4 -0
  21. pythonlings/curriculum/checks/async/async7.py +4 -0
  22. pythonlings/curriculum/checks/async/async8.py +2 -0
  23. pythonlings/curriculum/checks/async/async9.py +4 -0
  24. pythonlings/curriculum/checks/classes/classes1.py +3 -0
  25. pythonlings/curriculum/checks/classes/classes10.py +5 -0
  26. pythonlings/curriculum/checks/classes/classes11.py +5 -0
  27. pythonlings/curriculum/checks/classes/classes12.py +16 -0
  28. pythonlings/curriculum/checks/classes/classes2.py +3 -0
  29. pythonlings/curriculum/checks/classes/classes3.py +4 -0
  30. pythonlings/curriculum/checks/classes/classes4.py +4 -0
  31. pythonlings/curriculum/checks/classes/classes5.py +5 -0
  32. pythonlings/curriculum/checks/classes/classes6.py +4 -0
  33. pythonlings/curriculum/checks/classes/classes7.py +3 -0
  34. pythonlings/curriculum/checks/classes/classes8.py +3 -0
  35. pythonlings/curriculum/checks/classes/classes9.py +4 -0
  36. pythonlings/curriculum/checks/collections/collections1.py +4 -0
  37. pythonlings/curriculum/checks/collections/collections10.py +15 -0
  38. pythonlings/curriculum/checks/collections/collections2.py +4 -0
  39. pythonlings/curriculum/checks/collections/collections3.py +5 -0
  40. pythonlings/curriculum/checks/collections/collections4.py +4 -0
  41. pythonlings/curriculum/checks/collections/collections5.py +5 -0
  42. pythonlings/curriculum/checks/collections/collections6.py +6 -0
  43. pythonlings/curriculum/checks/collections/collections7.py +4 -0
  44. pythonlings/curriculum/checks/collections/collections8.py +5 -0
  45. pythonlings/curriculum/checks/collections/collections9.py +9 -0
  46. pythonlings/curriculum/checks/comprehensions/comprehensions1.py +3 -0
  47. pythonlings/curriculum/checks/comprehensions/comprehensions10.py +5 -0
  48. pythonlings/curriculum/checks/comprehensions/comprehensions2.py +3 -0
  49. pythonlings/curriculum/checks/comprehensions/comprehensions3.py +3 -0
  50. pythonlings/curriculum/checks/comprehensions/comprehensions4.py +6 -0
  51. pythonlings/curriculum/checks/comprehensions/comprehensions5.py +3 -0
  52. pythonlings/curriculum/checks/comprehensions/comprehensions6.py +3 -0
  53. pythonlings/curriculum/checks/comprehensions/comprehensions7.py +4 -0
  54. pythonlings/curriculum/checks/comprehensions/comprehensions8.py +5 -0
  55. pythonlings/curriculum/checks/comprehensions/comprehensions9.py +3 -0
  56. pythonlings/curriculum/checks/conditionals/conditionals1.py +2 -0
  57. pythonlings/curriculum/checks/conditionals/conditionals10.py +10 -0
  58. pythonlings/curriculum/checks/conditionals/conditionals2.py +5 -0
  59. pythonlings/curriculum/checks/conditionals/conditionals3.py +9 -0
  60. pythonlings/curriculum/checks/conditionals/conditionals4.py +7 -0
  61. pythonlings/curriculum/checks/conditionals/conditionals5.py +9 -0
  62. pythonlings/curriculum/checks/conditionals/conditionals6.py +7 -0
  63. pythonlings/curriculum/checks/conditionals/conditionals7.py +7 -0
  64. pythonlings/curriculum/checks/conditionals/conditionals8.py +9 -0
  65. pythonlings/curriculum/checks/conditionals/conditionals9.py +7 -0
  66. pythonlings/curriculum/checks/context_managers/context_managers1.py +15 -0
  67. pythonlings/curriculum/checks/context_managers/context_managers2.py +10 -0
  68. pythonlings/curriculum/checks/context_managers/context_managers3.py +2 -0
  69. pythonlings/curriculum/checks/context_managers/context_managers4.py +3 -0
  70. pythonlings/curriculum/checks/context_managers/context_managers5.py +5 -0
  71. pythonlings/curriculum/checks/context_managers/context_managers6.py +4 -0
  72. pythonlings/curriculum/checks/context_managers/context_managers7.py +16 -0
  73. pythonlings/curriculum/checks/context_managers/context_managers8.py +15 -0
  74. pythonlings/curriculum/checks/dataclasses/dataclasses1.py +3 -0
  75. pythonlings/curriculum/checks/dataclasses/dataclasses2.py +5 -0
  76. pythonlings/curriculum/checks/dataclasses/dataclasses3.py +4 -0
  77. pythonlings/curriculum/checks/dataclasses/dataclasses4.py +3 -0
  78. pythonlings/curriculum/checks/dataclasses/dataclasses5.py +9 -0
  79. pythonlings/curriculum/checks/dataclasses/dataclasses6.py +6 -0
  80. pythonlings/curriculum/checks/dataclasses/dataclasses7.py +6 -0
  81. pythonlings/curriculum/checks/dataclasses/dataclasses8.py +5 -0
  82. pythonlings/curriculum/checks/datetime/datetime1.py +4 -0
  83. pythonlings/curriculum/checks/datetime/datetime2.py +4 -0
  84. pythonlings/curriculum/checks/datetime/datetime3.py +6 -0
  85. pythonlings/curriculum/checks/datetime/datetime4.py +2 -0
  86. pythonlings/curriculum/checks/datetime/datetime5.py +2 -0
  87. pythonlings/curriculum/checks/datetime/datetime6.py +2 -0
  88. pythonlings/curriculum/checks/datetime/datetime7.py +2 -0
  89. pythonlings/curriculum/checks/datetime/datetime8.py +5 -0
  90. pythonlings/curriculum/checks/decorators/decorators1.py +8 -0
  91. pythonlings/curriculum/checks/decorators/decorators10.py +12 -0
  92. pythonlings/curriculum/checks/decorators/decorators2.py +5 -0
  93. pythonlings/curriculum/checks/decorators/decorators3.py +4 -0
  94. pythonlings/curriculum/checks/decorators/decorators4.py +7 -0
  95. pythonlings/curriculum/checks/decorators/decorators5.py +5 -0
  96. pythonlings/curriculum/checks/decorators/decorators6.py +8 -0
  97. pythonlings/curriculum/checks/decorators/decorators7.py +12 -0
  98. pythonlings/curriculum/checks/decorators/decorators8.py +20 -0
  99. pythonlings/curriculum/checks/decorators/decorators9.py +4 -0
  100. pythonlings/curriculum/checks/dictionaries/dictionaries1.py +3 -0
  101. pythonlings/curriculum/checks/dictionaries/dictionaries10.py +5 -0
  102. pythonlings/curriculum/checks/dictionaries/dictionaries2.py +2 -0
  103. pythonlings/curriculum/checks/dictionaries/dictionaries3.py +5 -0
  104. pythonlings/curriculum/checks/dictionaries/dictionaries4.py +5 -0
  105. pythonlings/curriculum/checks/dictionaries/dictionaries5.py +11 -0
  106. pythonlings/curriculum/checks/dictionaries/dictionaries6.py +4 -0
  107. pythonlings/curriculum/checks/dictionaries/dictionaries7.py +5 -0
  108. pythonlings/curriculum/checks/dictionaries/dictionaries8.py +4 -0
  109. pythonlings/curriculum/checks/dictionaries/dictionaries9.py +4 -0
  110. pythonlings/curriculum/checks/enums/enums1.py +4 -0
  111. pythonlings/curriculum/checks/enums/enums2.py +3 -0
  112. pythonlings/curriculum/checks/enums/enums3.py +3 -0
  113. pythonlings/curriculum/checks/enums/enums4.py +2 -0
  114. pythonlings/curriculum/checks/enums/enums5.py +4 -0
  115. pythonlings/curriculum/checks/enums/enums6.py +3 -0
  116. pythonlings/curriculum/checks/exceptions/exceptions1.py +4 -0
  117. pythonlings/curriculum/checks/exceptions/exceptions10.py +39 -0
  118. pythonlings/curriculum/checks/exceptions/exceptions2.py +5 -0
  119. pythonlings/curriculum/checks/exceptions/exceptions3.py +8 -0
  120. pythonlings/curriculum/checks/exceptions/exceptions4.py +5 -0
  121. pythonlings/curriculum/checks/exceptions/exceptions5.py +10 -0
  122. pythonlings/curriculum/checks/exceptions/exceptions6.py +17 -0
  123. pythonlings/curriculum/checks/exceptions/exceptions7.py +15 -0
  124. pythonlings/curriculum/checks/exceptions/exceptions8.py +6 -0
  125. pythonlings/curriculum/checks/exceptions/exceptions9.py +16 -0
  126. pythonlings/curriculum/checks/file_io/file_io1.py +15 -0
  127. pythonlings/curriculum/checks/file_io/file_io10.py +16 -0
  128. pythonlings/curriculum/checks/file_io/file_io2.py +10 -0
  129. pythonlings/curriculum/checks/file_io/file_io3.py +15 -0
  130. pythonlings/curriculum/checks/file_io/file_io4.py +14 -0
  131. pythonlings/curriculum/checks/file_io/file_io5.py +15 -0
  132. pythonlings/curriculum/checks/file_io/file_io6.py +10 -0
  133. pythonlings/curriculum/checks/file_io/file_io7.py +15 -0
  134. pythonlings/curriculum/checks/file_io/file_io8.py +12 -0
  135. pythonlings/curriculum/checks/file_io/file_io9.py +10 -0
  136. pythonlings/curriculum/checks/functional/functional1.py +4 -0
  137. pythonlings/curriculum/checks/functional/functional10.py +7 -0
  138. pythonlings/curriculum/checks/functional/functional2.py +5 -0
  139. pythonlings/curriculum/checks/functional/functional3.py +3 -0
  140. pythonlings/curriculum/checks/functional/functional4.py +4 -0
  141. pythonlings/curriculum/checks/functional/functional5.py +6 -0
  142. pythonlings/curriculum/checks/functional/functional6.py +8 -0
  143. pythonlings/curriculum/checks/functional/functional7.py +8 -0
  144. pythonlings/curriculum/checks/functional/functional8.py +7 -0
  145. pythonlings/curriculum/checks/functional/functional9.py +8 -0
  146. pythonlings/curriculum/checks/functions/functions1.py +9 -0
  147. pythonlings/curriculum/checks/functions/functions10.py +12 -0
  148. pythonlings/curriculum/checks/functions/functions2.py +4 -0
  149. pythonlings/curriculum/checks/functions/functions3.py +6 -0
  150. pythonlings/curriculum/checks/functions/functions4.py +6 -0
  151. pythonlings/curriculum/checks/functions/functions5.py +6 -0
  152. pythonlings/curriculum/checks/functions/functions6.py +8 -0
  153. pythonlings/curriculum/checks/functions/functions7.py +7 -0
  154. pythonlings/curriculum/checks/functions/functions8.py +6 -0
  155. pythonlings/curriculum/checks/functions/functions9.py +7 -0
  156. pythonlings/curriculum/checks/generators/generators1.py +4 -0
  157. pythonlings/curriculum/checks/generators/generators10.py +7 -0
  158. pythonlings/curriculum/checks/generators/generators2.py +3 -0
  159. pythonlings/curriculum/checks/generators/generators3.py +4 -0
  160. pythonlings/curriculum/checks/generators/generators4.py +9 -0
  161. pythonlings/curriculum/checks/generators/generators5.py +10 -0
  162. pythonlings/curriculum/checks/generators/generators6.py +8 -0
  163. pythonlings/curriculum/checks/generators/generators7.py +11 -0
  164. pythonlings/curriculum/checks/generators/generators8.py +8 -0
  165. pythonlings/curriculum/checks/generators/generators9.py +13 -0
  166. pythonlings/curriculum/checks/itertools/itertools1.py +2 -0
  167. pythonlings/curriculum/checks/itertools/itertools2.py +2 -0
  168. pythonlings/curriculum/checks/itertools/itertools3.py +2 -0
  169. pythonlings/curriculum/checks/itertools/itertools4.py +2 -0
  170. pythonlings/curriculum/checks/itertools/itertools5.py +2 -0
  171. pythonlings/curriculum/checks/itertools/itertools6.py +2 -0
  172. pythonlings/curriculum/checks/itertools/itertools7.py +2 -0
  173. pythonlings/curriculum/checks/itertools/itertools8.py +3 -0
  174. pythonlings/curriculum/checks/json/json1.py +3 -0
  175. pythonlings/curriculum/checks/json/json2.py +2 -0
  176. pythonlings/curriculum/checks/json/json3.py +2 -0
  177. pythonlings/curriculum/checks/json/json4.py +2 -0
  178. pythonlings/curriculum/checks/json/json5.py +2 -0
  179. pythonlings/curriculum/checks/json/json6.py +2 -0
  180. pythonlings/curriculum/checks/json/json7.py +3 -0
  181. pythonlings/curriculum/checks/json/json8.py +4 -0
  182. pythonlings/curriculum/checks/lists/lists1.py +2 -0
  183. pythonlings/curriculum/checks/lists/lists10.py +5 -0
  184. pythonlings/curriculum/checks/lists/lists2.py +3 -0
  185. pythonlings/curriculum/checks/lists/lists3.py +4 -0
  186. pythonlings/curriculum/checks/lists/lists4.py +3 -0
  187. pythonlings/curriculum/checks/lists/lists5.py +4 -0
  188. pythonlings/curriculum/checks/lists/lists6.py +3 -0
  189. pythonlings/curriculum/checks/lists/lists7.py +4 -0
  190. pythonlings/curriculum/checks/lists/lists8.py +7 -0
  191. pythonlings/curriculum/checks/lists/lists9.py +4 -0
  192. pythonlings/curriculum/checks/loops/loops1.py +2 -0
  193. pythonlings/curriculum/checks/loops/loops10.py +6 -0
  194. pythonlings/curriculum/checks/loops/loops2.py +2 -0
  195. pythonlings/curriculum/checks/loops/loops3.py +2 -0
  196. pythonlings/curriculum/checks/loops/loops4.py +3 -0
  197. pythonlings/curriculum/checks/loops/loops5.py +2 -0
  198. pythonlings/curriculum/checks/loops/loops6.py +2 -0
  199. pythonlings/curriculum/checks/loops/loops7.py +2 -0
  200. pythonlings/curriculum/checks/loops/loops8.py +2 -0
  201. pythonlings/curriculum/checks/loops/loops9.py +3 -0
  202. pythonlings/curriculum/checks/modules/modules1.py +2 -0
  203. pythonlings/curriculum/checks/modules/modules2.py +2 -0
  204. pythonlings/curriculum/checks/modules/modules3.py +5 -0
  205. pythonlings/curriculum/checks/modules/modules4.py +8 -0
  206. pythonlings/curriculum/checks/modules/modules5.py +7 -0
  207. pythonlings/curriculum/checks/modules/modules6.py +4 -0
  208. pythonlings/curriculum/checks/modules/modules7.py +4 -0
  209. pythonlings/curriculum/checks/modules/modules8.py +15 -0
  210. pythonlings/curriculum/checks/oop_advanced/oop_advanced1.py +4 -0
  211. pythonlings/curriculum/checks/oop_advanced/oop_advanced10.py +3 -0
  212. pythonlings/curriculum/checks/oop_advanced/oop_advanced11.py +3 -0
  213. pythonlings/curriculum/checks/oop_advanced/oop_advanced12.py +4 -0
  214. pythonlings/curriculum/checks/oop_advanced/oop_advanced2.py +4 -0
  215. pythonlings/curriculum/checks/oop_advanced/oop_advanced3.py +3 -0
  216. pythonlings/curriculum/checks/oop_advanced/oop_advanced4.py +3 -0
  217. pythonlings/curriculum/checks/oop_advanced/oop_advanced5.py +3 -0
  218. pythonlings/curriculum/checks/oop_advanced/oop_advanced6.py +2 -0
  219. pythonlings/curriculum/checks/oop_advanced/oop_advanced7.py +3 -0
  220. pythonlings/curriculum/checks/oop_advanced/oop_advanced8.py +3 -0
  221. pythonlings/curriculum/checks/oop_advanced/oop_advanced9.py +4 -0
  222. pythonlings/curriculum/checks/pathlib/pathlib1.py +5 -0
  223. pythonlings/curriculum/checks/pathlib/pathlib2.py +4 -0
  224. pythonlings/curriculum/checks/pathlib/pathlib3.py +6 -0
  225. pythonlings/curriculum/checks/pathlib/pathlib4.py +4 -0
  226. pythonlings/curriculum/checks/pathlib/pathlib5.py +4 -0
  227. pythonlings/curriculum/checks/pathlib/pathlib6.py +5 -0
  228. pythonlings/curriculum/checks/recursion/recursion1.py +5 -0
  229. pythonlings/curriculum/checks/recursion/recursion2.py +5 -0
  230. pythonlings/curriculum/checks/recursion/recursion3.py +6 -0
  231. pythonlings/curriculum/checks/recursion/recursion4.py +6 -0
  232. pythonlings/curriculum/checks/recursion/recursion5.py +7 -0
  233. pythonlings/curriculum/checks/recursion/recursion6.py +7 -0
  234. pythonlings/curriculum/checks/recursion/recursion7.py +7 -0
  235. pythonlings/curriculum/checks/recursion/recursion8.py +15 -0
  236. pythonlings/curriculum/checks/regex/regex1.py +3 -0
  237. pythonlings/curriculum/checks/regex/regex10.py +17 -0
  238. pythonlings/curriculum/checks/regex/regex2.py +3 -0
  239. pythonlings/curriculum/checks/regex/regex3.py +2 -0
  240. pythonlings/curriculum/checks/regex/regex4.py +4 -0
  241. pythonlings/curriculum/checks/regex/regex5.py +4 -0
  242. pythonlings/curriculum/checks/regex/regex6.py +6 -0
  243. pythonlings/curriculum/checks/regex/regex7.py +5 -0
  244. pythonlings/curriculum/checks/regex/regex8.py +6 -0
  245. pythonlings/curriculum/checks/regex/regex9.py +8 -0
  246. pythonlings/curriculum/checks/sets/sets1.py +3 -0
  247. pythonlings/curriculum/checks/sets/sets10.py +15 -0
  248. pythonlings/curriculum/checks/sets/sets2.py +5 -0
  249. pythonlings/curriculum/checks/sets/sets3.py +5 -0
  250. pythonlings/curriculum/checks/sets/sets4.py +3 -0
  251. pythonlings/curriculum/checks/sets/sets5.py +3 -0
  252. pythonlings/curriculum/checks/sets/sets6.py +3 -0
  253. pythonlings/curriculum/checks/sets/sets7.py +5 -0
  254. pythonlings/curriculum/checks/sets/sets8.py +5 -0
  255. pythonlings/curriculum/checks/sets/sets9.py +3 -0
  256. pythonlings/curriculum/checks/strings/strings1.py +3 -0
  257. pythonlings/curriculum/checks/strings/strings10.py +5 -0
  258. pythonlings/curriculum/checks/strings/strings2.py +3 -0
  259. pythonlings/curriculum/checks/strings/strings3.py +3 -0
  260. pythonlings/curriculum/checks/strings/strings4.py +2 -0
  261. pythonlings/curriculum/checks/strings/strings5.py +4 -0
  262. pythonlings/curriculum/checks/strings/strings6.py +3 -0
  263. pythonlings/curriculum/checks/strings/strings7.py +3 -0
  264. pythonlings/curriculum/checks/strings/strings8.py +3 -0
  265. pythonlings/curriculum/checks/strings/strings9.py +3 -0
  266. pythonlings/curriculum/checks/testing/testing1.py +2 -0
  267. pythonlings/curriculum/checks/testing/testing10.py +9 -0
  268. pythonlings/curriculum/checks/testing/testing11.py +7 -0
  269. pythonlings/curriculum/checks/testing/testing12.py +19 -0
  270. pythonlings/curriculum/checks/testing/testing2.py +2 -0
  271. pythonlings/curriculum/checks/testing/testing3.py +12 -0
  272. pythonlings/curriculum/checks/testing/testing4.py +6 -0
  273. pythonlings/curriculum/checks/testing/testing5.py +6 -0
  274. pythonlings/curriculum/checks/testing/testing6.py +14 -0
  275. pythonlings/curriculum/checks/testing/testing7.py +14 -0
  276. pythonlings/curriculum/checks/testing/testing8.py +27 -0
  277. pythonlings/curriculum/checks/testing/testing9.py +14 -0
  278. pythonlings/curriculum/checks/tuples/tuples1.py +3 -0
  279. pythonlings/curriculum/checks/tuples/tuples10.py +6 -0
  280. pythonlings/curriculum/checks/tuples/tuples2.py +3 -0
  281. pythonlings/curriculum/checks/tuples/tuples3.py +4 -0
  282. pythonlings/curriculum/checks/tuples/tuples4.py +3 -0
  283. pythonlings/curriculum/checks/tuples/tuples5.py +4 -0
  284. pythonlings/curriculum/checks/tuples/tuples6.py +5 -0
  285. pythonlings/curriculum/checks/tuples/tuples7.py +3 -0
  286. pythonlings/curriculum/checks/tuples/tuples8.py +4 -0
  287. pythonlings/curriculum/checks/tuples/tuples9.py +5 -0
  288. pythonlings/curriculum/checks/type_hints/type_hints1.py +3 -0
  289. pythonlings/curriculum/checks/type_hints/type_hints2.py +5 -0
  290. pythonlings/curriculum/checks/type_hints/type_hints3.py +5 -0
  291. pythonlings/curriculum/checks/type_hints/type_hints4.py +5 -0
  292. pythonlings/curriculum/checks/type_hints/type_hints5.py +6 -0
  293. pythonlings/curriculum/checks/type_hints/type_hints6.py +7 -0
  294. pythonlings/curriculum/checks/type_hints/type_hints7.py +9 -0
  295. pythonlings/curriculum/checks/type_hints/type_hints8.py +9 -0
  296. pythonlings/curriculum/checks/variables/variables1.py +7 -0
  297. pythonlings/curriculum/checks/variables/variables10.py +4 -0
  298. pythonlings/curriculum/checks/variables/variables2.py +19 -0
  299. pythonlings/curriculum/checks/variables/variables3.py +4 -0
  300. pythonlings/curriculum/checks/variables/variables4.py +2 -0
  301. pythonlings/curriculum/checks/variables/variables5.py +2 -0
  302. pythonlings/curriculum/checks/variables/variables6.py +4 -0
  303. pythonlings/curriculum/checks/variables/variables7.py +3 -0
  304. pythonlings/curriculum/checks/variables/variables8.py +4 -0
  305. pythonlings/curriculum/checks/variables/variables9.py +3 -0
  306. pythonlings/curriculum/exercises/async/async1.py +7 -0
  307. pythonlings/curriculum/exercises/async/async10.py +14 -0
  308. pythonlings/curriculum/exercises/async/async2.py +10 -0
  309. pythonlings/curriculum/exercises/async/async3.py +10 -0
  310. pythonlings/curriculum/exercises/async/async4.py +15 -0
  311. pythonlings/curriculum/exercises/async/async5.py +13 -0
  312. pythonlings/curriculum/exercises/async/async6.py +14 -0
  313. pythonlings/curriculum/exercises/async/async7.py +13 -0
  314. pythonlings/curriculum/exercises/async/async8.py +11 -0
  315. pythonlings/curriculum/exercises/async/async9.py +13 -0
  316. pythonlings/curriculum/exercises/classes/classes1.py +13 -0
  317. pythonlings/curriculum/exercises/classes/classes10.py +22 -0
  318. pythonlings/curriculum/exercises/classes/classes11.py +24 -0
  319. pythonlings/curriculum/exercises/classes/classes12.py +33 -0
  320. pythonlings/curriculum/exercises/classes/classes2.py +13 -0
  321. pythonlings/curriculum/exercises/classes/classes3.py +13 -0
  322. pythonlings/curriculum/exercises/classes/classes4.py +16 -0
  323. pythonlings/curriculum/exercises/classes/classes5.py +18 -0
  324. pythonlings/curriculum/exercises/classes/classes6.py +15 -0
  325. pythonlings/curriculum/exercises/classes/classes7.py +18 -0
  326. pythonlings/curriculum/exercises/classes/classes8.py +14 -0
  327. pythonlings/curriculum/exercises/classes/classes9.py +18 -0
  328. pythonlings/curriculum/exercises/collections/collections1.py +11 -0
  329. pythonlings/curriculum/exercises/collections/collections10.py +37 -0
  330. pythonlings/curriculum/exercises/collections/collections2.py +18 -0
  331. pythonlings/curriculum/exercises/collections/collections3.py +17 -0
  332. pythonlings/curriculum/exercises/collections/collections4.py +26 -0
  333. pythonlings/curriculum/exercises/collections/collections5.py +14 -0
  334. pythonlings/curriculum/exercises/collections/collections6.py +18 -0
  335. pythonlings/curriculum/exercises/collections/collections7.py +20 -0
  336. pythonlings/curriculum/exercises/collections/collections8.py +23 -0
  337. pythonlings/curriculum/exercises/collections/collections9.py +21 -0
  338. pythonlings/curriculum/exercises/comprehensions/comprehensions1.py +10 -0
  339. pythonlings/curriculum/exercises/comprehensions/comprehensions10.py +15 -0
  340. pythonlings/curriculum/exercises/comprehensions/comprehensions2.py +12 -0
  341. pythonlings/curriculum/exercises/comprehensions/comprehensions3.py +12 -0
  342. pythonlings/curriculum/exercises/comprehensions/comprehensions4.py +14 -0
  343. pythonlings/curriculum/exercises/comprehensions/comprehensions5.py +12 -0
  344. pythonlings/curriculum/exercises/comprehensions/comprehensions6.py +12 -0
  345. pythonlings/curriculum/exercises/comprehensions/comprehensions7.py +12 -0
  346. pythonlings/curriculum/exercises/comprehensions/comprehensions8.py +15 -0
  347. pythonlings/curriculum/exercises/comprehensions/comprehensions9.py +13 -0
  348. pythonlings/curriculum/exercises/conditionals/conditionals1.py +15 -0
  349. pythonlings/curriculum/exercises/conditionals/conditionals10.py +18 -0
  350. pythonlings/curriculum/exercises/conditionals/conditionals2.py +13 -0
  351. pythonlings/curriculum/exercises/conditionals/conditionals3.py +18 -0
  352. pythonlings/curriculum/exercises/conditionals/conditionals4.py +20 -0
  353. pythonlings/curriculum/exercises/conditionals/conditionals5.py +17 -0
  354. pythonlings/curriculum/exercises/conditionals/conditionals6.py +15 -0
  355. pythonlings/curriculum/exercises/conditionals/conditionals7.py +18 -0
  356. pythonlings/curriculum/exercises/conditionals/conditionals8.py +20 -0
  357. pythonlings/curriculum/exercises/conditionals/conditionals9.py +12 -0
  358. pythonlings/curriculum/exercises/context_managers/context_managers1.py +18 -0
  359. pythonlings/curriculum/exercises/context_managers/context_managers2.py +19 -0
  360. pythonlings/curriculum/exercises/context_managers/context_managers3.py +24 -0
  361. pythonlings/curriculum/exercises/context_managers/context_managers4.py +30 -0
  362. pythonlings/curriculum/exercises/context_managers/context_managers5.py +30 -0
  363. pythonlings/curriculum/exercises/context_managers/context_managers6.py +26 -0
  364. pythonlings/curriculum/exercises/context_managers/context_managers7.py +30 -0
  365. pythonlings/curriculum/exercises/context_managers/context_managers8.py +45 -0
  366. pythonlings/curriculum/exercises/dataclasses/dataclasses1.py +14 -0
  367. pythonlings/curriculum/exercises/dataclasses/dataclasses2.py +23 -0
  368. pythonlings/curriculum/exercises/dataclasses/dataclasses3.py +21 -0
  369. pythonlings/curriculum/exercises/dataclasses/dataclasses4.py +25 -0
  370. pythonlings/curriculum/exercises/dataclasses/dataclasses5.py +25 -0
  371. pythonlings/curriculum/exercises/dataclasses/dataclasses6.py +21 -0
  372. pythonlings/curriculum/exercises/dataclasses/dataclasses7.py +27 -0
  373. pythonlings/curriculum/exercises/dataclasses/dataclasses8.py +30 -0
  374. pythonlings/curriculum/exercises/datetime/datetime1.py +8 -0
  375. pythonlings/curriculum/exercises/datetime/datetime2.py +9 -0
  376. pythonlings/curriculum/exercises/datetime/datetime3.py +9 -0
  377. pythonlings/curriculum/exercises/datetime/datetime4.py +9 -0
  378. pythonlings/curriculum/exercises/datetime/datetime5.py +10 -0
  379. pythonlings/curriculum/exercises/datetime/datetime6.py +10 -0
  380. pythonlings/curriculum/exercises/datetime/datetime7.py +10 -0
  381. pythonlings/curriculum/exercises/datetime/datetime8.py +11 -0
  382. pythonlings/curriculum/exercises/decorators/decorators1.py +19 -0
  383. pythonlings/curriculum/exercises/decorators/decorators10.py +32 -0
  384. pythonlings/curriculum/exercises/decorators/decorators2.py +22 -0
  385. pythonlings/curriculum/exercises/decorators/decorators3.py +18 -0
  386. pythonlings/curriculum/exercises/decorators/decorators4.py +24 -0
  387. pythonlings/curriculum/exercises/decorators/decorators5.py +25 -0
  388. pythonlings/curriculum/exercises/decorators/decorators6.py +19 -0
  389. pythonlings/curriculum/exercises/decorators/decorators7.py +25 -0
  390. pythonlings/curriculum/exercises/decorators/decorators8.py +25 -0
  391. pythonlings/curriculum/exercises/decorators/decorators9.py +26 -0
  392. pythonlings/curriculum/exercises/dictionaries/dictionaries1.py +6 -0
  393. pythonlings/curriculum/exercises/dictionaries/dictionaries10.py +17 -0
  394. pythonlings/curriculum/exercises/dictionaries/dictionaries2.py +9 -0
  395. pythonlings/curriculum/exercises/dictionaries/dictionaries3.py +16 -0
  396. pythonlings/curriculum/exercises/dictionaries/dictionaries4.py +15 -0
  397. pythonlings/curriculum/exercises/dictionaries/dictionaries5.py +15 -0
  398. pythonlings/curriculum/exercises/dictionaries/dictionaries6.py +18 -0
  399. pythonlings/curriculum/exercises/dictionaries/dictionaries7.py +17 -0
  400. pythonlings/curriculum/exercises/dictionaries/dictionaries8.py +17 -0
  401. pythonlings/curriculum/exercises/dictionaries/dictionaries9.py +25 -0
  402. pythonlings/curriculum/exercises/enums/enums1.py +12 -0
  403. pythonlings/curriculum/exercises/enums/enums2.py +11 -0
  404. pythonlings/curriculum/exercises/enums/enums3.py +14 -0
  405. pythonlings/curriculum/exercises/enums/enums4.py +13 -0
  406. pythonlings/curriculum/exercises/enums/enums5.py +14 -0
  407. pythonlings/curriculum/exercises/enums/enums6.py +15 -0
  408. pythonlings/curriculum/exercises/exceptions/exceptions1.py +15 -0
  409. pythonlings/curriculum/exercises/exceptions/exceptions10.py +19 -0
  410. pythonlings/curriculum/exercises/exceptions/exceptions2.py +12 -0
  411. pythonlings/curriculum/exercises/exceptions/exceptions3.py +13 -0
  412. pythonlings/curriculum/exercises/exceptions/exceptions4.py +17 -0
  413. pythonlings/curriculum/exercises/exceptions/exceptions5.py +20 -0
  414. pythonlings/curriculum/exercises/exceptions/exceptions6.py +13 -0
  415. pythonlings/curriculum/exercises/exceptions/exceptions7.py +15 -0
  416. pythonlings/curriculum/exercises/exceptions/exceptions8.py +17 -0
  417. pythonlings/curriculum/exercises/exceptions/exceptions9.py +20 -0
  418. pythonlings/curriculum/exercises/file_io/file_io1.py +18 -0
  419. pythonlings/curriculum/exercises/file_io/file_io10.py +25 -0
  420. pythonlings/curriculum/exercises/file_io/file_io2.py +16 -0
  421. pythonlings/curriculum/exercises/file_io/file_io3.py +18 -0
  422. pythonlings/curriculum/exercises/file_io/file_io4.py +21 -0
  423. pythonlings/curriculum/exercises/file_io/file_io5.py +19 -0
  424. pythonlings/curriculum/exercises/file_io/file_io6.py +22 -0
  425. pythonlings/curriculum/exercises/file_io/file_io7.py +20 -0
  426. pythonlings/curriculum/exercises/file_io/file_io8.py +18 -0
  427. pythonlings/curriculum/exercises/file_io/file_io9.py +19 -0
  428. pythonlings/curriculum/exercises/functional/functional1.py +7 -0
  429. pythonlings/curriculum/exercises/functional/functional10.py +19 -0
  430. pythonlings/curriculum/exercises/functional/functional2.py +8 -0
  431. pythonlings/curriculum/exercises/functional/functional3.py +11 -0
  432. pythonlings/curriculum/exercises/functional/functional4.py +11 -0
  433. pythonlings/curriculum/exercises/functional/functional5.py +11 -0
  434. pythonlings/curriculum/exercises/functional/functional6.py +14 -0
  435. pythonlings/curriculum/exercises/functional/functional7.py +12 -0
  436. pythonlings/curriculum/exercises/functional/functional8.py +13 -0
  437. pythonlings/curriculum/exercises/functional/functional9.py +15 -0
  438. pythonlings/curriculum/exercises/functions/functions1.py +8 -0
  439. pythonlings/curriculum/exercises/functions/functions10.py +19 -0
  440. pythonlings/curriculum/exercises/functions/functions2.py +9 -0
  441. pythonlings/curriculum/exercises/functions/functions3.py +8 -0
  442. pythonlings/curriculum/exercises/functions/functions4.py +8 -0
  443. pythonlings/curriculum/exercises/functions/functions5.py +11 -0
  444. pythonlings/curriculum/exercises/functions/functions6.py +16 -0
  445. pythonlings/curriculum/exercises/functions/functions7.py +11 -0
  446. pythonlings/curriculum/exercises/functions/functions8.py +10 -0
  447. pythonlings/curriculum/exercises/functions/functions9.py +16 -0
  448. pythonlings/curriculum/exercises/generators/generators1.py +13 -0
  449. pythonlings/curriculum/exercises/generators/generators10.py +16 -0
  450. pythonlings/curriculum/exercises/generators/generators2.py +17 -0
  451. pythonlings/curriculum/exercises/generators/generators3.py +19 -0
  452. pythonlings/curriculum/exercises/generators/generators4.py +12 -0
  453. pythonlings/curriculum/exercises/generators/generators5.py +24 -0
  454. pythonlings/curriculum/exercises/generators/generators6.py +20 -0
  455. pythonlings/curriculum/exercises/generators/generators7.py +14 -0
  456. pythonlings/curriculum/exercises/generators/generators8.py +17 -0
  457. pythonlings/curriculum/exercises/generators/generators9.py +26 -0
  458. pythonlings/curriculum/exercises/itertools/itertools1.py +10 -0
  459. pythonlings/curriculum/exercises/itertools/itertools2.py +8 -0
  460. pythonlings/curriculum/exercises/itertools/itertools3.py +10 -0
  461. pythonlings/curriculum/exercises/itertools/itertools4.py +12 -0
  462. pythonlings/curriculum/exercises/itertools/itertools5.py +9 -0
  463. pythonlings/curriculum/exercises/itertools/itertools6.py +9 -0
  464. pythonlings/curriculum/exercises/itertools/itertools7.py +10 -0
  465. pythonlings/curriculum/exercises/itertools/itertools8.py +18 -0
  466. pythonlings/curriculum/exercises/json/json1.py +10 -0
  467. pythonlings/curriculum/exercises/json/json2.py +9 -0
  468. pythonlings/curriculum/exercises/json/json3.py +10 -0
  469. pythonlings/curriculum/exercises/json/json4.py +13 -0
  470. pythonlings/curriculum/exercises/json/json5.py +9 -0
  471. pythonlings/curriculum/exercises/json/json6.py +10 -0
  472. pythonlings/curriculum/exercises/json/json7.py +10 -0
  473. pythonlings/curriculum/exercises/json/json8.py +12 -0
  474. pythonlings/curriculum/exercises/lists/lists1.py +7 -0
  475. pythonlings/curriculum/exercises/lists/lists10.py +28 -0
  476. pythonlings/curriculum/exercises/lists/lists2.py +13 -0
  477. pythonlings/curriculum/exercises/lists/lists3.py +16 -0
  478. pythonlings/curriculum/exercises/lists/lists4.py +16 -0
  479. pythonlings/curriculum/exercises/lists/lists5.py +16 -0
  480. pythonlings/curriculum/exercises/lists/lists6.py +15 -0
  481. pythonlings/curriculum/exercises/lists/lists7.py +13 -0
  482. pythonlings/curriculum/exercises/lists/lists8.py +15 -0
  483. pythonlings/curriculum/exercises/lists/lists9.py +19 -0
  484. pythonlings/curriculum/exercises/loops/loops1.py +8 -0
  485. pythonlings/curriculum/exercises/loops/loops10.py +22 -0
  486. pythonlings/curriculum/exercises/loops/loops2.py +10 -0
  487. pythonlings/curriculum/exercises/loops/loops3.py +10 -0
  488. pythonlings/curriculum/exercises/loops/loops4.py +12 -0
  489. pythonlings/curriculum/exercises/loops/loops5.py +13 -0
  490. pythonlings/curriculum/exercises/loops/loops6.py +10 -0
  491. pythonlings/curriculum/exercises/loops/loops7.py +13 -0
  492. pythonlings/curriculum/exercises/loops/loops8.py +11 -0
  493. pythonlings/curriculum/exercises/loops/loops9.py +11 -0
  494. pythonlings/curriculum/exercises/modules/modules1.py +10 -0
  495. pythonlings/curriculum/exercises/modules/modules2.py +10 -0
  496. pythonlings/curriculum/exercises/modules/modules3.py +13 -0
  497. pythonlings/curriculum/exercises/modules/modules4.py +18 -0
  498. pythonlings/curriculum/exercises/modules/modules5.py +17 -0
  499. pythonlings/curriculum/exercises/modules/modules6.py +12 -0
  500. pythonlings/curriculum/exercises/modules/modules7.py +16 -0
  501. pythonlings/curriculum/exercises/modules/modules8.py +23 -0
  502. pythonlings/curriculum/exercises/oop_advanced/oop_advanced1.py +11 -0
  503. pythonlings/curriculum/exercises/oop_advanced/oop_advanced10.py +18 -0
  504. pythonlings/curriculum/exercises/oop_advanced/oop_advanced11.py +13 -0
  505. pythonlings/curriculum/exercises/oop_advanced/oop_advanced12.py +12 -0
  506. pythonlings/curriculum/exercises/oop_advanced/oop_advanced2.py +13 -0
  507. pythonlings/curriculum/exercises/oop_advanced/oop_advanced3.py +22 -0
  508. pythonlings/curriculum/exercises/oop_advanced/oop_advanced4.py +12 -0
  509. pythonlings/curriculum/exercises/oop_advanced/oop_advanced5.py +11 -0
  510. pythonlings/curriculum/exercises/oop_advanced/oop_advanced6.py +12 -0
  511. pythonlings/curriculum/exercises/oop_advanced/oop_advanced7.py +14 -0
  512. pythonlings/curriculum/exercises/oop_advanced/oop_advanced8.py +14 -0
  513. pythonlings/curriculum/exercises/oop_advanced/oop_advanced9.py +9 -0
  514. pythonlings/curriculum/exercises/pathlib/pathlib1.py +9 -0
  515. pythonlings/curriculum/exercises/pathlib/pathlib2.py +9 -0
  516. pythonlings/curriculum/exercises/pathlib/pathlib3.py +11 -0
  517. pythonlings/curriculum/exercises/pathlib/pathlib4.py +9 -0
  518. pythonlings/curriculum/exercises/pathlib/pathlib5.py +9 -0
  519. pythonlings/curriculum/exercises/pathlib/pathlib6.py +10 -0
  520. pythonlings/curriculum/exercises/recursion/recursion1.py +12 -0
  521. pythonlings/curriculum/exercises/recursion/recursion2.py +12 -0
  522. pythonlings/curriculum/exercises/recursion/recursion3.py +12 -0
  523. pythonlings/curriculum/exercises/recursion/recursion4.py +12 -0
  524. pythonlings/curriculum/exercises/recursion/recursion5.py +14 -0
  525. pythonlings/curriculum/exercises/recursion/recursion6.py +13 -0
  526. pythonlings/curriculum/exercises/recursion/recursion7.py +20 -0
  527. pythonlings/curriculum/exercises/recursion/recursion8.py +27 -0
  528. pythonlings/curriculum/exercises/regex/regex1.py +16 -0
  529. pythonlings/curriculum/exercises/regex/regex10.py +31 -0
  530. pythonlings/curriculum/exercises/regex/regex2.py +16 -0
  531. pythonlings/curriculum/exercises/regex/regex3.py +16 -0
  532. pythonlings/curriculum/exercises/regex/regex4.py +19 -0
  533. pythonlings/curriculum/exercises/regex/regex5.py +19 -0
  534. pythonlings/curriculum/exercises/regex/regex6.py +23 -0
  535. pythonlings/curriculum/exercises/regex/regex7.py +24 -0
  536. pythonlings/curriculum/exercises/regex/regex8.py +19 -0
  537. pythonlings/curriculum/exercises/regex/regex9.py +22 -0
  538. pythonlings/curriculum/exercises/sets/sets1.py +6 -0
  539. pythonlings/curriculum/exercises/sets/sets10.py +21 -0
  540. pythonlings/curriculum/exercises/sets/sets2.py +12 -0
  541. pythonlings/curriculum/exercises/sets/sets3.py +15 -0
  542. pythonlings/curriculum/exercises/sets/sets4.py +14 -0
  543. pythonlings/curriculum/exercises/sets/sets5.py +14 -0
  544. pythonlings/curriculum/exercises/sets/sets6.py +14 -0
  545. pythonlings/curriculum/exercises/sets/sets7.py +16 -0
  546. pythonlings/curriculum/exercises/sets/sets8.py +16 -0
  547. pythonlings/curriculum/exercises/sets/sets9.py +15 -0
  548. pythonlings/curriculum/exercises/strings/strings1.py +6 -0
  549. pythonlings/curriculum/exercises/strings/strings10.py +11 -0
  550. pythonlings/curriculum/exercises/strings/strings2.py +9 -0
  551. pythonlings/curriculum/exercises/strings/strings3.py +9 -0
  552. pythonlings/curriculum/exercises/strings/strings4.py +8 -0
  553. pythonlings/curriculum/exercises/strings/strings5.py +10 -0
  554. pythonlings/curriculum/exercises/strings/strings6.py +10 -0
  555. pythonlings/curriculum/exercises/strings/strings7.py +10 -0
  556. pythonlings/curriculum/exercises/strings/strings8.py +9 -0
  557. pythonlings/curriculum/exercises/strings/strings9.py +10 -0
  558. pythonlings/curriculum/exercises/testing/testing1.py +16 -0
  559. pythonlings/curriculum/exercises/testing/testing10.py +21 -0
  560. pythonlings/curriculum/exercises/testing/testing11.py +27 -0
  561. pythonlings/curriculum/exercises/testing/testing12.py +51 -0
  562. pythonlings/curriculum/exercises/testing/testing2.py +15 -0
  563. pythonlings/curriculum/exercises/testing/testing3.py +19 -0
  564. pythonlings/curriculum/exercises/testing/testing4.py +19 -0
  565. pythonlings/curriculum/exercises/testing/testing5.py +18 -0
  566. pythonlings/curriculum/exercises/testing/testing6.py +27 -0
  567. pythonlings/curriculum/exercises/testing/testing7.py +27 -0
  568. pythonlings/curriculum/exercises/testing/testing8.py +29 -0
  569. pythonlings/curriculum/exercises/testing/testing9.py +30 -0
  570. pythonlings/curriculum/exercises/tuples/tuples1.py +7 -0
  571. pythonlings/curriculum/exercises/tuples/tuples10.py +26 -0
  572. pythonlings/curriculum/exercises/tuples/tuples2.py +11 -0
  573. pythonlings/curriculum/exercises/tuples/tuples3.py +9 -0
  574. pythonlings/curriculum/exercises/tuples/tuples4.py +8 -0
  575. pythonlings/curriculum/exercises/tuples/tuples5.py +12 -0
  576. pythonlings/curriculum/exercises/tuples/tuples6.py +15 -0
  577. pythonlings/curriculum/exercises/tuples/tuples7.py +14 -0
  578. pythonlings/curriculum/exercises/tuples/tuples8.py +13 -0
  579. pythonlings/curriculum/exercises/tuples/tuples9.py +18 -0
  580. pythonlings/curriculum/exercises/type_hints/type_hints1.py +9 -0
  581. pythonlings/curriculum/exercises/type_hints/type_hints2.py +13 -0
  582. pythonlings/curriculum/exercises/type_hints/type_hints3.py +13 -0
  583. pythonlings/curriculum/exercises/type_hints/type_hints4.py +21 -0
  584. pythonlings/curriculum/exercises/type_hints/type_hints5.py +15 -0
  585. pythonlings/curriculum/exercises/type_hints/type_hints6.py +16 -0
  586. pythonlings/curriculum/exercises/type_hints/type_hints7.py +19 -0
  587. pythonlings/curriculum/exercises/type_hints/type_hints8.py +27 -0
  588. pythonlings/curriculum/exercises/variables/variables1.py +10 -0
  589. pythonlings/curriculum/exercises/variables/variables10.py +14 -0
  590. pythonlings/curriculum/exercises/variables/variables2.py +11 -0
  591. pythonlings/curriculum/exercises/variables/variables3.py +11 -0
  592. pythonlings/curriculum/exercises/variables/variables4.py +9 -0
  593. pythonlings/curriculum/exercises/variables/variables5.py +11 -0
  594. pythonlings/curriculum/exercises/variables/variables6.py +9 -0
  595. pythonlings/curriculum/exercises/variables/variables7.py +10 -0
  596. pythonlings/curriculum/exercises/variables/variables8.py +12 -0
  597. pythonlings/curriculum/exercises/variables/variables9.py +10 -0
  598. pythonlings/curriculum/info.toml +1755 -0
  599. pythonlings/curriculum/solutions/.keep +1 -0
  600. pythonlings/curriculum/solutions/_answers.py +1654 -0
  601. pythonlings/curriculum/solutions/async1.py +2 -0
  602. pythonlings/curriculum/solutions/async10.py +2 -0
  603. pythonlings/curriculum/solutions/async2.py +2 -0
  604. pythonlings/curriculum/solutions/async3.py +2 -0
  605. pythonlings/curriculum/solutions/async4.py +2 -0
  606. pythonlings/curriculum/solutions/async5.py +2 -0
  607. pythonlings/curriculum/solutions/async6.py +2 -0
  608. pythonlings/curriculum/solutions/async7.py +2 -0
  609. pythonlings/curriculum/solutions/async8.py +2 -0
  610. pythonlings/curriculum/solutions/async9.py +2 -0
  611. pythonlings/curriculum/solutions/classes1.py +2 -0
  612. pythonlings/curriculum/solutions/classes10.py +2 -0
  613. pythonlings/curriculum/solutions/classes11.py +2 -0
  614. pythonlings/curriculum/solutions/classes12.py +2 -0
  615. pythonlings/curriculum/solutions/classes2.py +2 -0
  616. pythonlings/curriculum/solutions/classes3.py +2 -0
  617. pythonlings/curriculum/solutions/classes4.py +2 -0
  618. pythonlings/curriculum/solutions/classes5.py +2 -0
  619. pythonlings/curriculum/solutions/classes6.py +2 -0
  620. pythonlings/curriculum/solutions/classes7.py +2 -0
  621. pythonlings/curriculum/solutions/classes8.py +2 -0
  622. pythonlings/curriculum/solutions/classes9.py +2 -0
  623. pythonlings/curriculum/solutions/collections1.py +2 -0
  624. pythonlings/curriculum/solutions/collections10.py +2 -0
  625. pythonlings/curriculum/solutions/collections2.py +2 -0
  626. pythonlings/curriculum/solutions/collections3.py +2 -0
  627. pythonlings/curriculum/solutions/collections4.py +2 -0
  628. pythonlings/curriculum/solutions/collections5.py +2 -0
  629. pythonlings/curriculum/solutions/collections6.py +2 -0
  630. pythonlings/curriculum/solutions/collections7.py +2 -0
  631. pythonlings/curriculum/solutions/collections8.py +2 -0
  632. pythonlings/curriculum/solutions/collections9.py +2 -0
  633. pythonlings/curriculum/solutions/comprehensions1.py +2 -0
  634. pythonlings/curriculum/solutions/comprehensions10.py +2 -0
  635. pythonlings/curriculum/solutions/comprehensions2.py +2 -0
  636. pythonlings/curriculum/solutions/comprehensions3.py +2 -0
  637. pythonlings/curriculum/solutions/comprehensions4.py +2 -0
  638. pythonlings/curriculum/solutions/comprehensions5.py +2 -0
  639. pythonlings/curriculum/solutions/comprehensions6.py +2 -0
  640. pythonlings/curriculum/solutions/comprehensions7.py +2 -0
  641. pythonlings/curriculum/solutions/comprehensions8.py +2 -0
  642. pythonlings/curriculum/solutions/comprehensions9.py +2 -0
  643. pythonlings/curriculum/solutions/conditionals1.py +2 -0
  644. pythonlings/curriculum/solutions/conditionals10.py +2 -0
  645. pythonlings/curriculum/solutions/conditionals2.py +2 -0
  646. pythonlings/curriculum/solutions/conditionals3.py +2 -0
  647. pythonlings/curriculum/solutions/conditionals4.py +2 -0
  648. pythonlings/curriculum/solutions/conditionals5.py +2 -0
  649. pythonlings/curriculum/solutions/conditionals6.py +2 -0
  650. pythonlings/curriculum/solutions/conditionals7.py +2 -0
  651. pythonlings/curriculum/solutions/conditionals8.py +2 -0
  652. pythonlings/curriculum/solutions/conditionals9.py +2 -0
  653. pythonlings/curriculum/solutions/context_managers1.py +2 -0
  654. pythonlings/curriculum/solutions/context_managers2.py +2 -0
  655. pythonlings/curriculum/solutions/context_managers3.py +2 -0
  656. pythonlings/curriculum/solutions/context_managers4.py +2 -0
  657. pythonlings/curriculum/solutions/context_managers5.py +2 -0
  658. pythonlings/curriculum/solutions/context_managers6.py +2 -0
  659. pythonlings/curriculum/solutions/context_managers7.py +2 -0
  660. pythonlings/curriculum/solutions/context_managers8.py +2 -0
  661. pythonlings/curriculum/solutions/dataclasses1.py +2 -0
  662. pythonlings/curriculum/solutions/dataclasses2.py +2 -0
  663. pythonlings/curriculum/solutions/dataclasses3.py +2 -0
  664. pythonlings/curriculum/solutions/dataclasses4.py +2 -0
  665. pythonlings/curriculum/solutions/dataclasses5.py +2 -0
  666. pythonlings/curriculum/solutions/dataclasses6.py +2 -0
  667. pythonlings/curriculum/solutions/dataclasses7.py +2 -0
  668. pythonlings/curriculum/solutions/dataclasses8.py +2 -0
  669. pythonlings/curriculum/solutions/datetime1.py +2 -0
  670. pythonlings/curriculum/solutions/datetime2.py +2 -0
  671. pythonlings/curriculum/solutions/datetime3.py +2 -0
  672. pythonlings/curriculum/solutions/datetime4.py +2 -0
  673. pythonlings/curriculum/solutions/datetime5.py +2 -0
  674. pythonlings/curriculum/solutions/datetime6.py +2 -0
  675. pythonlings/curriculum/solutions/datetime7.py +2 -0
  676. pythonlings/curriculum/solutions/datetime8.py +2 -0
  677. pythonlings/curriculum/solutions/decorators1.py +2 -0
  678. pythonlings/curriculum/solutions/decorators10.py +2 -0
  679. pythonlings/curriculum/solutions/decorators2.py +2 -0
  680. pythonlings/curriculum/solutions/decorators3.py +2 -0
  681. pythonlings/curriculum/solutions/decorators4.py +2 -0
  682. pythonlings/curriculum/solutions/decorators5.py +2 -0
  683. pythonlings/curriculum/solutions/decorators6.py +2 -0
  684. pythonlings/curriculum/solutions/decorators7.py +2 -0
  685. pythonlings/curriculum/solutions/decorators8.py +2 -0
  686. pythonlings/curriculum/solutions/decorators9.py +2 -0
  687. pythonlings/curriculum/solutions/dictionaries1.py +2 -0
  688. pythonlings/curriculum/solutions/dictionaries10.py +2 -0
  689. pythonlings/curriculum/solutions/dictionaries2.py +2 -0
  690. pythonlings/curriculum/solutions/dictionaries3.py +2 -0
  691. pythonlings/curriculum/solutions/dictionaries4.py +2 -0
  692. pythonlings/curriculum/solutions/dictionaries5.py +2 -0
  693. pythonlings/curriculum/solutions/dictionaries6.py +2 -0
  694. pythonlings/curriculum/solutions/dictionaries7.py +2 -0
  695. pythonlings/curriculum/solutions/dictionaries8.py +2 -0
  696. pythonlings/curriculum/solutions/dictionaries9.py +2 -0
  697. pythonlings/curriculum/solutions/enums1.py +2 -0
  698. pythonlings/curriculum/solutions/enums2.py +2 -0
  699. pythonlings/curriculum/solutions/enums3.py +2 -0
  700. pythonlings/curriculum/solutions/enums4.py +2 -0
  701. pythonlings/curriculum/solutions/enums5.py +2 -0
  702. pythonlings/curriculum/solutions/enums6.py +2 -0
  703. pythonlings/curriculum/solutions/exceptions1.py +2 -0
  704. pythonlings/curriculum/solutions/exceptions10.py +2 -0
  705. pythonlings/curriculum/solutions/exceptions2.py +2 -0
  706. pythonlings/curriculum/solutions/exceptions3.py +2 -0
  707. pythonlings/curriculum/solutions/exceptions4.py +2 -0
  708. pythonlings/curriculum/solutions/exceptions5.py +2 -0
  709. pythonlings/curriculum/solutions/exceptions6.py +2 -0
  710. pythonlings/curriculum/solutions/exceptions7.py +2 -0
  711. pythonlings/curriculum/solutions/exceptions8.py +2 -0
  712. pythonlings/curriculum/solutions/exceptions9.py +2 -0
  713. pythonlings/curriculum/solutions/file_io1.py +2 -0
  714. pythonlings/curriculum/solutions/file_io10.py +2 -0
  715. pythonlings/curriculum/solutions/file_io2.py +2 -0
  716. pythonlings/curriculum/solutions/file_io3.py +2 -0
  717. pythonlings/curriculum/solutions/file_io4.py +2 -0
  718. pythonlings/curriculum/solutions/file_io5.py +2 -0
  719. pythonlings/curriculum/solutions/file_io6.py +2 -0
  720. pythonlings/curriculum/solutions/file_io7.py +2 -0
  721. pythonlings/curriculum/solutions/file_io8.py +2 -0
  722. pythonlings/curriculum/solutions/file_io9.py +2 -0
  723. pythonlings/curriculum/solutions/functional1.py +2 -0
  724. pythonlings/curriculum/solutions/functional10.py +2 -0
  725. pythonlings/curriculum/solutions/functional2.py +2 -0
  726. pythonlings/curriculum/solutions/functional3.py +2 -0
  727. pythonlings/curriculum/solutions/functional4.py +2 -0
  728. pythonlings/curriculum/solutions/functional5.py +2 -0
  729. pythonlings/curriculum/solutions/functional6.py +2 -0
  730. pythonlings/curriculum/solutions/functional7.py +2 -0
  731. pythonlings/curriculum/solutions/functional8.py +2 -0
  732. pythonlings/curriculum/solutions/functional9.py +2 -0
  733. pythonlings/curriculum/solutions/functions1.py +2 -0
  734. pythonlings/curriculum/solutions/functions10.py +2 -0
  735. pythonlings/curriculum/solutions/functions2.py +2 -0
  736. pythonlings/curriculum/solutions/functions3.py +2 -0
  737. pythonlings/curriculum/solutions/functions4.py +2 -0
  738. pythonlings/curriculum/solutions/functions5.py +2 -0
  739. pythonlings/curriculum/solutions/functions6.py +2 -0
  740. pythonlings/curriculum/solutions/functions7.py +2 -0
  741. pythonlings/curriculum/solutions/functions8.py +2 -0
  742. pythonlings/curriculum/solutions/functions9.py +2 -0
  743. pythonlings/curriculum/solutions/generators1.py +2 -0
  744. pythonlings/curriculum/solutions/generators10.py +2 -0
  745. pythonlings/curriculum/solutions/generators2.py +2 -0
  746. pythonlings/curriculum/solutions/generators3.py +2 -0
  747. pythonlings/curriculum/solutions/generators4.py +2 -0
  748. pythonlings/curriculum/solutions/generators5.py +2 -0
  749. pythonlings/curriculum/solutions/generators6.py +2 -0
  750. pythonlings/curriculum/solutions/generators7.py +2 -0
  751. pythonlings/curriculum/solutions/generators8.py +2 -0
  752. pythonlings/curriculum/solutions/generators9.py +2 -0
  753. pythonlings/curriculum/solutions/itertools1.py +2 -0
  754. pythonlings/curriculum/solutions/itertools2.py +2 -0
  755. pythonlings/curriculum/solutions/itertools3.py +2 -0
  756. pythonlings/curriculum/solutions/itertools4.py +2 -0
  757. pythonlings/curriculum/solutions/itertools5.py +2 -0
  758. pythonlings/curriculum/solutions/itertools6.py +2 -0
  759. pythonlings/curriculum/solutions/itertools7.py +2 -0
  760. pythonlings/curriculum/solutions/itertools8.py +2 -0
  761. pythonlings/curriculum/solutions/json1.py +2 -0
  762. pythonlings/curriculum/solutions/json2.py +2 -0
  763. pythonlings/curriculum/solutions/json3.py +2 -0
  764. pythonlings/curriculum/solutions/json4.py +2 -0
  765. pythonlings/curriculum/solutions/json5.py +2 -0
  766. pythonlings/curriculum/solutions/json6.py +2 -0
  767. pythonlings/curriculum/solutions/json7.py +2 -0
  768. pythonlings/curriculum/solutions/json8.py +2 -0
  769. pythonlings/curriculum/solutions/lists1.py +2 -0
  770. pythonlings/curriculum/solutions/lists10.py +2 -0
  771. pythonlings/curriculum/solutions/lists2.py +2 -0
  772. pythonlings/curriculum/solutions/lists3.py +2 -0
  773. pythonlings/curriculum/solutions/lists4.py +2 -0
  774. pythonlings/curriculum/solutions/lists5.py +2 -0
  775. pythonlings/curriculum/solutions/lists6.py +2 -0
  776. pythonlings/curriculum/solutions/lists7.py +2 -0
  777. pythonlings/curriculum/solutions/lists8.py +2 -0
  778. pythonlings/curriculum/solutions/lists9.py +2 -0
  779. pythonlings/curriculum/solutions/loops1.py +2 -0
  780. pythonlings/curriculum/solutions/loops10.py +2 -0
  781. pythonlings/curriculum/solutions/loops2.py +2 -0
  782. pythonlings/curriculum/solutions/loops3.py +2 -0
  783. pythonlings/curriculum/solutions/loops4.py +2 -0
  784. pythonlings/curriculum/solutions/loops5.py +2 -0
  785. pythonlings/curriculum/solutions/loops6.py +2 -0
  786. pythonlings/curriculum/solutions/loops7.py +2 -0
  787. pythonlings/curriculum/solutions/loops8.py +2 -0
  788. pythonlings/curriculum/solutions/loops9.py +2 -0
  789. pythonlings/curriculum/solutions/modules1.py +2 -0
  790. pythonlings/curriculum/solutions/modules2.py +2 -0
  791. pythonlings/curriculum/solutions/modules3.py +2 -0
  792. pythonlings/curriculum/solutions/modules4.py +2 -0
  793. pythonlings/curriculum/solutions/modules5.py +2 -0
  794. pythonlings/curriculum/solutions/modules6.py +2 -0
  795. pythonlings/curriculum/solutions/modules7.py +2 -0
  796. pythonlings/curriculum/solutions/modules8.py +2 -0
  797. pythonlings/curriculum/solutions/oop_advanced1.py +2 -0
  798. pythonlings/curriculum/solutions/oop_advanced10.py +2 -0
  799. pythonlings/curriculum/solutions/oop_advanced11.py +2 -0
  800. pythonlings/curriculum/solutions/oop_advanced12.py +2 -0
  801. pythonlings/curriculum/solutions/oop_advanced2.py +2 -0
  802. pythonlings/curriculum/solutions/oop_advanced3.py +2 -0
  803. pythonlings/curriculum/solutions/oop_advanced4.py +2 -0
  804. pythonlings/curriculum/solutions/oop_advanced5.py +2 -0
  805. pythonlings/curriculum/solutions/oop_advanced6.py +2 -0
  806. pythonlings/curriculum/solutions/oop_advanced7.py +2 -0
  807. pythonlings/curriculum/solutions/oop_advanced8.py +2 -0
  808. pythonlings/curriculum/solutions/oop_advanced9.py +2 -0
  809. pythonlings/curriculum/solutions/pathlib1.py +2 -0
  810. pythonlings/curriculum/solutions/pathlib2.py +2 -0
  811. pythonlings/curriculum/solutions/pathlib3.py +2 -0
  812. pythonlings/curriculum/solutions/pathlib4.py +2 -0
  813. pythonlings/curriculum/solutions/pathlib5.py +2 -0
  814. pythonlings/curriculum/solutions/pathlib6.py +2 -0
  815. pythonlings/curriculum/solutions/recursion1.py +2 -0
  816. pythonlings/curriculum/solutions/recursion2.py +2 -0
  817. pythonlings/curriculum/solutions/recursion3.py +2 -0
  818. pythonlings/curriculum/solutions/recursion4.py +2 -0
  819. pythonlings/curriculum/solutions/recursion5.py +2 -0
  820. pythonlings/curriculum/solutions/recursion6.py +2 -0
  821. pythonlings/curriculum/solutions/recursion7.py +2 -0
  822. pythonlings/curriculum/solutions/recursion8.py +2 -0
  823. pythonlings/curriculum/solutions/regex1.py +2 -0
  824. pythonlings/curriculum/solutions/regex10.py +2 -0
  825. pythonlings/curriculum/solutions/regex2.py +2 -0
  826. pythonlings/curriculum/solutions/regex3.py +2 -0
  827. pythonlings/curriculum/solutions/regex4.py +2 -0
  828. pythonlings/curriculum/solutions/regex5.py +2 -0
  829. pythonlings/curriculum/solutions/regex6.py +2 -0
  830. pythonlings/curriculum/solutions/regex7.py +2 -0
  831. pythonlings/curriculum/solutions/regex8.py +2 -0
  832. pythonlings/curriculum/solutions/regex9.py +2 -0
  833. pythonlings/curriculum/solutions/sets1.py +2 -0
  834. pythonlings/curriculum/solutions/sets10.py +2 -0
  835. pythonlings/curriculum/solutions/sets2.py +2 -0
  836. pythonlings/curriculum/solutions/sets3.py +2 -0
  837. pythonlings/curriculum/solutions/sets4.py +2 -0
  838. pythonlings/curriculum/solutions/sets5.py +2 -0
  839. pythonlings/curriculum/solutions/sets6.py +2 -0
  840. pythonlings/curriculum/solutions/sets7.py +2 -0
  841. pythonlings/curriculum/solutions/sets8.py +2 -0
  842. pythonlings/curriculum/solutions/sets9.py +2 -0
  843. pythonlings/curriculum/solutions/strings1.py +2 -0
  844. pythonlings/curriculum/solutions/strings10.py +2 -0
  845. pythonlings/curriculum/solutions/strings2.py +2 -0
  846. pythonlings/curriculum/solutions/strings3.py +2 -0
  847. pythonlings/curriculum/solutions/strings4.py +2 -0
  848. pythonlings/curriculum/solutions/strings5.py +2 -0
  849. pythonlings/curriculum/solutions/strings6.py +2 -0
  850. pythonlings/curriculum/solutions/strings7.py +2 -0
  851. pythonlings/curriculum/solutions/strings8.py +2 -0
  852. pythonlings/curriculum/solutions/strings9.py +2 -0
  853. pythonlings/curriculum/solutions/testing1.py +2 -0
  854. pythonlings/curriculum/solutions/testing10.py +2 -0
  855. pythonlings/curriculum/solutions/testing11.py +2 -0
  856. pythonlings/curriculum/solutions/testing12.py +2 -0
  857. pythonlings/curriculum/solutions/testing2.py +2 -0
  858. pythonlings/curriculum/solutions/testing3.py +2 -0
  859. pythonlings/curriculum/solutions/testing4.py +2 -0
  860. pythonlings/curriculum/solutions/testing5.py +2 -0
  861. pythonlings/curriculum/solutions/testing6.py +2 -0
  862. pythonlings/curriculum/solutions/testing7.py +2 -0
  863. pythonlings/curriculum/solutions/testing8.py +2 -0
  864. pythonlings/curriculum/solutions/testing9.py +2 -0
  865. pythonlings/curriculum/solutions/tuples1.py +2 -0
  866. pythonlings/curriculum/solutions/tuples10.py +2 -0
  867. pythonlings/curriculum/solutions/tuples2.py +2 -0
  868. pythonlings/curriculum/solutions/tuples3.py +2 -0
  869. pythonlings/curriculum/solutions/tuples4.py +2 -0
  870. pythonlings/curriculum/solutions/tuples5.py +2 -0
  871. pythonlings/curriculum/solutions/tuples6.py +2 -0
  872. pythonlings/curriculum/solutions/tuples7.py +2 -0
  873. pythonlings/curriculum/solutions/tuples8.py +2 -0
  874. pythonlings/curriculum/solutions/tuples9.py +2 -0
  875. pythonlings/curriculum/solutions/type_hints1.py +2 -0
  876. pythonlings/curriculum/solutions/type_hints2.py +2 -0
  877. pythonlings/curriculum/solutions/type_hints3.py +2 -0
  878. pythonlings/curriculum/solutions/type_hints4.py +2 -0
  879. pythonlings/curriculum/solutions/type_hints5.py +2 -0
  880. pythonlings/curriculum/solutions/type_hints6.py +2 -0
  881. pythonlings/curriculum/solutions/type_hints7.py +2 -0
  882. pythonlings/curriculum/solutions/type_hints8.py +2 -0
  883. pythonlings/curriculum/solutions/variables1.py +2 -0
  884. pythonlings/curriculum/solutions/variables10.py +2 -0
  885. pythonlings/curriculum/solutions/variables2.py +2 -0
  886. pythonlings/curriculum/solutions/variables3.py +2 -0
  887. pythonlings/curriculum/solutions/variables4.py +2 -0
  888. pythonlings/curriculum/solutions/variables5.py +2 -0
  889. pythonlings/curriculum/solutions/variables6.py +2 -0
  890. pythonlings/curriculum/solutions/variables7.py +2 -0
  891. pythonlings/curriculum/solutions/variables8.py +2 -0
  892. pythonlings/curriculum/solutions/variables9.py +2 -0
  893. pythonlings/docs/NOTICE.md +9 -0
  894. pythonlings/docs/__init__.py +1 -0
  895. pythonlings/docs/index.json +160 -0
  896. pythonlings/docs/topics/__init__.py +1 -0
  897. pythonlings/docs/topics/async.md +97 -0
  898. pythonlings/docs/topics/classes.md +97 -0
  899. pythonlings/docs/topics/collections.md +96 -0
  900. pythonlings/docs/topics/comprehensions.md +97 -0
  901. pythonlings/docs/topics/conditionals.md +88 -0
  902. pythonlings/docs/topics/context_managers.md +97 -0
  903. pythonlings/docs/topics/dataclasses.md +97 -0
  904. pythonlings/docs/topics/datetime.md +97 -0
  905. pythonlings/docs/topics/decorators.md +91 -0
  906. pythonlings/docs/topics/dictionaries.md +96 -0
  907. pythonlings/docs/topics/enums.md +96 -0
  908. pythonlings/docs/topics/exceptions.md +96 -0
  909. pythonlings/docs/topics/file_io.md +96 -0
  910. pythonlings/docs/topics/functional.md +96 -0
  911. pythonlings/docs/topics/functions.md +97 -0
  912. pythonlings/docs/topics/generators.md +97 -0
  913. pythonlings/docs/topics/itertools.md +96 -0
  914. pythonlings/docs/topics/json.md +97 -0
  915. pythonlings/docs/topics/lists.md +97 -0
  916. pythonlings/docs/topics/loops.md +95 -0
  917. pythonlings/docs/topics/modules.md +97 -0
  918. pythonlings/docs/topics/oop_advanced.md +71 -0
  919. pythonlings/docs/topics/pathlib.md +79 -0
  920. pythonlings/docs/topics/recursion.md +73 -0
  921. pythonlings/docs/topics/regex.md +96 -0
  922. pythonlings/docs/topics/sets.md +97 -0
  923. pythonlings/docs/topics/strings.md +96 -0
  924. pythonlings/docs/topics/testing.md +97 -0
  925. pythonlings/docs/topics/tuples.md +97 -0
  926. pythonlings/docs/topics/type_hints.md +97 -0
  927. pythonlings/docs/topics/variables.md +97 -0
  928. pythonlings/pythonlings.tcss +123 -0
  929. pythonlings/screens/__init__.py +0 -0
  930. pythonlings/screens/docs.py +93 -0
  931. pythonlings/screens/topic_picker.py +107 -0
  932. pythonlings/screens/track.py +234 -0
  933. pythonlings/widgets/__init__.py +0 -0
  934. pythonlings/widgets/editor_pane.py +33 -0
  935. pythonlings/widgets/exercise_tree.py +33 -0
  936. pythonlings/widgets/output_panel.py +180 -0
  937. pythonlings/widgets/progress.py +15 -0
  938. pythonlings-0.3.0.dist-info/METADATA +259 -0
  939. pythonlings-0.3.0.dist-info/RECORD +942 -0
  940. pythonlings-0.3.0.dist-info/WHEEL +4 -0
  941. pythonlings-0.3.0.dist-info/entry_points.txt +2 -0
  942. pythonlings-0.3.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,93 @@
1
+ # pythonlings/screens/docs.py
2
+ from __future__ import annotations
3
+
4
+ import webbrowser
5
+ from urllib.parse import urldefrag
6
+
7
+ from rich.markup import escape
8
+ from textual.app import ComposeResult
9
+ from textual.binding import Binding
10
+ from textual.containers import Vertical, VerticalScroll
11
+ from textual.screen import ModalScreen
12
+ from textual.widgets import Markdown, Static
13
+
14
+ from pythonlings.core.docs import load_snippet
15
+ from pythonlings.core.exercise import Exercise
16
+
17
+
18
+ class DocsScreen(ModalScreen[None]):
19
+ """Small in-app reference window for the current exercise."""
20
+
21
+ BINDINGS = [
22
+ Binding("escape", "close", "Close", priority=True),
23
+ Binding("o", "open_browser", "Open"),
24
+ ]
25
+
26
+ def __init__(self, exercise: Exercise) -> None:
27
+ super().__init__()
28
+ self.exercise = exercise
29
+
30
+ def compose(self) -> ComposeResult:
31
+ yield Vertical(
32
+ Static(f"[bold]Docs: {escape(self.exercise.name)}[/bold]", id="docs-title"),
33
+ VerticalScroll(
34
+ Markdown(self._reference_markdown(), id="docs-content", open_links=False),
35
+ id="docs-scroll",
36
+ ),
37
+ Static("O Open official docs | Esc Close", id="docs-footer"),
38
+ id="docs-window",
39
+ )
40
+
41
+ def action_close(self) -> None:
42
+ self.dismiss()
43
+
44
+ def action_open_browser(self) -> None:
45
+ if self.exercise.docs:
46
+ webbrowser.open(self.exercise.docs)
47
+
48
+ def _reference_markdown(self) -> str:
49
+ docs = self.exercise.docs or "(no official docs link configured)"
50
+ hint = self.exercise.hint.strip() or "Use this reference while solving."
51
+ snippet = load_snippet(self.exercise.topic, self.exercise.docs)
52
+ if snippet is None:
53
+ local_reference = "No local reference is bundled for this topic."
54
+ snippet_source = ""
55
+ else:
56
+ local_reference = _display_reference(snippet.text)
57
+ snippet_source = snippet.source_url
58
+
59
+ source_section = ""
60
+ if snippet_source and _page_url(snippet_source) != _page_url(docs):
61
+ source_section = f"\n\n## Local snippet source\n\n{snippet_source}"
62
+
63
+ return (
64
+ f"## Local reference\n\n{local_reference}\n\n"
65
+ f"## Quick note\n\n{hint}\n\n"
66
+ f"## Official Python docs\n\n{docs}{source_section}\n"
67
+ )
68
+
69
+
70
+ def _display_reference(text: str) -> str:
71
+ """Remove file metadata so the modal starts with useful reference text."""
72
+ lines = text.strip().splitlines()
73
+ if lines and lines[0].startswith("# "):
74
+ lines = lines[1:]
75
+ lines = _drop_leading_blanks(lines)
76
+
77
+ if lines and lines[0].startswith("Source: "):
78
+ lines = lines[1:]
79
+ lines = _drop_leading_blanks(lines)
80
+
81
+ if lines and lines[0].startswith("This local reference is generated"):
82
+ lines = lines[1:]
83
+ return "\n".join(_drop_leading_blanks(lines)).strip()
84
+
85
+
86
+ def _drop_leading_blanks(lines: list[str]) -> list[str]:
87
+ while lines and not lines[0].strip():
88
+ lines = lines[1:]
89
+ return lines
90
+
91
+
92
+ def _page_url(url: str) -> str:
93
+ return urldefrag(url).url
@@ -0,0 +1,107 @@
1
+ # pythonlings/screens/topic_picker.py
2
+ from __future__ import annotations
3
+
4
+ from textual.app import ComposeResult
5
+ from textual.binding import Binding
6
+ from textual.containers import VerticalScroll
7
+ from textual.screen import Screen
8
+ from textual.widgets import Footer, Header, ListItem, ListView, Static
9
+
10
+ from pythonlings.core.state import save as save_state
11
+
12
+
13
+ class TopicPickerScreen(Screen[None]):
14
+ """Entry screen: choose a topic to work on."""
15
+
16
+ BINDINGS = [
17
+ Binding("escape", "quit", "Quit", priority=True),
18
+ Binding("ctrl+q", "quit", "Quit"),
19
+ ]
20
+
21
+ def compose(self) -> ComposeResult:
22
+ yield Header()
23
+ yield Static("", id="topic-banner")
24
+ yield VerticalScroll(ListView(id="topics"))
25
+ yield Footer()
26
+
27
+ def on_mount(self) -> None:
28
+ self.app.title = "pythonlings"
29
+ self.app.sub_title = "choose a topic"
30
+ self._populate()
31
+
32
+ def on_screen_resume(self) -> None:
33
+ self._populate()
34
+
35
+ def _populate(self) -> None:
36
+ listview = self.query_one("#topics", ListView)
37
+ listview.clear()
38
+ self.query_one("#topic-banner", Static).update(self._banner_text())
39
+ manifest = self.app.manifest
40
+ completed = self.app.state.completed
41
+ selected_index = 0
42
+ preferred = self._preferred_topic()
43
+ for topic_index, topic in enumerate(manifest.topics()):
44
+ exs = manifest.exercises_in(topic)
45
+ done = sum(1 for ex in exs if ex.name in completed)
46
+ if done == len(exs):
47
+ mark = "✓"
48
+ status = "Done"
49
+ elif done:
50
+ mark = "●"
51
+ status = "Continue"
52
+ else:
53
+ mark = " "
54
+ status = "Start"
55
+ label = f"{mark} {topic:<18} {done}/{len(exs):<5} {status}"
56
+ listview.append(
57
+ ListItem(Static(label, classes="topic-row"), name=topic)
58
+ )
59
+ if topic == preferred:
60
+ selected_index = topic_index
61
+ if listview.children:
62
+ listview.index = selected_index
63
+ listview.focus()
64
+
65
+ def _topic_progress(self, topic: str) -> tuple[int, int]:
66
+ exs = self.app.manifest.exercises_in(topic)
67
+ done = sum(1 for ex in exs if ex.name in self.app.state.completed)
68
+ return done, len(exs)
69
+
70
+ def _first_incomplete_topic(self) -> str | None:
71
+ for topic in self.app.manifest.topics():
72
+ done, total = self._topic_progress(topic)
73
+ if done < total:
74
+ return topic
75
+ return None
76
+
77
+ def _preferred_topic(self) -> str | None:
78
+ topic = self.app.state.last_topic
79
+ if topic in self.app.manifest.topics():
80
+ done, total = self._topic_progress(topic)
81
+ if done < total:
82
+ return topic
83
+ return self._first_incomplete_topic() or (
84
+ self.app.manifest.topics()[0] if self.app.manifest.topics() else None
85
+ )
86
+
87
+ def _banner_text(self) -> str:
88
+ first = self._first_incomplete_topic()
89
+ if first is None:
90
+ return self.app.manifest.final_message
91
+ if not self.app.state.seen_intro:
92
+ return f"Start here: {first}"
93
+ if self.app.state.last_topic is None:
94
+ return "Choose a topic to practice."
95
+ return "Topics"
96
+
97
+ def on_list_view_selected(self, event: ListView.Selected) -> None:
98
+ topic = event.item.name
99
+ if topic:
100
+ from pythonlings.screens.track import TrackScreen
101
+
102
+ self.app.state.record_resume(topic, None)
103
+ save_state(self.app.root, self.app.state)
104
+ self.app.push_screen(TrackScreen(topic))
105
+
106
+ def action_quit(self) -> None:
107
+ self.app.exit(0)
@@ -0,0 +1,234 @@
1
+ # pythonlings/screens/track.py
2
+ from __future__ import annotations
3
+
4
+ from textual.app import ComposeResult
5
+ from textual.binding import Binding
6
+ from textual.containers import Horizontal
7
+ from textual.screen import Screen
8
+ from textual.timer import Timer
9
+ from textual.widgets import Footer, Header, TextArea
10
+
11
+ from pythonlings.core.exercise import Exercise, RunResult
12
+ from pythonlings.core.runner import run as run_exercise
13
+ from pythonlings.core.state import next_pending, save as save_state
14
+ from pythonlings.widgets.editor_pane import EditorPane
15
+ from pythonlings.widgets.exercise_tree import ExerciseTree
16
+ from pythonlings.widgets.output_panel import OutputPanel
17
+ from pythonlings.widgets.progress import ProgressBar
18
+
19
+ _DEBOUNCE_SECONDS = 0.6
20
+
21
+
22
+ class TrackScreen(Screen[None]):
23
+ """One topic's linear track: editor + output + auto-save loop."""
24
+
25
+ BINDINGS = [
26
+ Binding("f1", "toggle_hint", "Hint"),
27
+ Binding("f2", "reset", "Reset"),
28
+ Binding("f3", "toggle_list", "List"),
29
+ Binding("f4", "topics", "Topics"),
30
+ Binding("f5", "docs", "Docs"),
31
+ Binding("escape", "quit", "Quit", priority=True),
32
+ Binding("ctrl+q", "quit", "Quit"),
33
+ ]
34
+
35
+ def __init__(self, topic: str, start_exercise: str | None = None) -> None:
36
+ super().__init__()
37
+ self.topic = topic
38
+ self._start_exercise = start_exercise
39
+ self._save_timer: Timer | None = None
40
+ self._loaded_text = ""
41
+ self._failure_counts: dict[str, int] = {}
42
+ self.current: str | None = None
43
+
44
+ def compose(self) -> ComposeResult:
45
+ yield Header()
46
+ yield ProgressBar(id="progress")
47
+ yield Horizontal(
48
+ ExerciseTree(),
49
+ EditorPane(id="editor"),
50
+ OutputPanel(id="output"),
51
+ id="main",
52
+ )
53
+ yield Footer()
54
+
55
+ def on_mount(self) -> None:
56
+ self.app.sub_title = f"topic: {self.topic}"
57
+ self.current = self._initial_exercise()
58
+ self._render_state()
59
+ if self.current is None:
60
+ self.query_one(OutputPanel).show_final(
61
+ f"Topic '{self.topic}' complete."
62
+ )
63
+ return
64
+ self._load_current()
65
+ self._run_current()
66
+ self.query_one(EditorPane).focus_editor()
67
+
68
+ # --- helpers ---------------------------------------------------------
69
+
70
+ def _exercises(self) -> list[Exercise]:
71
+ return self.app.manifest.exercises_in(self.topic)
72
+
73
+ def _initial_exercise(self) -> str | None:
74
+ names = {ex.name for ex in self._exercises()}
75
+ if (
76
+ self._start_exercise in names
77
+ and self._start_exercise not in self.app.state.completed
78
+ ):
79
+ return self._start_exercise
80
+ return next_pending(self._exercises(), self.app.state.completed)
81
+
82
+ def _render_state(self) -> None:
83
+ exs = self._exercises()
84
+ done = sum(1 for ex in exs if ex.name in self.app.state.completed)
85
+ self.query_one(ProgressBar).update_progress(done, len(exs))
86
+ self.query_one(ExerciseTree).render_topic(
87
+ self.topic, exs, self.app.state.completed, self.current
88
+ )
89
+
90
+ def _progress_counts(self) -> tuple[int, int]:
91
+ exs = self._exercises()
92
+ done = sum(1 for ex in exs if ex.name in self.app.state.completed)
93
+ return done, len(exs)
94
+
95
+ def _exercise(self, name: str) -> Exercise:
96
+ for ex in self._exercises():
97
+ if ex.name == name:
98
+ return ex
99
+ raise KeyError(name)
100
+
101
+ def _load_current(self) -> None:
102
+ if self.current is None:
103
+ return
104
+ if self._save_timer is not None:
105
+ self._save_timer.stop()
106
+ self._save_timer = None
107
+ self.query_one(OutputPanel).reset_hint()
108
+ pane = self.query_one(EditorPane)
109
+ pane.load_exercise(self._exercise(self.current))
110
+ self._loaded_text = pane.text
111
+ self._failure_counts[self.current] = 0
112
+ self._record_resume(self.current)
113
+
114
+ def _record_resume(self, exercise: str | None) -> None:
115
+ self.app.state.record_resume(self.topic, exercise)
116
+ save_state(self.app.root, self.app.state)
117
+
118
+ # --- auto-save / run loop -------------------------------------------
119
+
120
+ def on_text_area_changed(self, event: TextArea.Changed) -> None:
121
+ if event.text_area is not self.query_one("#code", TextArea):
122
+ return
123
+ if self.query_one(EditorPane).text == self._loaded_text:
124
+ return
125
+ if self._save_timer is not None:
126
+ self._save_timer.stop()
127
+ self._save_timer = self.set_timer(_DEBOUNCE_SECONDS, self._flush_and_run)
128
+
129
+ def _flush_and_run(self) -> None:
130
+ self._save_timer = None
131
+ if self.current is None:
132
+ return
133
+ ex = self._exercise(self.current)
134
+ ex.path.write_text(self.query_one(EditorPane).text, encoding="utf-8")
135
+ self._run_current()
136
+
137
+ def _run_current(self) -> None:
138
+ if self.current is None:
139
+ return
140
+ ex = self._exercise(self.current)
141
+ completed, total = self._progress_counts()
142
+ self.query_one(OutputPanel).render_running(ex, completed, total)
143
+ self.run_worker(
144
+ lambda: self._run_blocking(ex), exclusive=True, thread=True
145
+ )
146
+
147
+ def _run_blocking(self, exercise: Exercise) -> None:
148
+ result = run_exercise(exercise)
149
+ self.app.call_from_thread(self._apply_result, exercise, result)
150
+
151
+ def _apply_result(self, exercise: Exercise, result: RunResult) -> None:
152
+ if not self.is_attached:
153
+ return # the track screen was popped while a run was in flight
154
+ if exercise.name != self.current:
155
+ return
156
+ if result.exit_code != 0 or result.timed_out:
157
+ self._failure_counts[exercise.name] = (
158
+ self._failure_counts.get(exercise.name, 0) + 1
159
+ )
160
+ else:
161
+ self._failure_counts[exercise.name] = 0
162
+ completed, total = self._progress_counts()
163
+ self.query_one(OutputPanel).render_result(
164
+ exercise,
165
+ result,
166
+ failures=self._failure_counts.get(exercise.name, 0),
167
+ completed=completed,
168
+ total=total,
169
+ )
170
+ if not result.passed:
171
+ return
172
+ self.app.state.mark_done(exercise.name)
173
+ save_state(self.app.root, self.app.state)
174
+ self.current = next_pending(self._exercises(), self.app.state.completed)
175
+ self._render_state()
176
+ if self.current is None:
177
+ self._record_resume(None)
178
+ self.query_one(OutputPanel).show_final(
179
+ f"Topic '{self.topic}' complete — press F4 for topics."
180
+ )
181
+ return
182
+ self._load_current()
183
+ self._run_current()
184
+
185
+ # --- actions ---------------------------------------------------------
186
+
187
+ def action_toggle_hint(self) -> None:
188
+ if self.current is None:
189
+ return
190
+ self.query_one(OutputPanel).toggle_hint(self._exercise(self.current).hint)
191
+
192
+ def action_reset(self) -> None:
193
+ from pythonlings.core.reset import restore
194
+
195
+ if self.current is None:
196
+ return
197
+ if self._save_timer is not None:
198
+ self._save_timer.stop()
199
+ self._save_timer = None
200
+ restore(self.app.root, self._exercise(self.current))
201
+ self._load_current()
202
+ self._run_current()
203
+
204
+ def action_toggle_list(self) -> None:
205
+ tree = self.query_one(ExerciseTree)
206
+ tree.display = not tree.display
207
+
208
+ def action_docs(self) -> None:
209
+ if self.current is None:
210
+ return
211
+ from pythonlings.screens.docs import DocsScreen
212
+
213
+ self.app.push_screen(DocsScreen(self._exercise(self.current)))
214
+
215
+ def action_topics(self) -> None:
216
+ self._flush_pending()
217
+ if self.current is not None:
218
+ self._record_resume(self.current)
219
+ self.app.pop_screen()
220
+
221
+ def action_quit(self) -> None:
222
+ self._flush_pending()
223
+ if self.current is not None:
224
+ self._record_resume(self.current)
225
+ self.app.exit(0)
226
+
227
+ def _flush_pending(self) -> None:
228
+ if self._save_timer is not None:
229
+ self._save_timer.stop()
230
+ self._save_timer = None
231
+ if self.current is not None:
232
+ self._exercise(self.current).path.write_text(
233
+ self.query_one(EditorPane).text, encoding="utf-8"
234
+ )
File without changes
@@ -0,0 +1,33 @@
1
+ # pythonlings/widgets/editor_pane.py
2
+ from __future__ import annotations
3
+
4
+ from textual.app import ComposeResult
5
+ from textual.containers import Vertical
6
+ from textual.widgets import TextArea
7
+
8
+ from pythonlings.core.exercise import Exercise
9
+
10
+
11
+ class EditorPane(Vertical):
12
+ """The in-TUI code editor for the current exercise."""
13
+
14
+ def compose(self) -> ComposeResult:
15
+ try:
16
+ editor = TextArea.code_editor("", language="python", id="code")
17
+ except Exception:
18
+ # tree-sitter Python grammar unavailable — fall back to plain text.
19
+ editor = TextArea.code_editor("", id="code")
20
+ yield editor
21
+
22
+ def load_exercise(self, exercise: Exercise) -> None:
23
+ """Replace the editor contents with the exercise file from disk."""
24
+ area = self.query_one("#code", TextArea)
25
+ area.text = exercise.path.read_text(encoding="utf-8")
26
+ area.move_cursor((0, 0))
27
+
28
+ def focus_editor(self) -> None:
29
+ self.query_one("#code", TextArea).focus()
30
+
31
+ @property
32
+ def text(self) -> str:
33
+ return self.query_one("#code", TextArea).text
@@ -0,0 +1,33 @@
1
+ # pythonlings/widgets/exercise_tree.py
2
+ from __future__ import annotations
3
+
4
+ from typing import TYPE_CHECKING
5
+
6
+ from textual.widgets import Tree
7
+
8
+ if TYPE_CHECKING:
9
+ from pythonlings.core.exercise import Exercise
10
+
11
+
12
+ class ExerciseTree(Tree[str]):
13
+ def __init__(self) -> None:
14
+ super().__init__("exercises", id="tree")
15
+ self.show_root = False
16
+
17
+ def render_topic(
18
+ self,
19
+ topic: str,
20
+ exercises: list["Exercise"],
21
+ completed: set[str],
22
+ current: str | None,
23
+ ) -> None:
24
+ self.clear()
25
+ node = self.root.add(topic, expand=True)
26
+ for ex in exercises:
27
+ if ex.name in completed:
28
+ marker = "✓"
29
+ elif ex.name == current:
30
+ marker = "●"
31
+ else:
32
+ marker = "🔒"
33
+ node.add_leaf(f"{marker} {ex.name}", data=ex.name)
@@ -0,0 +1,180 @@
1
+ # pythonlings/widgets/output_panel.py
2
+ from __future__ import annotations
3
+
4
+ import os
5
+
6
+ from textual.app import ComposeResult
7
+ from textual.containers import Vertical
8
+ from textual.widgets import Static
9
+
10
+ from pythonlings.core.exercise import Exercise, RunResult
11
+
12
+ _INSTRUCTION = "Edit the code on the left. Checks update automatically."
13
+
14
+
15
+ class OutputPanel(Vertical):
16
+ def compose(self) -> ComposeResult:
17
+ yield Static("", id="output-header")
18
+ yield Static("", id="goal")
19
+ yield Static("", id="docs")
20
+ yield Static("Loading…", id="status")
21
+ yield Static("", id="next-step")
22
+ yield Static("", id="details")
23
+ yield Static("", id="hint")
24
+
25
+ def renderable_text(self) -> str:
26
+ parts: list[str] = []
27
+ for widget_id in (
28
+ "output-header",
29
+ "goal",
30
+ "docs",
31
+ "status",
32
+ "next-step",
33
+ "details",
34
+ "hint",
35
+ ):
36
+ parts.append(str(self.query_one(f"#{widget_id}", Static).content))
37
+ return "\n".join(parts)
38
+
39
+ def render_running(
40
+ self, exercise: Exercise, completed: int = 0, total: int = 0
41
+ ) -> None:
42
+ self.remove_class("passed", "failed", "pending")
43
+ self._render_header(exercise, completed, total)
44
+ self.query_one("#goal", Static).update(
45
+ f"[bold]Goal[/bold]\n{self._goal_from(exercise)}"
46
+ )
47
+ self._render_docs(exercise)
48
+ self.query_one("#status", Static).update(
49
+ "[bold blue]Running checks...[/bold blue]"
50
+ )
51
+ self.query_one("#next-step", Static).update(
52
+ "Keep editing; results update automatically."
53
+ )
54
+ self.query_one("#details", Static).update("")
55
+
56
+ def render_result(
57
+ self,
58
+ exercise: Exercise,
59
+ result: RunResult,
60
+ failures: int = 0,
61
+ completed: int = 0,
62
+ total: int = 0,
63
+ ) -> None:
64
+ self._render_header(exercise, completed, total)
65
+ self.query_one("#goal", Static).update(
66
+ f"[bold]Goal[/bold]\n{self._goal_from(exercise)}"
67
+ )
68
+ self._render_docs(exercise)
69
+ details = (result.stderr or result.stdout).rstrip()
70
+ self.query_one("#details", Static).update(
71
+ f"[bold]Details[/bold]\n{details}" if details else ""
72
+ )
73
+ self.remove_class("passed", "failed", "pending")
74
+ if result.timed_out:
75
+ self.add_class("failed")
76
+ self.query_one("#status", Static).update(
77
+ "[bold red]Not passing yet[/bold red]"
78
+ )
79
+ self.query_one("#next-step", Static).update(
80
+ f"Timed out after {result.duration_s:.1f}s. "
81
+ "Check for an infinite loop."
82
+ )
83
+ return
84
+ if result.exit_code != 0:
85
+ self.add_class("failed")
86
+ self.query_one("#status", Static).update(
87
+ "[bold red]Not passing yet[/bold red]"
88
+ )
89
+ nudge = (
90
+ self._hint_nudge(exercise)
91
+ if failures
92
+ else "Hint available: press F1."
93
+ )
94
+ self.query_one("#next-step", Static).update(
95
+ f"Read the details, fix the code, then pause typing.\n{nudge}"
96
+ )
97
+ return
98
+ if exercise.is_pending():
99
+ self.add_class("pending")
100
+ self.query_one("#status", Static).update(
101
+ "[bold yellow]Checks pass, remove marker[/bold yellow]"
102
+ )
103
+ self.query_one("#next-step", Static).update(
104
+ "Remove the # I AM NOT DONE line to advance."
105
+ )
106
+ return
107
+ self.add_class("passed")
108
+ self.query_one("#status", Static).update(
109
+ f"[bold green]✓ {exercise.name} complete[/bold green]"
110
+ )
111
+ self.query_one("#next-step", Static).update(
112
+ "Loading the next exercise."
113
+ )
114
+
115
+ def show_final(self, message: str) -> None:
116
+ """Render the curriculum-complete screen."""
117
+ self.remove_class("failed", "pending")
118
+ self.add_class("passed")
119
+ self.query_one("#hint", Static).remove_class("visible")
120
+ self.query_one("#output-header", Static).update(
121
+ "[bold green]All exercises complete[/bold green]"
122
+ )
123
+ self.query_one("#goal", Static).update("")
124
+ self.query_one("#docs", Static).update("")
125
+ self.query_one("#status", Static).update(message)
126
+ self.query_one("#next-step", Static).update("")
127
+ self.query_one("#details", Static).update("")
128
+
129
+ def _render_header(
130
+ self, exercise: Exercise, completed: int = 0, total: int = 0
131
+ ) -> None:
132
+ header = self.query_one("#output-header", Static)
133
+ progress = f" [dim]{completed}/{total} complete[/dim]" if total else ""
134
+ header.update(
135
+ f"[bold]{exercise.name}[/bold]{progress} "
136
+ f"[dim]{self._display_path(exercise)}[/dim]\n"
137
+ f"{_INSTRUCTION}"
138
+ )
139
+
140
+ def _goal_from(self, exercise: Exercise) -> str:
141
+ for line in exercise.path.read_text(encoding="utf-8").splitlines()[:12]:
142
+ stripped = line.strip()
143
+ if stripped.startswith("# Goal:"):
144
+ return stripped.removeprefix("# Goal:").strip()
145
+ if stripped.startswith("# Exercise:"):
146
+ return stripped.removeprefix("# Exercise:").strip()
147
+ return exercise.name
148
+
149
+ def _render_docs(self, exercise: Exercise) -> None:
150
+ docs = exercise.docs.strip()
151
+ self.query_one("#docs", Static).update(
152
+ f"[bold]Docs[/bold]\n{docs}" if docs else ""
153
+ )
154
+
155
+ @staticmethod
156
+ def _hint_nudge(exercise: Exercise) -> str:
157
+ first = exercise.hint.strip().split(".")[0].strip()
158
+ return f"{first}." if first else "Hint available: press F1."
159
+
160
+ @staticmethod
161
+ def _display_path(exercise: Exercise) -> str:
162
+ # Prefer a short path relative to the working directory; fall back to
163
+ # the absolute path when that isn't possible (e.g. different drives).
164
+ try:
165
+ return os.path.relpath(exercise.path)
166
+ except ValueError:
167
+ return str(exercise.path)
168
+
169
+ def toggle_hint(self, text: str) -> None:
170
+ hint = self.query_one("#hint", Static)
171
+ if "visible" in hint.classes:
172
+ hint.remove_class("visible")
173
+ else:
174
+ hint.add_class("visible")
175
+ hint.update(f"[italic]Hint:[/italic] {text or '(no hint provided)'}")
176
+
177
+ def reset_hint(self) -> None:
178
+ hint = self.query_one("#hint", Static)
179
+ hint.remove_class("visible")
180
+ hint.update("")