Skip to content

RFC - Restore Subservice export for compatibility#141

Draft
mokagio wants to merge 2 commits into
us-irs:mainfrom
mokagio:enum-export-compat
Draft

RFC - Restore Subservice export for compatibility#141
mokagio wants to merge 2 commits into
us-irs:mainfrom
mokagio:enum-export-compat

Conversation

@mokagio
Copy link
Copy Markdown
Contributor

@mokagio mokagio commented Mar 15, 2026

I tried to update one of the consuming projects, https://github.com/robamu-org/tmtccmd, and run into test failures of the kind ImportError: cannot import name 'Subservice' from 'spacepackets.ecss.pus_5_event'

Full stacktrace
➜ uv pip install -e ".[test]" && uv run pytest
    Updated https://github.com/us-irs/py-spacepackets.git (abba1784
Resolved 24 packages in 1.99s
      Built tmtccmd @ file:///Users/gio/Developer/oss/space-and-ene
      Built spacepackets @ git+https://github.com/us-irs/py-spacepa
Prepared 2 packages in 719ms
Uninstalled 2 packages in 15ms
Installed 2 packages in 2ms
 - spacepackets==0.31.0
 + spacepackets==0.31.0 (from git+https://github.com/us-irs/py-spacepackets.git@abba178451e39974ad96d0a4478a554365a14e05)
 ~ tmtccmd==9.0.0 (from file:///Users/gio/Developer/oss/space-and-energy/tmtccmd)
    Updated https://github.com/us-irs/py-spacepackets.git (abba1784
===================== test session starts =====================
platform darwin -- Python 3.14.0, pytest-9.0.2, pluggy-1.6.0
rootdir: /Users/gio/Developer/oss/space-and-energy/tmtccmd
configfile: pyproject.toml
plugins: pyfakefs-5.10.2
collected 107 items / 7 errors

=========================== ERRORS ============================
________ ERROR collecting src/tmtccmd/pus/s17_test.py _________
ImportError while importing test module '/Users/gio/Developer/oss/space-and-energy/tmtccmd/src/tmtccmd/pus/s17_test.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/opt/homebrew/Cellar/python@3.14/3.14.0_1/Frameworks/Python.framework/Versions/3.14/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/tmtccmd/pus/s17_test.py:2: in <module>
    from .tc.s17_test import *  # noqa re-export
    ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/tmtccmd/pus/tc/s17_test.py:3: in <module>
    from spacepackets.ecss.pus_17_test import Subservice
E   ImportError: cannot import name 'Subservice' from 'spacepackets.ecss.pus_17_test' (/Users/gio/Developer/oss/space-and-energy/tmtccmd/.venv/lib/python3.14/site-packages/spacepackets/ecss/pus_17_test.py)
_______ ERROR collecting src/tmtccmd/pus/tc/s17_test.py _______
ImportError while importing test module '/Users/gio/Developer/oss/space-and-energy/tmtccmd/src/tmtccmd/pus/tc/s17_test.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/opt/homebrew/Cellar/python@3.14/3.14.0_1/Frameworks/Python.framework/Versions/3.14/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/tmtccmd/pus/tc/s17_test.py:3: in <module>
    from spacepackets.ecss.pus_17_test import Subservice
E   ImportError: cannot import name 'Subservice' from 'spacepackets.ecss.pus_17_test' (/Users/gio/Developer/oss/space-and-energy/tmtccmd/.venv/lib/python3.14/site-packages/spacepackets/ecss/pus_17_test.py)
__________ ERROR collecting tests/com/test_dummy.py ___________
ImportError while importing test module '/Users/gio/Developer/oss/space-and-energy/tmtccmd/tests/com/test_dummy.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/opt/homebrew/Cellar/python@3.14/3.14.0_1/Frameworks/Python.framework/Versions/3.14/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/com/test_dummy.py:5: in <module>
    from tmtccmd.com.dummy import DummyInterface
src/tmtccmd/com/dummy.py:21: in <module>
    from tmtccmd.pus.s17_test import Subservice as Pus17Subservice
src/tmtccmd/pus/s17_test.py:2: in <module>
    from .tc.s17_test import *  # noqa re-export
    ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/tmtccmd/pus/tc/s17_test.py:3: in <module>
    from spacepackets.ecss.pus_17_test import Subservice
E   ImportError: cannot import name 'Subservice' from 'spacepackets.ecss.pus_17_test' (/Users/gio/Developer/oss/space-and-energy/tmtccmd/.venv/lib/python3.14/site-packages/spacepackets/ecss/pus_17_test.py)
___________ ERROR collecting tests/test_backend.py ____________
ImportError while importing test module '/Users/gio/Developer/oss/space-and-energy/tmtccmd/tests/test_backend.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/opt/homebrew/Cellar/python@3.14/3.14.0_1/Frameworks/Python.framework/Versions/3.14/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_backend.py:8: in <module>
    from tmtccmd.com.dummy import DummyInterface
src/tmtccmd/com/dummy.py:21: in <module>
    from tmtccmd.pus.s17_test import Subservice as Pus17Subservice
src/tmtccmd/pus/s17_test.py:2: in <module>
    from .tc.s17_test import *  # noqa re-export
    ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/tmtccmd/pus/tc/s17_test.py:3: in <module>
    from spacepackets.ecss.pus_17_test import Subservice
E   ImportError: cannot import name 'Subservice' from 'spacepackets.ecss.pus_17_test' (/Users/gio/Developer/oss/space-and-energy/tmtccmd/.venv/lib/python3.14/site-packages/spacepackets/ecss/pus_17_test.py)
___________ ERROR collecting tests/test_printer.py ____________
ImportError while importing test module '/Users/gio/Developer/oss/space-and-energy/tmtccmd/tests/test_printer.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/opt/homebrew/Cellar/python@3.14/3.14.0_1/Frameworks/Python.framework/Versions/3.14/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_printer.py:18: in <module>
    from tmtccmd.pus.s17_test import create_service_17_ping_command
src/tmtccmd/pus/s17_test.py:2: in <module>
    from .tc.s17_test import *  # noqa re-export
    ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/tmtccmd/pus/tc/s17_test.py:3: in <module>
    from spacepackets.ecss.pus_17_test import Subservice
E   ImportError: cannot import name 'Subservice' from 'spacepackets.ecss.pus_17_test' (/Users/gio/Developer/oss/space-and-energy/tmtccmd/.venv/lib/python3.14/site-packages/spacepackets/ecss/pus_17_test.py)
_________ ERROR collecting tests/tm/test_srv3_fsfw.py _________
ImportError while importing test module '/Users/gio/Developer/oss/space-and-energy/tmtccmd/tests/tm/test_srv3_fsfw.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/opt/homebrew/Cellar/python@3.14/3.14.0_1/Frameworks/Python.framework/Versions/3.14/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/tm/test_srv3_fsfw.py:7: in <module>
    from tmtccmd.pus.s3_fsfw_hk import Subservice
src/tmtccmd/pus/s3_fsfw_hk.py:1: in <module>
    from .tc.s3_fsfw_hk import *  # noqa re-export
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/tmtccmd/pus/tc/s3_fsfw_hk.py:6: in <module>
    from spacepackets.ecss.pus_3_hk import Subservice
E   ImportError: cannot import name 'Subservice' from 'spacepackets.ecss.pus_3_hk' (/Users/gio/Developer/oss/space-and-energy/tmtccmd/.venv/lib/python3.14/site-packages/spacepackets/ecss/pus_3_hk.py)
___________ ERROR collecting tests/tm/test_srv5.py ____________
ImportError while importing test module '/Users/gio/Developer/oss/space-and-energy/tmtccmd/tests/tm/test_srv5.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/opt/homebrew/Cellar/python@3.14/3.14.0_1/Frameworks/Python.framework/Versions/3.14/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/tm/test_srv5.py:4: in <module>
    from tmtccmd.pus.s5_fsfw_event import EventDefinition, Service5Tm, Subservice
src/tmtccmd/pus/s5_fsfw_event.py:4: in <module>
    from .tm.s5_fsfw_event import *  # noqa re-export
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/tmtccmd/pus/tm/s5_fsfw_event.py:11: in <module>
    from spacepackets.ecss.pus_5_event import Subservice
E   ImportError: cannot import name 'Subservice' from 'spacepackets.ecss.pus_5_event' (/Users/gio/Developer/oss/space-and-energy/tmtccmd/.venv/lib/python3.14/site-packages/spacepackets/ecss/pus_5_event.py)
=================== short test summary info ===================
ERROR src/tmtccmd/pus/s17_test.py
ERROR src/tmtccmd/pus/tc/s17_test.py
ERROR tests/com/test_dummy.py
ERROR tests/test_backend.py
ERROR tests/test_printer.py
ERROR tests/tm/test_srv3_fsfw.py
ERROR tests/tm/test_srv5.py
!!!!!!!!!!! Interrupted: 7 errors during collection !!!!!!!!!!!
====================== 7 errors in 0.39s ======================

The reason, as far as I understand, is that with #135 replacing subservice with message_subtype, the Subservice export has been removed.

This opens up an interesting question: Should the PUS-C change be a breaking change or a deprecation?

Given the library is not in a stable version yet, according to SemVer, breaking changes are allowed. Still, I tried to implement #135 as a deprecation only. Evidently, I didn't do a enough of a good job.

If deprecation is the way to move, then this PR restores support for Subservice. There's more work to do, though, because pointing to these changes still has test failures on the constructors.

Details
========================== FAILURES ===========================
___________________ TestDummy.test_dummy_if ___________________

self = <tests.com.test_dummy.TestDummy testMethod=test_dummy_if>

    def test_dummy_if(self):
        dummy_com_if = DummyInterface()
        self.assertFalse(dummy_com_if.is_open())
        dummy_com_if.open()
        self.assertTrue(dummy_com_if.is_open())
        self.assertFalse(dummy_com_if.initialized)
        dummy_com_if.initialize()
        self.assertTrue(dummy_com_if.initialized)
        self.assertEqual(dummy_com_if.packets_available(), 0)
>       dummy_com_if.send(PusTelecommand(apid=0x02, service=17, subservice=1).pack())

tests/com/test_dummy.py:18:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
src/tmtccmd/com/dummy.py:149: in send
    self.dummy_handler.insert_telecommand(data)
src/tmtccmd/com/dummy.py:35: in insert_telecommand
    self.generate_reply_package()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <tmtccmd.com.dummy.DummyHandler object at 0x10be581a0>

    def generate_reply_package(self):
        """Generate a reply package. Currently, this only generates a reply for a ping
        telecommand."""
        assert self.last_tc is not None
        if self.last_tc.service == 17 and self.last_tc.subservice == 1:
            current_time_stamp = CdsShortTimestamp.now()
            tm_packer = Service1Tm(
                subservice=Pus1Subservice.TM_ACCEPTANCE_SUCCESS,
                apid=self.last_tc.apid,
                seq_count=self.current_ssc,
                verif_params=VerificationParams(
                    req_id=RequestId(self.last_tc.packet_id, self.last_tc.packet_seq_control)
                ),
                timestamp=current_time_stamp.pack(),
            )

            self.current_ssc += 1
            tm_packet_raw = tm_packer.pack()
            self.next_telemetry_package.append(tm_packet_raw)
            tm_packer = Service1Tm(
                subservice=Pus1Subservice.TM_START_SUCCESS,
                apid=self.last_tc.apid,
                seq_count=self.current_ssc,
                verif_params=VerificationParams(
                    req_id=RequestId(self.last_tc.packet_id, self.last_tc.packet_seq_control)
                ),
                timestamp=current_time_stamp.pack(),
            )
            tm_packet_raw = tm_packer.pack()
            self.next_telemetry_package.append(tm_packet_raw)
            self.current_ssc += 1

>           tm_packer = Service17Tm(
                subservice=Pus17Subservice.TM_REPLY,
                apid=self.last_tc.apid,
                timestamp=current_time_stamp.pack(),
            )
E           TypeError: Service17Tm.__init__() got an unexpected keyword argument 'subservice'

src/tmtccmd/com/dummy.py:69: TypeError
______________ TestTelemetry.test_generic_pus_c _______________

self = <tests.tm.test_srv17.TestTelemetry testMethod=test_generic_pus_c>

    def setUp(self) -> None:
        self.apid = 0xEF
>       self.pus_17_telemetry = Service17Tm(
            apid=self.apid,
            subservice=1,
            ssc=36,
            timestamp=CdsShortTimestamp.now().pack(),
        )
E       TypeError: Service17Tm.__init__() got an unexpected keyword argument 'subservice'

tests/tm/test_srv17.py:11: TypeError

I'm not sure what the best way forward is in relation to the library design and its consumers. Keen to help either way, be that via a deprecation with support for subservice or a breaking change that keeps only message_subtype and require source changes in the consumers.

mokagio and others added 2 commits March 15, 2026 20:24
Add focused reproducers for downstream imports that still expect service-module Subservice enum aliases.
The new tests cover services 17, 3, and 5, matching the import errors seen in tmtccmd after the PUS-C rename.
These tests currently fail against main and document another compatibility gap beyond the packet and header behavior changes.

---

Generated with the help of Codex, GPT-5

Co-Authored-By: Codex GPT-5 <codex@openai.com>
Keep legacy service-module enum imports working during the PUS-C rename transition.
This restores  aliases in the affected modules so downstream consumers like tmtccmd can continue importing the old names while moving to the new terminology.

---

Generated with the help of Codex, GPT-5

Co-Authored-By: Codex GPT-5 <codex@openai.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.85%. Comparing base (abba178) to head (3924a9f).
⚠️ Report is 7 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #141   +/-   ##
=======================================
  Coverage   99.85%   99.85%           
=======================================
  Files          43       44    +1     
  Lines        3534     3543    +9     
=======================================
+ Hits         3529     3538    +9     
  Misses          5        5           
Flag Coverage Δ
unittests 99.85% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mokagio
Copy link
Copy Markdown
Contributor Author

mokagio commented Mar 27, 2026

@robamu keen to hear what you think: fully support older syntax or require consumers to update usages when using the newer version of the package?

@robamu
Copy link
Copy Markdown
Contributor

robamu commented Mar 30, 2026

Hmm, I am fine with a breaking change, if backwards compatibility would be a huge pain. The subservice scheme is an old PUS A legacy thing after all. Can't really guess how many library users there are that might be annoyed by this, we are actually moving away from PUS.

@mokagio
Copy link
Copy Markdown
Contributor Author

mokagio commented Mar 30, 2026

we are actually moving away from PUS.

Where can I read about this as someone outside the industry who wants to learn more? Thanks!

@robamu
Copy link
Copy Markdown
Contributor

robamu commented Mar 31, 2026

Hmm, this is all new and not really written down somewhere. We are transitioning to CCSDS spacepackets + custom application specific protocol. In our case, we are using Rust + the serde framework to directly model all of our required services with Rust data types, but something like using protobuf to remain language agnostic would also be an option.

PUS is not designed for running component oriented / object oriented software, and it keeps showing. We tailored the standard so much that we pretty much do not use it anymore in its standard form anyway.

@mokagio
Copy link
Copy Markdown
Contributor Author

mokagio commented Apr 10, 2026

We tailored the standard so much that we pretty much do not use it anymore in its standard form anyway.

I see, I see... well maybe then this repo can stay as it is and wait for signal from consumers to discover if something is not right.

In our case, we are using Rust + the serde framework to directly model all of our required services with Rust data types

Is it this? https://github.com/us-irs/spacepackets-rs

@robamu
Copy link
Copy Markdown
Contributor

robamu commented Apr 10, 2026

Is it this? https://github.com/us-irs/spacepackets-rs

that is thethe rust equivalent of this library. Using serde instead of PUS is an application layer solution. i will probably write all of this up at some point.

@mokagio
Copy link
Copy Markdown
Contributor Author

mokagio commented Apr 12, 2026

Using serde instead of PUS is an application layer solution.

Oh, I see. Closer to metal, in a sense. Roll out just enough serialization/deserialization as necessary, without implementing the whole protocol.

i will probably write all of this up at some point.

That's be cool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants