Compare commits

...

5 Commits

Author SHA1 Message Date
catarrh 1b2fc595ce Allow saving unknown files with raw data
When a file has no custom encoder, the decode path returns {'raw': ...}
but the encode path raised NotImplementedError. Add a fallback in all
encoder methods to return the raw data directly when present in the
abstract_data dict, allowing unknown files to round-trip.
2026-07-04 11:48:53 +03:00
Vadim Yanitskiy 973d6eb2cc pySim.log: fix E0611: No name 'style' in module 'cmd2'
Change-Id: I191ea56f4c6e4e1916369f69fe2e1653e1d92df1
Fixes: 597f1e0 ("pySim.log, pySim-shell: fix compatibility with cmd2 >= 3.0.0")
2026-07-01 16:47:08 +07:00
Vadim Yanitskiy 597f1e0398 pySim.log, pySim-shell: fix compatibility with cmd2 >= 3.0.0
Some Linux distributions (e.g. Arch Linux) already ship cmd2 3.x.x,
which removed the style()/Fg/Bg API in favor of stylize()/Color.

Add a version guard to select the right API at runtime.
Adjust the upper bound cap in requirements.txt and setup.py.

Change-Id: Ibf2ac7847933296fb06665c87f53ed6e1f315d27
2026-06-26 02:47:45 +07:00
Vadim Yanitskiy 45d37ed959 pySim-shell: drop backwards compat quirks for cmd2 < 2.6.2
Remove version guards for cmd2 < 2.0.0 and < 2.3.0, the Cmd2Compat
and Settable2Compat wrapper classes, and the old fg/bg color API -
none of these are needed since both requirements.txt and setup.py
already mandate cmd2 >= 2.6.2.

Change-Id: Ifd1c484ab66d74323d10e946347daa637cf6f5d8
2026-06-25 22:51:03 +07:00
Harald Welte 757c7d048e setup.py: Align cmd2 minimum version with requirements.txt
As pointed out in the commit-log of Change-Id
I5186f242dbc1b770e3ab8cdca7f27d2a1029fff6 we had different minimum
versions for cmd2 in requirements.txt vs setup.py.  Let's align that.

