OpenSMI-Core 0.1__tar.gz → 0.2__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 (44) hide show
  1. opensmi_core-0.2/.github/workflows/api-docs.yml +58 -0
  2. {opensmi_core-0.1 → opensmi_core-0.2}/PKG-INFO +1 -1
  3. {opensmi_core-0.1 → opensmi_core-0.2}/pyproject.toml +1 -1
  4. {opensmi_core-0.1 → opensmi_core-0.2}/src/OpenSMI_Core.egg-info/PKG-INFO +1 -1
  5. {opensmi_core-0.1 → opensmi_core-0.2}/src/OpenSMI_Core.egg-info/SOURCES.txt +1 -0
  6. {opensmi_core-0.1 → opensmi_core-0.2}/src/OpenSMI_Core.egg-info/scm_file_list.json +1 -0
  7. opensmi_core-0.2/src/OpenSMI_Core.egg-info/scm_version.json +8 -0
  8. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/base_ua_object.py +24 -5
  9. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/ua_node_util.py +17 -10
  10. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/unit_all.py +14 -14
  11. {opensmi_core-0.1 → opensmi_core-0.2}/tools/auto_generate_units.py +1 -1
  12. opensmi_core-0.1/src/OpenSMI_Core.egg-info/scm_version.json +0 -8
  13. {opensmi_core-0.1 → opensmi_core-0.2}/.github/workflows/python-publish.yml +0 -0
  14. {opensmi_core-0.1 → opensmi_core-0.2}/.gitignore +0 -0
  15. {opensmi_core-0.1 → opensmi_core-0.2}/.gitlab-ci.yml +0 -0
  16. {opensmi_core-0.1 → opensmi_core-0.2}/LICENSE +0 -0
  17. {opensmi_core-0.1 → opensmi_core-0.2}/LICENSES/MIT.txt +0 -0
  18. {opensmi_core-0.1 → opensmi_core-0.2}/README.md +0 -0
  19. {opensmi_core-0.1 → opensmi_core-0.2}/setup.cfg +0 -0
  20. {opensmi_core-0.1 → opensmi_core-0.2}/src/OpenSMI_Core.egg-info/dependency_links.txt +0 -0
  21. {opensmi_core-0.1 → opensmi_core-0.2}/src/OpenSMI_Core.egg-info/requires.txt +0 -0
  22. {opensmi_core-0.1 → opensmi_core-0.2}/src/OpenSMI_Core.egg-info/top_level.txt +0 -0
  23. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/__init__.py +0 -0
  24. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/async_task_mixin.py +0 -0
  25. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/base_server.py +0 -0
  26. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/config.py +0 -0
  27. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/enums.py +0 -0
  28. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/errors.py +0 -0
  29. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/frozen.py +0 -0
  30. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/hook.py +0 -0
  31. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/lifecycle_mixin.py +0 -0
  32. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/logging.py +0 -0
  33. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/meta.py +0 -0
  34. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/mixin_helpers.py +0 -0
  35. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/protocols.py +0 -0
  36. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/repr_mixin.py +0 -0
  37. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/signal.py +0 -0
  38. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/subscription_manager.py +0 -0
  39. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/ua.py +0 -0
  40. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/ua_enums.py +0 -0
  41. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/unit_base.py +0 -0
  42. {opensmi_core-0.1 → opensmi_core-0.2}/src/opensmi/core/unset.py +0 -0
  43. {opensmi_core-0.1 → opensmi_core-0.2}/tests/test_signal.py +0 -0
  44. {opensmi_core-0.1 → opensmi_core-0.2}/tests/test_unit.py +0 -0
