Skip to content

Commit 0c0ca13

Browse files
authored
Merge pull request #177 from skogsbaer/sw/fixes-for-2.0.1
fixes for 2.0.*
2 parents bebb25b + 8f55295 commit 0c0ca13

25 files changed

+142
-51
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
},
99
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
1010
"typescript.tsc.autoDetect": "off"
11-
}
11+
}

ChangeLog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Write Your Python Program - CHANGELOG
22

3+
* 2.0.3 (2025-09-24)
4+
* More fixes
5+
* 2.0.2 (2025-09-24)
6+
* More fixes
7+
* 2.0.1 (2025-09-24)
8+
* Minor fixes
39
* 2.0.0 (2025-09-24)
410
* Remove wrappers, only check types at function enter/exit points
511
* Restructure directory layout

README.md

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,14 @@ After installation, you can use the `wypp` command
3333
for running your python files, making all features explained below available.
3434
Run `wypp --help` for usage information.
3535

36-
## What's new?
37-
38-
Here is the [Changelog](ChangeLog.md).
39-
40-
* **Breaking change** in version 2.0.0 (2025-0-24): type annotations are now only
41-
checked when entering/exiting a function. Before, certain things such as lists
42-
or callable were put behind wrapper objects. For example, these wrappers ensured
43-
that only ints could be appended to a list of type `list[int]`. However, these
44-
wrappers came with several drawbacks, so they were removed in release 2.0.0
45-
* **Breaking change** in version 0.12.0 (2021-09-28): type annotations are now checked
46-
dynamically when the code is executed.
47-
This behavior can be deactivated in the settings of the extension.
48-
* **Breaking change** in version 0.11.0 (2021-03-11): wypp is no longer automatically imported.
49-
You need an explicit import statement such as `from wypp import *`.
50-
5136
## Features
5237

5338
Here is a screen shot:
5439

55-
![Screenshot](screenshot.png)
40+
![Screenshot](screenshot.jpg)
5641