Change-Id: I71cee0ec3ed2abec68ec567beaab13c868721dad
2026-06-25 21:43:52 +07:00
5 changed files with 54 additions and 52 deletions
+30 -48
View File
@@ -24,21 +24,21 @@ import traceback
import re import re
import cmd2 import cmd2
from packaging import version from packaging import version
from cmd2 import style
import logging import logging
from pySim.log import PySimLogger from pySim.log import PySimLogger
from osmocom.utils import auto_uint8 from osmocom.utils import auto_uint8
# cmd2 >= 2.3.0 has deprecated the bg/fg in favor of Bg/Fg :( # cmd2 >= 3.0 replaced Fg + style() with Color + stylize()
if version.parse(cmd2.__version__) < version.parse("2.3.0"): if version.parse(cmd2.__version__) >= version.parse("3.0.0"):
from cmd2 import fg, bg # pylint: disable=no-name-in-module from cmd2 import Color, stylize # pylint: disable=no-name-in-module
RED = fg.red RED = Color.RED
YELLOW = fg.yellow YELLOW = Color.YELLOW
LIGHT_RED = fg.bright_red LIGHT_RED = Color.BRIGHT_RED
LIGHT_GREEN = fg.bright_green LIGHT_GREEN = Color.BRIGHT_GREEN
def style(text, fg=None, bg=None, bold=False): # pylint: disable=function-redefined
return stylize(text, fg) if fg else text
else: else:
from cmd2 import Fg, Bg # pylint: disable=no-name-in-module from cmd2 import style, Fg # pylint: disable=no-name-in-module
RED = Fg.RED RED = Fg.RED
YELLOW = Fg.YELLOW YELLOW = Fg.YELLOW
LIGHT_RED = Fg.LIGHT_RED LIGHT_RED = Fg.LIGHT_RED
@@ -76,43 +76,19 @@ from pySim.app import init_card
log = PySimLogger.get(Path(__file__).stem) log = PySimLogger.get(Path(__file__).stem)
class Cmd2Compat(cmd2.Cmd): class PysimApp(cmd2.Cmd):
"""Backwards-compatibility wrapper around cmd2.Cmd to support older and newer
releases. See https://github.com/python-cmd2/cmd2/blob/master/CHANGELOG.md"""
def run_editor(self, file_path: Optional[str] = None) -> None:
if version.parse(cmd2.__version__) < version.parse("2.0.0"):
return self._run_editor(file_path) # pylint: disable=no-member
else:
return super().run_editor(file_path) # pylint: disable=no-member
class Settable2Compat(cmd2.Settable):
"""Backwards-compatibility wrapper around cmd2.Settable to support older and newer
releases. See https://github.com/python-cmd2/cmd2/blob/master/CHANGELOG.md"""
def __init__(self, name, val_type, description, settable_object, **kwargs):
if version.parse(cmd2.__version__) < version.parse("2.0.0"):
super().__init__(name, val_type, description, **kwargs) # pylint: disable=no-value-for-parameter
else:
super().__init__(name, val_type, description, settable_object, **kwargs) # pylint: disable=too-many-function-args
class PysimApp(Cmd2Compat):
CUSTOM_CATEGORY = 'pySim Commands' CUSTOM_CATEGORY = 'pySim Commands'
BANNER = """Welcome to pySim-shell! BANNER = """Welcome to pySim-shell!
(C) 2021-2023 by Harald Welte, sysmocom - s.f.m.c. GmbH and contributors (C) 2021-2023 by Harald Welte, sysmocom - s.f.m.c. GmbH and contributors
Online manual available at https://downloads.osmocom.org/docs/pysim/master/html/shell.html """ Online manual available at https://downloads.osmocom.org/docs/pysim/master/html/shell.html """
def __init__(self, verbose, card, rs, sl, ch, script=None): def __init__(self, verbose, card, rs, sl, ch, script=None):
if version.parse(cmd2.__version__) < version.parse("2.0.0"):
kwargs = {'use_ipython': True}
else:
kwargs = {'include_ipy': True}
self.verbose = verbose self.verbose = verbose
PySimLogger.setup(self.poutput, {logging.WARN: YELLOW}) PySimLogger.setup(self.poutput, {logging.WARN: YELLOW})
self._onchange_verbose('verbose', False, self.verbose) self._onchange_verbose('verbose', False, self.verbose)
# pylint: disable=unexpected-keyword-arg
super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False, super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
auto_load_commands=False, startup_script=script, **kwargs) auto_load_commands=False, startup_script=script, include_ipy=True)
self.intro = style(self.BANNER, fg=RED) self.intro = style(self.BANNER, fg=RED)
self.default_category = 'pySim-shell built-in commands' self.default_category = 'pySim-shell built-in commands'
self.card = None self.card = None
@@ -128,18 +104,24 @@ Online manual available at https://downloads.osmocom.org/docs/pysim/master/html/
self.apdu_trace = False self.apdu_trace = False
self.apdu_strict = False self.apdu_strict = False
self.add_settable(Settable2Compat('numeric_path', bool, 'Print File IDs instead of names', self, self.add_settable(cmd2.Settable('numeric_path', bool,
onchange_cb=self._onchange_numeric_path)) 'Print File IDs instead of names',
self.add_settable(Settable2Compat('conserve_write', bool, 'Read and compare before write', self, self, onchange_cb=self._onchange_numeric_path))
onchange_cb=self._onchange_conserve_write)) self.add_settable(cmd2.Settable('conserve_write', bool,
self.add_settable(Settable2Compat('json_pretty_print', bool, 'Pretty-Print JSON output', self)) 'Read and compare before write',
self.add_settable(Settable2Compat('apdu_trace', bool, 'Trace and display APDUs exchanged with card', self, self, onchange_cb=self._onchange_conserve_write))
onchange_cb=self._onchange_apdu_trace)) self.add_settable(cmd2.Settable('json_pretty_print', bool,
self.add_settable(Settable2Compat('apdu_strict', bool, 'Pretty-Print JSON output',
'Strictly apply APDU format according to ISO/IEC 7816-3, table 12', self)) self))
self.add_settable(Settable2Compat('verbose', bool, self.add_settable(cmd2.Settable('apdu_trace', bool,
'Enable/disable verbose logging', self, 'Trace and display APDUs exchanged with card',
onchange_cb=self._onchange_verbose)) self, onchange_cb=self._onchange_apdu_trace))
self.add_settable(cmd2.Settable('apdu_strict', bool,
'Strictly apply APDU format according to ISO/IEC 7816-3, table 12',
self))
self.add_settable(cmd2.Settable('verbose', bool,
'Enable/disable verbose logging',
self, onchange_cb=self._onchange_verbose))
self.equip(card, rs) self.equip(card, rs)
def equip(self, card, rs): def equip(self, card, rs):
+12
View File
@@ -863,6 +863,8 @@ class TransparentEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return t.to_tlv() return t.to_tlv()
if 'raw' in abstract_data:
return h2b(abstract_data['raw'])
raise NotImplementedError( raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self) "%s encoder not yet implemented. Patches welcome." % self)
@@ -892,6 +894,8 @@ class TransparentEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return b2h(t.to_tlv()) return b2h(t.to_tlv())
if 'raw' in abstract_data:
return abstract_data['raw']
raise NotImplementedError( raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self) "%s encoder not yet implemented. Patches welcome." % self)
@@ -1166,6 +1170,8 @@ class LinFixedEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return b2h(t.to_tlv()) return b2h(t.to_tlv())
if 'raw' in abstract_data:
return abstract_data['raw']
raise NotImplementedError( raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self) "%s encoder not yet implemented. Patches welcome." % self)
@@ -1195,6 +1201,8 @@ class LinFixedEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return t.to_tlv() return t.to_tlv()
if 'raw' in abstract_data:
return h2b(abstract_data['raw'])
raise NotImplementedError( raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self) "%s encoder not yet implemented. Patches welcome." % self)
@@ -1386,6 +1394,8 @@ class TransRecEF(TransparentEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return b2h(t.to_tlv()) return b2h(t.to_tlv())
if 'raw' in abstract_data:
return abstract_data['raw']
raise NotImplementedError( raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self) "%s encoder not yet implemented. Patches welcome." % self)
@@ -1415,6 +1425,8 @@ class TransRecEF(TransparentEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return t.to_tlv() return t.to_tlv()
if 'raw' in abstract_data:
return h2b(abstract_data['raw'])
raise NotImplementedError( raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self) "%s encoder not yet implemented. Patches welcome." % self)
+10 -2
View File
@@ -24,7 +24,15 @@
# #
import logging import logging
from cmd2 import style import cmd2
from packaging import version
if version.parse(cmd2.__version__) >= version.parse("3.0.0"):
from cmd2 import stylize as _stylize # pylint: disable=no-name-in-module
def _style(text, fg=None): # pylint: disable=function-redefined
return _stylize(text, fg) if fg else text
else: # cmd2>=2.6.2
from cmd2 import style as _style # pylint: disable=no-name-in-module
class _PySimLogHandler(logging.Handler): class _PySimLogHandler(logging.Handler):
def __init__(self, log_callback): def __init__(self, log_callback):
@@ -121,7 +129,7 @@ class PySimLogger:
if isinstance(color, str): if isinstance(color, str):
PySimLogger.print_callback(color + formatted_message + "\033[0m") PySimLogger.print_callback(color + formatted_message + "\033[0m")
else: else:
PySimLogger.print_callback(style(formatted_message, fg = color)) PySimLogger.print_callback(_style(formatted_message, fg = color))
else: else:
PySimLogger.print_callback(formatted_message) PySimLogger.print_callback(formatted_message)
+1 -1
View File
@@ -1,7 +1,7 @@
pyscard pyscard
pyserial pyserial
pytlv pytlv
cmd2>=2.6.2,<3.0 cmd2>=2.6.2,<4.0
jsonpath-ng jsonpath-ng
construct>=2.10.70 construct>=2.10.70
bidict bidict
+1 -1
View File
@@ -21,7 +21,7 @@ setup(
"pyscard", "pyscard",
"pyserial", "pyserial",
"pytlv", "pytlv",
"cmd2 >= 1.5.0, < 3.0", "cmd2 >= 2.6.2, < 4.0",
"jsonpath-ng", "jsonpath-ng",
"construct >= 2.10.70", "construct >= 2.10.70",
"bidict", "bidict",