@@ -0,0 +1,58 @@
1
+ # Simple workflow for deploying static content to GitHub Pages
2
+ name: Publish API doc to Pages
3
+
4
+ on:
5
+ # Runs on pushes targeting the default branch
6
+ push:
7
+ branches: ["main"]
8
+
9
+ # Allows you to run this workflow manually from the Actions tab
10
+ workflow_dispatch:
11
+
12
+ # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13
+ permissions:
14
+ contents: read
15
+ pages: write
16
+ id-token: write
17
+
18
+ # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19
+ # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20
+ concurrency:
21
+ group: "pages"
22
+ cancel-in-progress: false
23
+
24
+ jobs:
25
+ # Single deploy job since we're just deploying
26
+ deploy:
27
+ environment:
28
+ name: github-pages
29
+ url: ${{ steps.deployment.outputs.page_url }}
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - name: Checkout
33
+ uses: actions/checkout@v4
34
+
35
+ - name: Set up Python
36
+ uses: actions/setup-python@v6
37
+ with:
38
+ python-version: "3.13"
39
+
40
+ - name: Install dependencies
41
+ run: |
42
+ pip install pydoctor
43
+
44
+ - name: Generate API documentation
45
+ run: |
46
+ pydoctor
47
+
48
+ - name: Setup Pages
49
+ uses: actions/configure-pages@v5
50
+
51
+ - name: Upload artifact
52
+ uses: actions/upload-pages-artifact@v3
53
+ with:
54
+ path: './apidocs'
55
+
56
+ - name: Deploy to GitHub Pages
57
+ id: deployment
58
+ uses: actions/deploy-pages@v5
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenSMI-Core
3
- Version: 0.1
3
+ Version: 0.2
4
4
  Summary: Smart Machine Interface core functionality for server and client applications.
5
5
  Author-email: Carsten Harms <Carsten.Harms@dfki.de>, Simon Jungbluth <Simon.Jungbluth@smartfactory.de>
6
6
  Classifier: Development Status :: 4 - Beta
@@ -176,5 +176,5 @@ add-package = ["src/opensmi/core"]
176
176
  docformat = "restructuredtext"
177
177
  theme = "readthedocs"
178
178
  # verbose = 1
179
- warnings-as-errors = true
179
+ # warnings-as-errors = true
180
180
  intersphinx = ["https://opcua-asyncio.readthedocs.io/en/latest/objects.inv"] # TODO(CaHa): does not work
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenSMI-Core
3
- Version: 0.1
3
+ Version: 0.2
4
4
  Summary: Smart Machine Interface core functionality for server and client applications.
5
5
  Author-email: Carsten Harms <Carsten.Harms@dfki.de>, Simon Jungbluth <Simon.Jungbluth@smartfactory.de>
6
6
  Classifier: Development Status :: 4 - Beta
@@ -3,6 +3,7 @@
3
3
  LICENSE
4
4
  README.md
5
5
  pyproject.toml
6
+ .github/workflows/api-docs.yml
6
7
  .github/workflows/python-publish.yml
7
8
  LICENSES/MIT.txt
8
9
  src/OpenSMI_Core.egg-info/PKG-INFO
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "files": [
3
+ ".github/workflows/api-docs.yml",
3
4
  ".github/workflows/python-publish.yml",
4
5
  ".gitignore",
5
6
  ".gitlab-ci.yml",
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.2",
3
+ "distance": 0,
4
+ "node": "g90c7c0325f86ebe6f93c7c832b29f4672c03faa6",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-09-23"
8
+ }
@@ -21,11 +21,14 @@ _ServerType = TypeVar("_ServerType", bound="BaseServer")
21
21
  class BaseUaObject(Generic[_ServerType]):
22
22
  """Base class for all UA objects, both client- and server-side."""
23
23
 
24
+ _parent: "BaseUaObject[_ServerType] | None" = None
25
+
24
26
  def __init__(
25
27
  self,
26
28
  *,
27
29
  name: str | None = None,
28
30
  server: _ServerType | None = None,
31
+ parent: "BaseUaObject[_ServerType] | None" = None,
29
32
  **kwargs,
30
33
  ) -> None:
