pymergetic-common 0.0.1__tar.gz

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 (95) hide show
  1. pymergetic_common-0.0.1/.github/workflows/publish.yml +39 -0
  2. pymergetic_common-0.0.1/.gitignore +207 -0
  3. pymergetic_common-0.0.1/.old/examples/pydataobject_roundtrip.py +36 -0
  4. pymergetic_common-0.0.1/.old/examples/pyobject_pydantic.py +29 -0
  5. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/__cpp__.cpp +3 -0
  6. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/base/__init__.cpp +9 -0
  7. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/base/__init__.hpp +6 -0
  8. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/base/base.hpp +14 -0
  9. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/base/defs.hpp +14 -0
  10. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/codec/__init__.cpp +9 -0
  11. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/codec/__init__.hpp +163 -0
  12. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/exceptions/__init__.cpp +17 -0
  13. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/exceptions/__init__.hpp +28 -0
  14. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/header/__init__.cpp +9 -0
  15. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/header/__init__.hpp +5 -0
  16. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/nb/__init__.cpp +9 -0
  17. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/nb/__init__.hpp +7 -0
  18. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/nb/asyncio_bridge.hpp +32 -0
  19. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/nb/base.hpp +26 -0
  20. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/nb/data.hpp +34 -0
  21. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/net/__init__.cpp +328 -0
  22. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/net/__init__.hpp +10 -0
  23. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/net/acceptor.hpp +93 -0
  24. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/net/peer_info.hpp +63 -0
  25. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/net/pmdg_channel.hpp +109 -0
  26. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/net/stream.hpp +28 -0
  27. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/net/tcp.hpp +112 -0
  28. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/net/uds.hpp +119 -0
  29. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/objects/__init__.cpp +9 -0
  30. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/objects/__init__.hpp +5 -0
  31. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/objects/data.hpp +27 -0
  32. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/runtime/__init__.cpp +9 -0
  33. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/runtime/__init__.hpp +5 -0
  34. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/runtime/asio_runtime.hpp +60 -0
  35. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/session/__init__.cpp +41 -0
  36. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/session/__init__.hpp +4 -0
  37. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/session/session.hpp +78 -0
  38. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/test/__init__.cpp +346 -0
  39. pymergetic_common-0.0.1/.old/src_cpp/pymergetic/common/test/__init__.hpp +5 -0
  40. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/__init__.py +11 -0
  41. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/__project__.py +19 -0
  42. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/base/__init__.py +0 -0
  43. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/codec/__init__.py +74 -0
  44. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/exceptions/__init__.py +29 -0
  45. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/header/__init__.py +20 -0
  46. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/header/decorators.py +17 -0
  47. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/header/exceptions.py +42 -0
  48. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/header/resolve.py +66 -0
  49. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/header/types.py +127 -0
  50. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/net/__init__.py +46 -0
  51. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/net/peer_info.py +21 -0
  52. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/objects/__init__.py +6 -0
  53. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/objects/pydataobject.py +145 -0
  54. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/objects/pyobject.py +77 -0
  55. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/runtime/__init__.py +0 -0
  56. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/session/__init__.py +58 -0
  57. pymergetic_common-0.0.1/.old/src_py/pymergetic/common/test/__init__.py +3 -0
  58. pymergetic_common-0.0.1/.old/tests/fixtures/hdrpkg/__header__.py +13 -0
  59. pymergetic_common-0.0.1/.old/tests/fixtures/hdrpkg/__impl__.py +8 -0
  60. pymergetic_common-0.0.1/.old/tests/fixtures/hdrpkg/__init__.py +5 -0
  61. pymergetic_common-0.0.1/.old/tests/test_asyncio_bridge.py +31 -0
  62. pymergetic_common-0.0.1/.old/tests/test_codec_python_mirror.py +19 -0
  63. pymergetic_common-0.0.1/.old/tests/test_cpp_extension.py +50 -0
  64. pymergetic_common-0.0.1/.old/tests/test_header_resolution.py +20 -0
  65. pymergetic_common-0.0.1/.old/tests/test_net_pmdg_channel.py +56 -0
  66. pymergetic_common-0.0.1/.old/tests/test_pydataobject.py +84 -0
  67. pymergetic_common-0.0.1/.old/tests/test_pyobject.py +53 -0
  68. pymergetic_common-0.0.1/.old/tests/test_session_policy.py +81 -0
  69. pymergetic_common-0.0.1/PKG-INFO +127 -0
  70. pymergetic_common-0.0.1/README.md +75 -0
  71. pymergetic_common-0.0.1/RELEASING.md +93 -0
  72. pymergetic_common-0.0.1/pyproject.toml +98 -0
  73. pymergetic_common-0.0.1/pytest.ini +5 -0
  74. pymergetic_common-0.0.1/setup.cfg +4 -0
  75. pymergetic_common-0.0.1/src_py/pymergetic/__init__.py +3 -0
  76. pymergetic_common-0.0.1/src_py/pymergetic/common/__init__.py +5 -0
  77. pymergetic_common-0.0.1/src_py/pymergetic/common/__project__.py +18 -0
  78. pymergetic_common-0.0.1/src_py/pymergetic/common/devtools/__init__.py +75 -0
  79. pymergetic_common-0.0.1/src_py/pymergetic/common/devtools/pin_pyproject.py +511 -0
  80. pymergetic_common-0.0.1/src_py/pymergetic/common/devtools/project_paths.py +102 -0
  81. pymergetic_common-0.0.1/src_py/pymergetic/common/devtools/release_helpers.py +154 -0
  82. pymergetic_common-0.0.1/src_py/pymergetic/common/devtools/release_tag.py +152 -0
  83. pymergetic_common-0.0.1/src_py/pymergetic/common/devtools/wait_pypi.py +85 -0
  84. pymergetic_common-0.0.1/src_py/pymergetic/common/header/__init__.py +20 -0
  85. pymergetic_common-0.0.1/src_py/pymergetic/common/header/decorators.py +17 -0
  86. pymergetic_common-0.0.1/src_py/pymergetic/common/header/exceptions.py +42 -0
  87. pymergetic_common-0.0.1/src_py/pymergetic/common/header/resolve.py +66 -0
  88. pymergetic_common-0.0.1/src_py/pymergetic/common/header/types.py +127 -0
  89. pymergetic_common-0.0.1/src_py/pymergetic/common/sysinfo/__init__.py +21 -0
  90. pymergetic_common-0.0.1/src_py/pymergetic_common.egg-info/PKG-INFO +127 -0
  91. pymergetic_common-0.0.1/src_py/pymergetic_common.egg-info/SOURCES.txt +93 -0
  92. pymergetic_common-0.0.1/src_py/pymergetic_common.egg-info/dependency_links.txt +1 -0
  93. pymergetic_common-0.0.1/src_py/pymergetic_common.egg-info/entry_points.txt +4 -0
  94. pymergetic_common-0.0.1/src_py/pymergetic_common.egg-info/requires.txt +47 -0
  95. pymergetic_common-0.0.1/src_py/pymergetic_common.egg-info/top_level.txt +1 -0
@@ -0,0 +1,39 @@
1
+ # Publish sdist + pure-Python wheel to PyPI on tag v*.
2
+ # Requires repository secret: PYPI_API_TOKEN
3
+
4
+ name: Publish to PyPI
5
+
6
+ on:
7
+ push:
8
+ tags:
9
+ - "v*"
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ publish:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ fetch-depth: 0
21
+
22
+ - uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.12"
25
+
26
+ - name: Install build tools
27
+ run: pip install --upgrade build twine
28
+
29
+ - name: Build sdist and wheel
30
+ run: python -m build
31
+
32
+ - name: Check distributions
33
+ run: twine check dist/*
34
+
35
+ - name: Publish to PyPI
36
+ uses: pypa/gh-action-pypi-publish@release/v1
37
+ with:
38
+ password: ${{ secrets.PYPI_API_TOKEN }}
39
+ skip-existing: true
@@ -0,0 +1,207 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+ #poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ #pdm.lock
116
+ #pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ #pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # SageMath parsed files
135
+ *.sage.py
136
+
137
+ # Environments
138
+ .env
139
+ .envrc
140
+ .venv
141
+ env/
142
+ venv/
143
+ ENV/
144
+ env.bak/
145
+ venv.bak/
146
+
147
+ # Spyder project settings
148
+ .spyderproject
149
+ .spyproject
150
+
151
+ # Rope project settings
152
+ .ropeproject
153
+
154
+ # mkdocs documentation
155
+ /site
156
+
157
+ # mypy
158
+ .mypy_cache/
159
+ .dmypy.json
160
+ dmypy.json
161
+
162
+ # Pyre type checker
163
+ .pyre/
164
+
165
+ # pytype static type analyzer
166
+ .pytype/
167
+
168
+ # Cython debug symbols
169
+ cython_debug/
170
+
171
+ # PyCharm
172
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
175
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
+ #.idea/
177
+
178
+ # Abstra
179
+ # Abstra is an AI-powered process automation framework.
180
+ # Ignore directories containing user credentials, local state, and settings.
181
+ # Learn more at https://abstra.io/docs
182
+ .abstra/
183
+
184
+ # Visual Studio Code
185
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
+ # you could uncomment the following to ignore the entire vscode folder
189
+ # .vscode/
190
+
191
+ # Ruff stuff:
192
+ .ruff_cache/
193
+
194
+ # PyPI configuration file
195
+ .pypirc
196
+
197
+ # Cursor
198
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
199
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
200
+ # refer to https://docs.cursor.com/context/ignore-files
201
+ .cursorignore
202
+ .cursorindexingignore
203
+
204
+ # Marimo
205
+ marimo/_static/
206
+ marimo/_lsp/
207
+ __marimo__/
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from pymergetic.common import PyDataObject
6
+
7
+
8
+ def main() -> None:
9
+ # This sample uses the common package's test extension to demonstrate the API.
10
+ from pymergetic.common import __cpp_test__ as ext # type: ignore
11
+
12
+ DataPoint = PyDataObject.native(ext.DataPoint)
13
+
14
+ dp = DataPoint(ext.make_datapoint(7, "hello"))
15
+ blob = dp.to_bytes()
16
+ dp2 = DataPoint.from_bytes(blob)
17
+
18
+ print("native snapshot:", dp2.to_dict())
19
+ print("bytes length:", len(blob))
20
+
21
+ class Packet(BaseModel):
22
+ dp: DataPoint
23
+
24
+ pkt = Packet(dp=dp)
25
+ s = pkt.model_dump_json()
26
+ pkt2 = Packet.model_validate_json(s)
27
+
28
+ print("json (base64 bytes payload):")
29
+ print(s)
30
+ print("roundtrip snapshot:", pkt2.dp.to_dict())
31
+
32
+
33
+ if __name__ == "__main__":
34
+ main()
35
+
36
+
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from pymergetic.common import PyObject
6
+
7
+
8
+ def main() -> None:
9
+ # This sample uses the common package's test extension to demonstrate the API.
10
+ from pymergetic.common import __cpp_test__ as ext # type: ignore
11
+
12
+ svc = ext.make_network_service()
13
+ svc.connect("http://example")
14
+
15
+ class StatusResponse(BaseModel):
16
+ service: PyObject[object]
17
+
18
+ payload = StatusResponse(service=PyObject(svc))
19
+
20
+ print("python repr:", payload.service)
21
+ print("python dict snapshot:", payload.service.to_dict())
22
+ print("json (via pydantic -> calls C++ to_dict):")
23
+ print(payload.model_dump_json())
24
+
25
+
26
+ if __name__ == "__main__":
27
+ main()
28
+
29
+
@@ -0,0 +1,3 @@
1
+ #include <pymergetic/easybind/prelude.hpp>
2
+
3
+ EASYBIND_MODULE("pymergetic.common");
@@ -0,0 +1,9 @@
1
+ #include <pymergetic/easybind/prelude.hpp>
2
+
3
+ namespace pymergetic::common::bindings {
4
+
5
+ void bind_base(::nanobind::module_& /*m*/) {}
6
+
7
+ EASYBIND_REGISTER_PACKAGE("pymergetic.common", [](::nanobind::module_& m) { bind_base(m); });
8
+
9
+ } // namespace pymergetic::common::bindings
@@ -0,0 +1,6 @@
1
+ #pragma once
2
+
3
+ #include <pymergetic/common/base/base.hpp>
4
+ #include <pymergetic/common/base/defs.hpp>
5
+
6
+
@@ -0,0 +1,14 @@
1
+ #pragma once
2
+
3
+ namespace pymergetic::common {
4
+
5
+ // Placeholder for future C++ registration base types.
6
+ // (Keep stable; used as an ABI anchor across pymergetic extensions.)
7
+
8
+ struct RegistryBase {
9
+ virtual ~RegistryBase() = default;
10
+ };
11
+
12
+ } // namespace pymergetic::common
13
+
14
+
@@ -0,0 +1,14 @@
1
+ #pragma once
2
+
3
+ #include <string>
4
+
5
+ namespace pymergetic::common {
6
+
7
+ // Minimal shared definitions to anchor ABI expectations across extensions.
8
+ // Extend carefully: prefer additive changes.
9
+
10
+ using PeerId = std::string;
11
+
12
+ } // namespace pymergetic::common
13
+
14
+
@@ -0,0 +1,9 @@
1
+ #include <pymergetic/easybind/prelude.hpp>
2
+
3
+ namespace pymergetic::common::bindings {
4
+
5
+ void bind_codec(::nanobind::module_& /*m*/) {}
6
+
7
+ EASYBIND_REGISTER_PACKAGE("pymergetic.common", [](::nanobind::module_& m) { bind_codec(m); });
8
+
9
+ } // namespace pymergetic::common::bindings
@@ -0,0 +1,163 @@
1
+ #pragma once
2
+
3
+ #include <cstddef>
4
+ #include <cstdint>
5
+ #include <string>
6
+ #include <string_view>
7
+
8
+ #include <pymergetic/common/exceptions/__init__.hpp>
9
+
10
+ namespace pymergetic::common::codec {
11
+
12
+ // Canonical binary header for CppDataObject payloads.
13
+ //
14
+ // Layout (all little-endian):
15
+ // magic[4] = "PMDG"
16
+ // u8 version = 1
17
+ // u8 flags = 0 (reserved for compression/encryption)
18
+ // u16 schema = 0 (reserved schema version for payload encoding)
19
+ // u32 type_id = stable type id for the concrete data object
20
+ // u32 len = payload length in bytes
21
+ // payload[len]
22
+ //
23
+ // This makes payloads self-describing and forward-compatible.
24
+ inline constexpr char k_magic[4] = {'P', 'M', 'D', 'G'};
25
+ inline constexpr std::uint8_t k_header_version = 1;
26
+
27
+ // Stable type-id hash (FNV-1a 32-bit) for avoiding collisions across modules.
28
+ // Use fully-qualified names like "pymergetic.axon.PeerInfo".
29
+ inline constexpr std::uint32_t fnv1a32(std::string_view s) {
30
+ std::uint32_t h = 2166136261u;
31
+ for (char c : s) {
32
+ h ^= static_cast<std::uint8_t>(c);
33
+ h *= 16777619u;
34
+ }
35
+ return h;
36
+ }
37
+
38
+ inline constexpr std::uint32_t type_id(std::string_view fully_qualified_name) {
39
+ return fnv1a32(fully_qualified_name);
40
+ }
41
+
42
+ inline void append_u8(std::string& out, std::uint8_t v) { out.push_back(static_cast<char>(v)); }
43
+
44
+ inline void append_u16_le(std::string& out, std::uint16_t v) {
45
+ out.push_back(static_cast<char>(v & 0xFF));
46
+ out.push_back(static_cast<char>((v >> 8) & 0xFF));
47
+ }
48
+
49
+ inline void append_u32_le(std::string& out, std::uint32_t v) {
50
+ out.push_back(static_cast<char>(v & 0xFF));
51
+ out.push_back(static_cast<char>((v >> 8) & 0xFF));
52
+ out.push_back(static_cast<char>((v >> 16) & 0xFF));
53
+ out.push_back(static_cast<char>((v >> 24) & 0xFF));
54
+ }
55
+
56
+ inline void append_i32_le(std::string& out, std::int32_t v) {
57
+ append_u32_le(out, static_cast<std::uint32_t>(v));
58
+ }
59
+
60
+ inline std::uint16_t read_u16_le(const char* p, std::size_t n, std::size_t off) {
61
+ if (off + 2 > n) {
62
+ throw pymergetic::common::EndOfStreamError("codec: read_u16_le out of bounds");
63
+ }
64
+ return (static_cast<std::uint16_t>(static_cast<unsigned char>(p[off])) |
65
+ (static_cast<std::uint16_t>(static_cast<unsigned char>(p[off + 1])) << 8));
66
+ }
67
+
68
+ inline std::uint32_t read_u32_le(const char* p, std::size_t n, std::size_t off) {
69
+ if (off + 4 > n) {
70
+ throw pymergetic::common::EndOfStreamError("codec: read_u32_le out of bounds");
71
+ }
72
+ return (static_cast<std::uint32_t>(static_cast<unsigned char>(p[off])) |
73
+ (static_cast<std::uint32_t>(static_cast<unsigned char>(p[off + 1])) << 8) |
74
+ (static_cast<std::uint32_t>(static_cast<unsigned char>(p[off + 2])) << 16) |
75
+ (static_cast<std::uint32_t>(static_cast<unsigned char>(p[off + 3])) << 24));
76
+ }
77
+
78
+ inline std::int32_t read_i32_le(const char* p, std::size_t n, std::size_t off) {
79
+ return static_cast<std::int32_t>(read_u32_le(p, n, off));
80
+ }
81
+
82
+ inline void append_u32_len_prefixed(std::string& out, const std::string& bytes) {
83
+ append_u32_le(out, static_cast<std::uint32_t>(bytes.size()));
84
+ out.append(bytes);
85
+ }
86
+
87
+ inline std::string read_u32_len_prefixed_bytes(const char* p, std::size_t n, std::size_t off, std::size_t* next_off) {
88
+ const std::uint32_t len = read_u32_le(p, n, off);
89
+ const std::size_t start = off + 4;
90
+ const std::size_t end = start + static_cast<std::size_t>(len);
91
+ if (end > n) {
92
+ throw pymergetic::common::EndOfStreamError("codec: length-prefixed read out of bounds");
93
+ }
94
+ if (next_off) {
95
+ *next_off = end;
96
+ }
97
+ return std::string(p + start, p + end);
98
+ }
99
+
100
+ struct Header {
101
+ // NOTE: magic is implied/checked and not stored here.
102
+ std::uint8_t version{};
103
+ std::uint8_t flags{}; // reserved
104
+ std::uint16_t schema_ver{}; // reserved
105
+ std::uint32_t type_id{};
106
+ std::uint32_t payload_len{};
107
+ std::size_t payload_off{};
108
+ };
109
+
110
+ inline void append_header(std::string& out,
111
+ std::uint32_t type_id,
112
+ std::uint32_t payload_len,
113
+ std::uint8_t flags = 0,
114
+ std::uint16_t schema_ver = 0) {
115
+ // Reserve enough for header + payload to minimize reallocations.
116
+ out.reserve(out.size() + 16 + static_cast<std::size_t>(payload_len));
117
+ out.append(k_magic, k_magic + 4);
118
+ append_u8(out, k_header_version);
119
+ append_u8(out, flags);
120
+ append_u16_le(out, schema_ver);
121
+ append_u32_le(out, type_id);
122
+ append_u32_le(out, payload_len);
123
+ }
124
+
125
+ inline Header read_header(const char* p, std::size_t n) {
126
+ // Header is always exactly 16 bytes.
127
+ if (n < 16) {
128
+ throw pymergetic::common::EndOfStreamError("codec: buffer too small for header");
129
+ }
130
+ if (p[0] != k_magic[0] || p[1] != k_magic[1] || p[2] != k_magic[2] || p[3] != k_magic[3]) {
131
+ throw pymergetic::common::MagicMismatchError("codec: bad magic");
132
+ }
133
+ Header h;
134
+ h.version = static_cast<std::uint8_t>(p[4]);
135
+ h.flags = static_cast<std::uint8_t>(p[5]);
136
+ h.schema_ver = read_u16_le(p, n, 6);
137
+ if (h.version != k_header_version) {
138
+ throw pymergetic::common::CodecError("codec: unsupported header version");
139
+ }
140
+ // Offsets:
141
+ // version @ 4, flags @ 5, schema @ 6, type_id @ 8, len @ 12
142
+ h.type_id = read_u32_le(p, n, 8);
143
+ h.payload_len = read_u32_le(p, n, 12);
144
+ h.payload_off = 16;
145
+
146
+ // Validate the 16-byte aligned header layout.
147
+ if ((h.payload_off % 16) != 0) {
148
+ throw pymergetic::common::CodecError("codec: header is not 16-byte aligned");
149
+ }
150
+
151
+ const std::size_t expected_n = h.payload_off + static_cast<std::size_t>(h.payload_len);
152
+ if (n < expected_n) {
153
+ throw pymergetic::common::EndOfStreamError("codec: payload length mismatch");
154
+ }
155
+ if (n != expected_n) {
156
+ throw pymergetic::common::CodecError("codec: payload length mismatch");
157
+ }
158
+ return h;
159
+ }
160
+
161
+ } // namespace pymergetic::common::codec
162
+
163
+
@@ -0,0 +1,17 @@
1
+ #include <pymergetic/easybind/prelude.hpp>
2
+
3
+ #include <pymergetic/common/exceptions/__init__.hpp>
4
+
5
+ namespace pymergetic::common::bindings {
6
+
7
+ namespace nb = ::nanobind;
8
+
9
+ void bind_exceptions(::nanobind::module_& m) {
10
+ nb::exception<pymergetic::common::CodecError>(m, "CodecError");
11
+ nb::exception<pymergetic::common::EndOfStreamError>(m, "EndOfStreamError");
12
+ nb::exception<pymergetic::common::MagicMismatchError>(m, "MagicMismatchError");
13
+ }
14
+
15
+ EASYBIND_REGISTER_PACKAGE("pymergetic.common", [](::nanobind::module_& m) { bind_exceptions(m); });
16
+
17
+ } // namespace pymergetic::common::bindings
@@ -0,0 +1,28 @@
1
+ #pragma once
2
+
3
+ #include <stdexcept>
4
+ #include <string>
5
+
6
+ namespace pymergetic::common {
7
+
8
+ /// Base error for codec/serialization failures.
9
+ class CodecError : public std::runtime_error {
10
+ public:
11
+ using std::runtime_error::runtime_error;
12
+ };
13
+
14
+ /// Thrown when a buffer ends before the requested bytes are available.
15
+ class EndOfStreamError : public CodecError {
16
+ public:
17
+ using CodecError::CodecError;
18
+ };
19
+
20
+ /// Thrown when the PMDG magic does not match.
21
+ class MagicMismatchError : public CodecError {
22
+ public:
23
+ using CodecError::CodecError;
24
+ };
25
+
26
+ } // namespace pymergetic::common
27
+
28
+
@@ -0,0 +1,9 @@
1
+ #include <pymergetic/easybind/prelude.hpp>
2
+
3
+ namespace pymergetic::common::bindings {
4
+
5
+ void bind_header(::nanobind::module_& /*m*/) {}
6
+
7
+ EASYBIND_REGISTER_PACKAGE("pymergetic.common", [](::nanobind::module_& m) { bind_header(m); });
8
+
9
+ } // namespace pymergetic::common::bindings
@@ -0,0 +1,5 @@
1
+ #pragma once
2
+
3
+ // Reserved for header/metadata utilities. (Mirrors src_py and src_bind.)
4
+
5
+
@@ -0,0 +1,9 @@
1
+ #include <pymergetic/easybind/prelude.hpp>
2
+
3
+ namespace pymergetic::common::bindings {
4
+
5
+ void bind_nb(::nanobind::module_& /*m*/) {}
6
+
7
+ EASYBIND_REGISTER_PACKAGE("pymergetic.common", [](::nanobind::module_& m) { bind_nb(m); });
8
+
9
+ } // namespace pymergetic::common::bindings
@@ -0,0 +1,7 @@
1
+ #pragma once
2
+
3
+ #include <pymergetic/common/nb/asyncio_bridge.hpp>
4
+ #include <pymergetic/common/nb/base.hpp>
5
+ #include <pymergetic/common/nb/data.hpp>
6
+
7
+
@@ -0,0 +1,32 @@
1
+ #pragma once
2
+
3
+ #include <nanobind/nanobind.h>
4
+
5
+ namespace pymergetic::nb::asyncio_bridge {
6
+
7
+ namespace nb = nanobind;
8
+
9
+ // Thread-safe completion helpers for asyncio.Future from non-Python threads
10
+ // (e.g. Boost.Asio io_context thread).
11
+ inline void future_set_result(nb::object loop, nb::object future, nb::object value) {
12
+ nb::gil_scoped_acquire _gil;
13
+ loop.attr("call_soon_threadsafe")(future.attr("set_result"), value);
14
+ }
15
+
16
+ inline void future_set_exception(nb::object loop, nb::object future, nb::object exc) {
17
+ nb::gil_scoped_acquire _gil;
18
+ loop.attr("call_soon_threadsafe")(future.attr("set_exception"), exc);
19
+ }
20
+
21
+ inline nb::object get_running_loop() {
22
+ nb::module_ asyncio = nb::module_::import_("asyncio");
23
+ return asyncio.attr("get_running_loop")();
24
+ }
25
+
26
+ inline nb::object create_future(nb::object loop) {
27
+ return loop.attr("create_future")();
28
+ }
29
+
30
+ } // namespace pymergetic::nb::asyncio_bridge
31
+
32
+
@@ -0,0 +1,26 @@
1
+ #pragma once
2
+
3
+ #include <nanobind/nanobind.h>
4
+ #include <string>
5
+
6
+ namespace pymergetic::nb {
7
+
8
+ /// Binding-layer base class for native objects exposed to Python.
9
+ ///
10
+ /// IMPORTANT:
11
+ /// - This is a Level-2 (bindings) concept (it depends on nanobind).
12
+ /// - Do not use this in Level-1 engine code (EP-0006: Level 1 must not depend on Python).
13
+ class CppObject {
14
+ public:
15
+ virtual ~CppObject() = default;
16
+
17
+ /// Debug representation.
18
+ virtual std::string repr() const { return "<CppObject>"; }
19
+
20
+ /// Fast serialization to a Python dict (zero extra Python-side validation/ORM work).
21
+ virtual ::nanobind::dict to_dict() const { return ::nanobind::dict(); }
22
+ };
23
+
24
+ } // namespace pymergetic::nb
25
+
26
+