5742
There is also a visualization mode, similar to [Python Tutor](https://pythontutor.com/):
58-
![Screenshot](screenshot2.png)
43+
![Screenshot](screenshot2.jpg)
5944

6045
When hitting the RUN button, the vscode extension saves the current file, opens
6146
a terminal and executes the file with Python, staying in interactive mode after
@@ -198,13 +183,27 @@ before the type being defined, for example to define recursive types or as
198183
the type of `self` inside of classes. In fact, there is no check at all to make sure
199184
that anotations refer to existing types.
200185

201-
202186
## Module name and current working directory
203187

204188
When executing a python file with the RUN button, the current working directory is set to
205189
the directory of the file being executed. The `__name__` attribute is set to the value
206190
`'__wypp__'`.
207191

192+
## What's new?
193+
194+
Here is the [Changelog](ChangeLog.md).
195+
196+
* **Breaking change** in version 2.0.0 (2025-09-24): type annotations are now only
197+
checked when entering/exiting a function. Before, certain things such as lists
198+
or callable were put behind wrapper objects. For example, these wrappers ensured
199+
that only ints could be appended to a list of type `list[int]`. However, these
200+
wrappers came with several drawbacks, so they were removed in release 2.0.0
201+
* **Breaking change** in version 0.12.0 (2021-09-28): type annotations are now checked
202+
dynamically when the code is executed.
203+
This behavior can be deactivated in the settings of the extension.
204+
* **Breaking change** in version 0.11.0 (2021-03-11): wypp is no longer automatically imported.
205+
You need an explicit import statement such as `from wypp import *`.
206+
208207
## Bugs & Problems
209208

210209
Please report them in the [issue tracker](https://github.com/skogsbaer/write-your-python-program/issues).

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Write Your Python Program!",
44
"description": "A user friendly python environment for beginners",
55
"license": "See license in LICENSE",
6-
"version": "2.0.0",
6+
"version": "2.0.3",
77
"publisher": "StefanWehr",
88
"icon": "icon.png",
99
"engines": {

python/code/wypp/i18n.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,25 @@ def tr(key: str, **kws) -> str:
140140
'Constructor of record `{cls}` does not accept keyword argument `{name}`.':
141141
'Konstruktor des Records `{cls}` akzeptiert kein Schlüsselwort-Argument `{name}`.',
142142

143-
'invalid record definition': 'ungültige Record-Definition'
143+
'invalid record definition': 'ungültige Record-Definition',
144+
145+
'Expected {expected}, but the result is {actual}':
146+
'Erwartet wird {expected}, aber das Ergebnis ist {actual}',
147+
'ERROR: ': 'FEHLER: ',
148+
'ERROR in ': 'FEHLER in ',
149+
'File {filename}, line {lineno}: ': 'Datei {filename}, Zeile {lineno}: ',
150+
'Uncovered case': 'Ein Fall ist nicht abgedeckt',
151+
'uncovered case': 'ein Fall ist nicht abgedeckt',
152+
'The impossible happened!': 'Das Unmögliche ist passiert!',
153+
'Stop of execution': 'Abbruch der Ausführung',
154+
'1 successful test': '1 erfolgreicher Test',
155+
'all succesful': 'alle erfolgreich',
156+
'and stop of execution': 'und Abbruch der Ausführung',
157+
'all successful': 'alle erfolgreich',
158+
159+
'NOTE: running the code failed, some definitions might not be available in the interactive window!':
160+
'ACHTUNG: der Code enthält Fehler, einige Definition sind möglicherweise in interaktiven Fenster nicht verfügbar!'
161+
144162
}
145163

146164
def expectingNoReturn(cn: location.CallableName) -> str:
@@ -346,3 +364,40 @@ def unknownKeywordArgument(cn: location.CallableName, name: str) -> str:
346364
return tr('Constructor of record `{cls}` does not accept keyword argument `{name}`.',
347365
cls=cls, name=name)
348366
raise ValueError(f'Unexpected: {cn}')
367+
368+
def checkExpected(expected: str, actual: str) -> str:
369+
return tr('Expected {expected}, but the result is {actual}', expected=expected,actual=actual)
370+
371+
def numTests(n: int) -> str:
372+
match getLang():
373+
case 'en':
374+
if n == 0:
375+
return 'no tests'
376+
elif n == 1:
377+
return '1 test'
378+
else:
379+
return f'{n} tests'
380+
case 'de':
381+
if n == 0:
382+
return 'keine Tests'
383+
elif n == 1:
384+
return '1 Test'
385+
else:
386+
return f'{n} Tests'
387+
388+
def numFailing(n: int) -> str:
389+
match getLang():
390+
case 'en':
391+
if n == 0:
392+
return 'no errors'
393+
elif n == 1:
394+
return '1 error'
395+
else:
396+
return f'{n} errors'
397+
case 'de':
398+
if n == 0:
399+
return 'keine Fehler'
400+
elif n == 1:
401+
return '1 Fehler'
402+
else:
403+
return f'{n} Fehler'

python/code/wypp/interactive.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from constants import *
99
from myLogging import *
1010
from exceptionHandler import handleCurrentException
11+
import i18n
1112

1213
def prepareInteractive(reset=True):
1314
print()
@@ -41,7 +42,7 @@ def enterInteractive(userDefs: dict, checkTypes: bool, loadingFailed: bool):
4142
globals()[k] = v
4243
print()
4344
if loadingFailed:
44-
print('NOTE: running the code failed, some definitions might not be available!')
45+
print(i18n.tr('NOTE: running the code failed, some definitions might not be available in the interactive window!'))
4546
print()
4647
if checkTypes:
4748
consoleClass = TypecheckedInteractiveConsole

python/code/wypp/lang.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
def _langFromEnv(env: MutableMapping) -> str | None:
66
# 1) GNU LANGUAGE: colon-separated fallbacks (e.g., "de:en_US:en")
7-
os.getenv
87
lng = env.get("LANGUAGE")
98
if lng:
109
for part in lng.split(":"):
@@ -35,9 +34,11 @@ def _normLang(tag: str) -> str:
3534

3635
def pickLanguage[T: str](supported: list[T], default: T) -> T:
3736
"""Return best match like 'de' or 'de_DE' from supported codes."""
38-
raw = _langFromEnv(os.environ)
37+
(raw, _) = locale.getlocale()
3938
if not raw:
40-
return default
39+
raw = _langFromEnv(os.environ)
40+
if not raw:
41+
return default
4142
want = _normLang(raw)
4243
# exact match first
4344
for s in supported:

python/code/wypp/writeYourProgram.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import location
1010
import paths
1111
import utils
12+
import i18n
1213

1314
_DEBUG = False
1415
def _debug(s):
@@ -147,23 +148,21 @@ def printTestResults(prefix='', loadingFailed=False):
147148
failing = _testCount['failing']
148149
bad = '🙁'
149150
good = '😀'
150-
tests = f'{prefix}{total} Tests'
151-
if total == 1:
152-
tests = f'{prefix}{total} Test'
151+
tests = f'{prefix}' + i18n.numTests(total)
153152
if total == 0:
154153
pass
155154
elif failing == 0:
156155
if loadingFailed:
157-
print(f'{tests}, Abbruch der Ausführung {bad}')
156+
print(f'{tests}, {i18n.tr("Stop of execution")} {bad}')
158157
elif total == 1:
159-
print(f'1 erfolgreicher Test {good}')
158+
print(f'{i18n.tr("1 successful test")} {good}')
160159
else:
161-
print(f'{tests}, alle erfolgreich {good}')
160+
print(f'{tests}, {i18n.tr("all successful")} {good}')
162161
else:
163162
if loadingFailed:
164-
print(f'{tests}, {failing} Fehler und Abbruch der Ausführung {bad}')
163+
print(f'{tests}, {i18n.numFailing(failing)} and stop of execution {bad}')
165164
else:
166-
print(f'{tests}, {failing} Fehler {bad}')
165+
print(f'{tests}, {i18n.numFailing(failing)} {bad}')
167166
return {'total': total, 'failing': failing}
168167

169168
def checkEq(actual, expected):
@@ -197,20 +196,20 @@ def checkGeneric(actual, expected, *, structuralObjEq=True, floatEqWithDelta=Tru
197196
frame = stack[2] if len(stack) > 2 else None
198197
if frame:
199198
filename = paths.canonicalizePath(frame.filename)
200-
caller = f"Datei {filename}, Zeile {frame.lineno}: "
199+
caller = i18n.tr('File {filename}, line {lineno}: ',
200+
filename=filename, lineno=frame.lineno)
201201
else:
202202
caller = ""
203203
def fmt(x):
204204
if type(x) == str:
205205
return repr(x)
206206
else:
207207
return str(x)
208-
msg = f"{caller}Erwartet wird {fmt(expected)}, aber das " \
209-
f"Ergebnis ist {fmt(actual)}"
208+
msg = caller + i18n.checkExpected(fmt(expected), fmt(actual))
210209
if _dieOnCheckFailures():
211210
raise Exception(msg)
212211
else:
213-
print("FEHLER in " + msg)
212+
print(i18n.tr('ERROR in ') + msg)
214213

215214
def checkFail(msg: str):
216215
if not _checksEnabled:
@@ -220,15 +219,17 @@ def checkFail(msg: str):
220219
if _dieOnCheckFailures():
221220
raise Exception(msg)
222221
else:
223-
print("FEHLER: " + msg)
222+
print(i18n.tr('ERROR: ') + msg)
224223

225224
def uncoveredCase():
226225
stack = inspect.stack()
227226
if len(stack) > 1:
228227
caller = stack[1]
229-
raise Exception(f"{caller.filename}, Zeile {caller.lineno}: ein Fall ist nicht abgedeckt")
228+
callerStr = i18n.tr('File {filename}, line {lineno}: ',
229+
filename=caller.filename, lineno=caller.lineno)
230+
raise Exception(callerStr + i18n.tr('uncovered case'))
230231
else:
231-
raise Exception(f"Ein Fall ist nicht abgedeckt")
232+
raise Exception(i18n.tr('Uncovered case'))
232233

233234
#
234235
# Deep equality
@@ -328,7 +329,8 @@ def todo(msg=None):
328329

329330
def impossible(msg=None):
330331
if msg is None:
331-
msg = 'Das Unmögliche ist passiert!'
332+
msg = i18n.tr('The impossible happened!')
333+
'Das Unmögliche ist passiert!'
332334
raise errors.ImpossibleError(msg)
333335

334336
# Additional functions and aliases

python/file-test-data/basics/check2_ok.err

Whitespace-only changes.

python/file-test-data/basics/check2_ok.err_en

Whitespace-only changes.

0 commit comments

Comments
 (0)