31
34
  """*Cooperative* constructor.
@@ -33,6 +36,8 @@ class BaseUaObject(Generic[_ServerType]):
33
36
  :param name: The name of the UA object. Defaults to the class name.
34
37
  :param server: The associated server of the UA object. Note that there must be at least one non ``None`` in
35
38
  the hierarchy.
39
+ :param parent: The parent of the UA object. Only Machines are allowed to have no parent. Can be set later before
40
+ asynchronous initialization.
36
41
  """
37
42
  if name is None:
38
43
  name = self.__class__.__name__
@@ -42,6 +47,8 @@ class BaseUaObject(Generic[_ServerType]):
42
47
 
43
48
  self._name: str = name
44
49
  self._server: _ServerType | None = server
50
+ if parent is not None:
51
+ self.parent = parent # via setter to allow optional runtime type checking
45
52
  self._ua_node: Node | None = None
46
53
  self._logger: structlog.BoundLogger = structlog.get_logger(
47
54
  f"opensmi.{self.__class__.__name__}", name=self.name
@@ -76,16 +83,17 @@ class BaseUaObject(Generic[_ServerType]):
76
83
  @property
77
84
  @deprecated("Use path instead")
78
85
  def full_name(self) -> str:
79
- parent = getattr(self, "parent", None)
80
- if isinstance(parent, BaseUaObject):
81
- return f"{parent.full_name}/{self.name}"
86
+ if self.parent is not None:
87
+ assert isinstance(self.parent, BaseUaObject)
88
+ return f"{self.parent.full_name}/{self.name}"
82
89
  return f"/{self.name}"
83
90
 
84
91
  @property
85
92
  def path(self) -> PurePosixPath:
86
93
  """Return the full path of the object."""
87
- if isinstance(parent := getattr(self, "parent", None), BaseUaObject):
88
- return parent.path / self.name
94
+ if self.parent is not None:
95
+ assert isinstance(self.parent, BaseUaObject)
96
+ return self.parent.path / self.name
89
97
  return PurePosixPath("/") / self.name
90
98
 
91
99
  @property
@@ -93,6 +101,17 @@ class BaseUaObject(Generic[_ServerType]):
93
101
  """Logger associated with the `UaObject` instance."""
94
102
  return self._logger # type: ignore
95
103
 
104
+ @property
105
+ def parent(self) -> "BaseUaObject[_ServerType] | None":
106
+ """Return the untyped parent of the UA object."""
107
+ return self._parent
108
+
109
+ @parent.setter
110
+ def parent(self, parent: "BaseUaObject[_ServerType]") -> None:
111
+ """Set the parent of the UA object. Given ``parent`` must be an instance of `BaseUaObject`."""
112
+ assert isinstance(parent, BaseUaObject)
113
+ self._parent = parent
114
+
96
115
  # TODO
97
116
  # @override
98
117
  # def _repr_items(self) -> Iterator[tuple[str, object]]:
@@ -21,6 +21,8 @@ from opensmi.core import Unit
21
21
  NULL_NODE_ID = ua.NodeId()
22
22
  """ This node id indicates an invalid Node ID and should be treated as 'None'. """
23
23
 
24
+ _LOGGER = structlog.getLogger(__name__)
25
+
24
26
 
25
27
  async def read_value(node: Node) -> Any:
26
28
  """Read the value of the given node and transforms the NULL_NODE_ID into None."""
@@ -126,17 +128,22 @@ async def get_properties(node: Node, *, logger: structlog.stdlib.BoundLogger | N
126
128
  return result
127
129
 
128
130
 
129
- async def get_type_definition(ua_node: Node) -> str:
130
- """Return the browse name of the OPC UA type definition of the given node."""
131
- try:
132
- type_definition_list = await ua_node.get_references(
133
- ua.object_ids.ObjectIds.HasTypeDefinition,
134
- ua.BrowseDirection.Forward,
135
- )
131
+ async def get_type_definition(ua_node: Node, *, default: str = "UNKNOWN") -> str:
132
+ """Return the browse name of the OPC UA type definition of the given node.
133
+
134
+ :param ua_node: OPC UA node to operate on.
135
+ :param default: Default value to return if type definition cannot be determined.
136
+ """
137
+ assert ua_node is not None, "No node node given!"
138
+ type_definition_list = await ua_node.get_references(
139
+ ua.object_ids.ObjectIds.HasTypeDefinition,
140
+ ua.BrowseDirection.Forward,
141
+ )
142
+ if len(type_definition_list) > 0:
136
143
  return type_definition_list[0].BrowseName.Name
137
- except Exception as ex:
138
- msg = f"Could not find type definition for node {ua_node.nodeid.to_string()}!"
139
- raise RuntimeError(msg) from ex
144
+
145
+ _LOGGER.warning("Could not determine type definition!", ua_node = ua_node.nodeid.to_string(), default=default)
146
+ return default
140
147
 
141
148
 
142
149
  def get_node_id(
@@ -1,7 +1,7 @@
1
1
  """Units as type-checkable enum."""
2
2
 
3
3
  # !!! autogenerated code, do not change manually !!!
4
- # generated on 2026-09-10 08:32:16
4
+ # generated on 2026-09-15 14:19:13
5
5
  from opensmi.core.unit_base import BaseUnit
6
6
 
7
7
 
@@ -73,7 +73,7 @@ class Unit(BaseUnit):
73
73
  BARREL_UK_PETROLEUM_PER_MINUTE = ("bbl (UK liq.)/min", 4863288)
74
74
  BARREL_UK_PETROLEUM_PER_SECOND = ("bbl (UK liq.)/s", 4863537)
75
75
  BARREL_US = ("barrel (US)", 4344908)
76
- BARREL_US_PER_DAY = ("barrel\xa0(US)/d", 16945)
76
+ BARREL_US_PER_DAY = ("barrel (US)/d", 16945)
77
77
  BARREL_US_PER_MINUTE = ("barrel (US)/min", 13633)
78
78
  BARREL_US_PETROLEUM_PER_HOUR = ("bbl (US)/h", 4863538)
79
79
  BARREL_US_PETROLEUM_PER_SECOND = ("bbl (US)/s", 4863539)
@@ -119,9 +119,9 @@ class Unit(BaseUnit):
119
119
  BOTTLE = ("bottle", 16975)
120
120
  BOX = ("box", 16984)
121
121
  BRAKE_HORSE_POWER = ("BHP", 4343888)
122
- BRITISH_THERMAL_UNIT_39_F = ("Btu (39 ºF) ", 5125686)
122
+ BRITISH_THERMAL_UNIT_39_F = ("Btu (39 ºF)", 5125686)
123
123
  BRITISH_THERMAL_UNIT_59_F = ("Btu (59 ºF)", 5125687)
124
- BRITISH_THERMAL_UNIT_60_F = ("Btu (60 ºF) ", 5125688)
124
+ BRITISH_THERMAL_UNIT_60_F = ("Btu (60 ºF)", 5125688)
125
125
  BRITISH_THERMAL_UNIT_INTERNATIONAL_TABLE = ("BtuIT", 4346965)
126
126
  BRITISH_THERMAL_UNIT_INTERNATIONAL_TABLE_FOOT_PER_HOUR_SQUARE_FOOT_DEGREE_FAHRENHEIT = (
127
127
  "BtuIT·ft/(h·ft²·°F)",
@@ -167,10 +167,10 @@ class Unit(BaseUnit):
167
167
  BRITISH_THERMAL_UNIT_THERMOCHEMICAL_PER_POUND_DEGREE_FAHRENHEIT = ("Btuth/(lb·°F)", 4863280)
168
168
  BRITISH_THERMAL_UNIT_THERMOCHEMICAL_PER_POUND_DEGREE_RANKINE = ("(Btuth/°R)/lb", 5125684)
169
169
  BRITISH_THERMAL_UNIT_THERMOCHEMICAL_PER_SECOND = ("Btuth/s", 4863282)
170
- BRITISH_THERMAL_UNIT_THERMOCHEMICAL_PER_SECOND_SQUARE_FOOT_DEGREE_FAHRENHEIT = ("Btuth/(s·ft²·ºF) ", 5125943)
170
+ BRITISH_THERMAL_UNIT_THERMOCHEMICAL_PER_SECOND_SQUARE_FOOT_DEGREE_FAHRENHEIT = ("Btuth/(s·ft²·ºF)", 5125943)
171
171
  BRITISH_THERMAL_UNIT_THERMOCHEMICAL_PER_SQUARE_FOOT = ("Btuth/ft²", 5255992)
172
172
  BRITISH_THERMAL_UNIT_THERMOCHEMICAL_PER_SQUARE_FOOT_HOUR = ("Btuth/(ft²·h)", 5125425)
173
- BRITISH_THERMAL_UNIT_THERMOCHEMICAL_PER_SQUARE_FOOT_MINUTE = ("Btuth/(ft²·min) ", 5125426)
173
+ BRITISH_THERMAL_UNIT_THERMOCHEMICAL_PER_SQUARE_FOOT_MINUTE = ("Btuth/(ft²·min)", 5125426)
174
174
  BRITISH_THERMAL_UNIT_THERMOCHEMICAL_PER_SQUARE_FOOT_SECOND = ("Btuth/(ft²·s)", 5125428)
175
175
  BRUSH = ("brush", 16968)
176
176
  BTU_PER_CUBIC_FOOT = ("BTU/ft³", 16944)
@@ -261,7 +261,7 @@ class Unit(BaseUnit):
261
261
  CENTIPOISE_PER_BAR = ("cP/bar", 4863796)
262
262
  CENTIPOISE_PER_KELVIN = ("cP/K", 4863795)
263
263
  CENTISTOKES = ("cSt", 13379)
264
- CHAIN_BASED_ON_U_S_SURVEY_FOOT = ("ch (US survey) ", 5059641)
264
+ CHAIN_BASED_ON_U_S_SURVEY_FOOT = ("ch (US survey)", 5059641)
265
265
  CHEST = ("chest", 23090)
266
266
  CHEVAL_VAPEUR = ("CV", 4272693)
267
267
  CIRCULAR_MIL = ("cmil", 5059639)
@@ -562,7 +562,7 @@ class Unit(BaseUnit):
562
562
  FOOT_POUND_FORCE_PER_MINUTE = ("ft·lbf/min", 4927798)
563
563
  FOOT_POUND_FORCE_PER_SECOND = ("ft·lbf/s", 4273972)
564
564
  FOOT_TO_THE_FOURTH_POWER = ("ft⁴", 5124663)
565
- FOOT_U_S_SURVEY = ("ft (US survey) ", 5059889)
565
+ FOOT_U_S_SURVEY = ("ft (US survey)", 5059889)
566
566
  FORMAZIN_NEPHELOMETRIC_UNIT = ("FNU", 4607573)
567
567
  FORTY_FOOT_CONTAINER = ("forty foot container", 12849)
568
568
  FOUR_PACK = ("four pack", 20532)
@@ -1235,7 +1235,7 @@ class Unit(BaseUnit):
1235
1235
  MICROWATT_PER_SQUARE_METRE = ("µW/m²", 4470837)
1236
1236
  MICRO_INCH = ("µin", 19767)
1237
1237
  MIL = ("mil", 5059635)
1238
- MILE_BASED_ON_U_S_SURVEY_FOOT = ("mi (US survey) ", 5059890)
1238
+ MILE_BASED_ON_U_S_SURVEY_FOOT = ("mi (US survey)", 5059890)
1239
1239
  MILE_PER_HOUR_STATUTE_MILE = ("mile/h", 18509)
1240
1240
  MILE_PER_MINUTE = ("mi/min", 5059895)
1241
1241
  MILE_PER_SECOND = ("mi/s", 5059896)
@@ -1297,7 +1297,7 @@ class Unit(BaseUnit):
1297
1297
  MILLIHENRY = ("mH", 4403508)
1298
1298
  MILLIHENRY_PER_KILOOHM = ("mH/kΩ", 4730933)
1299
1299
  MILLIHENRY_PER_OHM = ("mH/Ω", 4730934)
1300
- MILLIHERTZ = ("mHz\xa0", 5067866)
1300
+ MILLIHERTZ = ("mHz", 5067866)
1301
1301
  MILLIJOULE = ("mJ", 4403509)
1302
1302
  MILLILITRE = ("ml", 5065812)
1303
1303
  MILLILITRE_OF_WATER = ("millilitre of water", 22359)
@@ -1487,7 +1487,7 @@ class Unit(BaseUnit):
1487
1487
  OERSTED = ("Oe", 13878)
1488
1488
  OHM = ("Ω", 5195853)
1489
1489
  OHM_CENTIMETRE = ("Ω·cm", 4404784)
1490
- OHM_CIRCULAR_MIL_PER_FOOT = ("Ω·cmil/ft ", 5255731)
1490
+ OHM_CIRCULAR_MIL_PER_FOOT = ("Ω·cmil/ft", 5255731)
1491
1491
  OHM_KILOMETRE = ("Ω·km", 5059124)
1492
1492
  OHM_METRE = ("Ω·m", 4404785)
1493
1493
  OHM_PER_KILOMETRE = ("Ω/km", 4601142)
@@ -1598,8 +1598,8 @@ class Unit(BaseUnit):
1598
1598
  PERCENT_PER_VOLT = ("%/V", 4733237)
1599
1599
  PERCENT_VOLUME = ("percent volume", 22096)
1600
1600
  PERCENT_WEIGHT = ("percent weight", 13872)
1601
- PERM_0_C = ("perm (0 ºC) ", 5257521)
1602
- PERM_23_C = ("perm (23 ºC) ", 5257522)
1601
+ PERM_0_C = ("perm (0 ºC)", 5257521)
1602
+ PERM_23_C = ("perm (23 ºC)", 5257522)
1603
1603
  PERSON = ("person", 18757)
1604
1604
  PER_MILLE_PER_PSI = ("‰/psi", 4862258)
1605
1605
  PETABIT = ("Pbit", 4536120)
@@ -2114,7 +2114,7 @@ class Unit(BaseUnit):
2114
2114
  UNIT_15_C_CALORIE = ("cal₁₅", 16689)
2115
2115
  UNIT_30_DAY_MONTH = ("mo (30 days)", 5059382)
2116
2116
  UNIT_8_PART_CLOUD_COVER = ("8-part cloud cover", 4273465)
2117
- UNIT_POLE = ("unit pole ", 5256499)
2117
+ UNIT_POLE = ("unit pole", 5256499)
2118
2118
  USAGE_PER_TELECOMMUNICATION_LINE_AVERAGE = ("usage per telecommunication line average", 21830)
2119
2119
  USE = ("use", 4535605)
2120
2120
  US_GALLON_PER_MINUTE = ("gal (US) /min", 18226)
@@ -108,7 +108,7 @@ def _parse(path: Path) -> dict[str, _UneceUnit]:
108
108
 
109
109
  unit = _UneceUnit(
110
110
  name=name,
111
- symbol=str(symbol),
111
+ symbol=str(symbol).replace("\xa0", " ").strip(), # replace non-breaking space with space,
112
112
  common_code=str(common_code),
113
113
  level=str(level),
114
114
  )
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.1",
3
- "distance": 0,
4
- "node": "g00bee2346bf90929caf7a2fb0ea251f864e2174c",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-09-15"
8
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes