From 3f1315c9f7f4856b0ce9ef2e261a85d24ea23de0 Mon Sep 17 00:00:00 2001 From: hephaisto <1156478+hephaisto@users.noreply.github.com> Date: Sun, 9 Aug 2026 17:44:21 +0200 Subject: [PATCH 1/2] Fix entities module dependencies to main common file Move all identifiers used in entities/* from common.py to entities/helper.py Rationale: Sub-packages should not depend on files of the parent hierarchy. Previously, files in entities used absolute import "common" which is ambiguous in some contexts. --- common.py | 48 +--------------------------------- entities/common.py | 4 +-- entities/component.py | 2 +- entities/device.py | 6 ++--- entities/helper.py | 61 +++++++++++++++++++++++++++++++++++++++++-- entities/package.py | 2 +- entities/symbol.py | 2 +- test_common.py | 3 ++- 8 files changed, 69 insertions(+), 59 deletions(-) diff --git a/common.py b/common.py index f158a3fb..028846c9 100644 --- a/common.py +++ b/common.py @@ -6,7 +6,7 @@ import csv import re from datetime import datetime -from os import makedirs, path +from os import path from typing import Any, Dict, Iterable, List, OrderedDict, Union @@ -52,29 +52,6 @@ def now() -> str: return datetime.utcnow().replace(microsecond=0).isoformat() + 'Z' -def escape_string(string: str) -> str: - """ - Escape a string according to LibrePCB S-Expression escaping rules. - """ - for search, replacement in STRING_ESCAPE_SEQUENCES: - string = string.replace(search, replacement) - return string - - -def format_float(number: float) -> str: - """ - Format a float according to LibrePCB normalization rules. - """ - formatted = '{:.3f}'.format(number) - if formatted == '-0.000': - return '0.0' # Remove useless sign - if formatted[-1] == '0': - if formatted[-2] == '0': - return formatted[:-2] - return formatted[:-1] - return formatted - - def format_ipc_dimension(number: float, decimal_places: int = 2) -> str: """ Format a dimension (e.g. lead span or height) according to IPC rules. @@ -91,13 +68,6 @@ def format_ipc_dimension(number: float, decimal_places: int = 2) -> str: return str(int(round(number, 6 - decimal_places))) -def indent(level: int, lines: Iterable[str]) -> List[str]: - """ - Indent the lines by the specified level. - """ - return [' ' * level + line for line in lines] - - def sign(val: Union[int, float]) -> int: """ Return 1 for positive or zero values, -1 otherwise. @@ -135,19 +105,3 @@ def _convert(text: str) -> Union[int, str]: return int(text) if text.isdigit() else text return [_convert(x) for x in re.split(r'(\d+)', key) if x] - - -def serialize_common( - serializable: Any, output_directory: str, uuid: str, long_type: str, short_type: str -) -> None: - """ - Centralized serialize() implementation shared between Component, Symbol, Device, Package - """ - dir_path = path.join(output_directory, uuid) - if not (path.exists(dir_path) and path.isdir(dir_path)): - makedirs(dir_path) - with open(path.join(dir_path, f'.librepcb-{short_type}'), 'w', newline='\n') as f: - f.write('2\n') - with open(path.join(dir_path, f'{long_type}.lp'), 'w', newline='\n') as f: - f.write(str(serializable)) - f.write('\n') diff --git a/entities/common.py b/entities/common.py index f45c9c9c..32e18862 100644 --- a/entities/common.py +++ b/entities/common.py @@ -6,9 +6,7 @@ from typing import List, Optional -from common import escape_string, format_float - -from .helper import indent_entities +from .helper import indent_entities, escape_string, format_float class EnumValue(Enum): diff --git a/entities/component.py b/entities/component.py index bbcb2331..9e134d8e 100644 --- a/entities/component.py +++ b/entities/component.py @@ -1,6 +1,6 @@ from typing import Iterable, List, Optional -from common import serialize_common +from .helper import serialize_common from .common import ( Author, diff --git a/entities/device.py b/entities/device.py index c493e898..5446529d 100644 --- a/entities/device.py +++ b/entities/device.py @@ -1,10 +1,10 @@ from typing import Iterable, List, Optional -from common import escape_string, serialize_common -from entities.attribute import Attribute -from entities.common import BoolValue +from .helper import escape_string, serialize_common +from .attribute import Attribute from .common import ( + BoolValue, Author, Category, Created, diff --git a/entities/helper.py b/entities/helper.py index 7b14a254..d475a5a7 100644 --- a/entities/helper.py +++ b/entities/helper.py @@ -1,6 +1,24 @@ -from typing import Any, Iterable +from typing import Any, Iterable, List +from os import makedirs, path -from common import indent +# String escape sequences +STRING_ESCAPE_SEQUENCES = ( + ('\\', '\\\\'), # Must be the first one to avoid recursion! + ('\b', '\\b'), + ('\f', '\\f'), + ('\n', '\\n'), + ('\r', '\\r'), + ('\t', '\\t'), + ('\v', '\\v'), + ('"', '\\"'), +) + + +def indent(level: int, lines: Iterable[str]) -> List[str]: + """ + Indent the lines by the specified level. + """ + return [' ' * level + line for line in lines] def indent_entity(entity: Any) -> str: @@ -27,3 +45,42 @@ def indent_entities(entities: Iterable[Any]) -> str: ' (bar "2")\\n (bar "3")\\n' """ return ''.join(map(indent_entity, entities)) + + +def escape_string(string: str) -> str: + """ + Escape a string according to LibrePCB S-Expression escaping rules. + """ + for search, replacement in STRING_ESCAPE_SEQUENCES: + string = string.replace(search, replacement) + return string + + +def format_float(number: float) -> str: + """ + Format a float according to LibrePCB normalization rules. + """ + formatted = '{:.3f}'.format(number) + if formatted == '-0.000': + return '0.0' # Remove useless sign + if formatted[-1] == '0': + if formatted[-2] == '0': + return formatted[:-2] + return formatted[:-1] + return formatted + + +def serialize_common( + serializable: Any, output_directory: str, uuid: str, long_type: str, short_type: str +) -> None: + """ + Centralized serialize() implementation shared between Component, Symbol, Device, Package + """ + dir_path = path.join(output_directory, uuid) + if not (path.exists(dir_path) and path.isdir(dir_path)): + makedirs(dir_path) + with open(path.join(dir_path, f'.librepcb-{short_type}'), 'w', newline='\n') as f: + f.write('2\n') + with open(path.join(dir_path, f'{long_type}.lp'), 'w', newline='\n') as f: + f.write(str(serializable)) + f.write('\n') diff --git a/entities/package.py b/entities/package.py index e855a652..7a897739 100644 --- a/entities/package.py +++ b/entities/package.py @@ -1,6 +1,6 @@ from typing import Iterable, List, Optional, Union -from common import format_float, serialize_common +from .helper import format_float, serialize_common from .common import ( Align, diff --git a/entities/symbol.py b/entities/symbol.py index aa5135b3..3f7b0a45 100644 --- a/entities/symbol.py +++ b/entities/symbol.py @@ -1,6 +1,6 @@ from typing import Iterable, List -from common import format_float, serialize_common +from .helper import format_float, serialize_common from .common import ( Author, diff --git a/test_common.py b/test_common.py index e01e0983..594f8d2a 100644 --- a/test_common.py +++ b/test_common.py @@ -1,6 +1,7 @@ import pytest -from common import escape_string, format_float, format_ipc_dimension, human_sort_key, sign +from entities.helper import escape_string, format_float +from common import format_ipc_dimension, human_sort_key, sign @pytest.mark.parametrize( From 8ec0783b30354e9168e5cc4bc38fc73ff8dcc4f2 Mon Sep 17 00:00:00 2001 From: hephaisto <1156478+hephaisto@users.noreply.github.com> Date: Tue, 11 Aug 2026 21:01:13 +0200 Subject: [PATCH 2/2] Apply fixes --- common.py | 14 +------------- entities/common.py | 2 +- entities/component.py | 4 +--- entities/device.py | 6 ++---- entities/helper.py | 3 ++- entities/package.py | 4 +--- entities/symbol.py | 4 +--- test_common.py | 2 +- 8 files changed, 10 insertions(+), 29 deletions(-) diff --git a/common.py b/common.py index 028846c9..6c6429de 100644 --- a/common.py +++ b/common.py @@ -8,19 +8,7 @@ from datetime import datetime from os import path -from typing import Any, Dict, Iterable, List, OrderedDict, Union - -# String escape sequences -STRING_ESCAPE_SEQUENCES = ( - ('\\', '\\\\'), # Must be the first one to avoid recursion! - ('\b', '\\b'), - ('\f', '\\f'), - ('\n', '\\n'), - ('\r', '\\r'), - ('\t', '\\t'), - ('\v', '\\v'), - ('"', '\\"'), -) +from typing import Any, Dict, List, OrderedDict, Union def init_cache(uuid_cache_file: str) -> Dict[str, str]: diff --git a/entities/common.py b/entities/common.py index 32e18862..7c94e47e 100644 --- a/entities/common.py +++ b/entities/common.py @@ -6,7 +6,7 @@ from typing import List, Optional -from .helper import indent_entities, escape_string, format_float +from .helper import escape_string, format_float, indent_entities class EnumValue(Enum): diff --git a/entities/component.py b/entities/component.py index 9e134d8e..2ded04f5 100644 --- a/entities/component.py +++ b/entities/component.py @@ -1,7 +1,5 @@ from typing import Iterable, List, Optional -from .helper import serialize_common - from .common import ( Author, BoolValue, @@ -19,7 +17,7 @@ UUIDValue, Version, ) -from .helper import indent_entities +from .helper import indent_entities, serialize_common class DefaultValue(StringValue): diff --git a/entities/device.py b/entities/device.py index 5446529d..83fd1f8e 100644 --- a/entities/device.py +++ b/entities/device.py @@ -1,11 +1,9 @@ from typing import Iterable, List, Optional -from .helper import escape_string, serialize_common from .attribute import Attribute - from .common import ( - BoolValue, Author, + BoolValue, Category, Created, Deprecated, @@ -19,7 +17,7 @@ Version, ) from .component import SignalUUID -from .helper import indent_entities +from .helper import escape_string, indent_entities, serialize_common class ComponentUUID(UUIDValue): diff --git a/entities/helper.py b/entities/helper.py index d475a5a7..7b03d64f 100644 --- a/entities/helper.py +++ b/entities/helper.py @@ -1,6 +1,7 @@ -from typing import Any, Iterable, List from os import makedirs, path +from typing import Any, Iterable, List + # String escape sequences STRING_ESCAPE_SEQUENCES = ( ('\\', '\\\\'), # Must be the first one to avoid recursion! diff --git a/entities/package.py b/entities/package.py index 7a897739..ee845738 100644 --- a/entities/package.py +++ b/entities/package.py @@ -1,7 +1,5 @@ from typing import Iterable, List, Optional, Union -from .helper import format_float, serialize_common - from .common import ( Align, Author, @@ -29,7 +27,7 @@ Version, Vertex, ) -from .helper import indent_entities +from .helper import format_float, indent_entities, serialize_common class Package3DModel: diff --git a/entities/symbol.py b/entities/symbol.py index 3f7b0a45..844c1d0d 100644 --- a/entities/symbol.py +++ b/entities/symbol.py @@ -1,7 +1,5 @@ from typing import Iterable, List -from .helper import format_float, serialize_common - from .common import ( Author, Category, @@ -20,7 +18,7 @@ Text, Version, ) -from .helper import indent_entities +from .helper import format_float, indent_entities, serialize_common class NamePosition: diff --git a/test_common.py b/test_common.py index 594f8d2a..d5243496 100644 --- a/test_common.py +++ b/test_common.py @@ -1,7 +1,7 @@ import pytest -from entities.helper import escape_string, format_float from common import format_ipc_dimension, human_sort_key, sign +from entities.helper import escape_string, format_float @pytest.mark.parametrize(