From d5e26109b8a0a6b8598d528c56ad044ab54900fe Mon Sep 17 00:00:00 2001 From: Luis Segalla Date: Mon, 24 Mar 2025 13:39:11 +0000 Subject: [PATCH 01/10] Initial changes to add pv creation helper --- src/fastcs/transport/epics/ca/ioc.py | 5 +++-- src/fastcs/transport/epics/gui.py | 8 ++++---- src/fastcs/transport/epics/pva/ioc.py | 9 +-------- src/fastcs/transport/epics/util.py | 10 ++++++++++ 4 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 src/fastcs/transport/epics/util.py diff --git a/src/fastcs/transport/epics/ca/ioc.py b/src/fastcs/transport/epics/ca/ioc.py index 47a202af0..fd7c04486 100644 --- a/src/fastcs/transport/epics/ca/ioc.py +++ b/src/fastcs/transport/epics/ca/ioc.py @@ -17,6 +17,7 @@ record_metadata_from_datatype, ) from fastcs.transport.epics.options import EpicsIOCOptions +from fastcs.transport.epics.util import _snake_to_pascal EPICS_MAX_NAME_LENGTH = 60 @@ -120,7 +121,7 @@ def _create_and_link_attribute_pvs( for controller_api in root_controller_api.walk_api(): path = controller_api.path for attr_name, attribute in controller_api.attributes.items(): - pv_name = attr_name.title().replace("_", "") + pv_name = _snake_to_pascal(attr_name) _pv_prefix = ":".join([pv_prefix] + path) full_pv_name_length = len(f"{_pv_prefix}:{pv_name}") @@ -219,7 +220,7 @@ def _create_and_link_command_pvs( for controller_api in root_controller_api.walk_api(): path = controller_api.path for attr_name, method in controller_api.command_methods.items(): - pv_name = attr_name.title().replace("_", "") + pv_name = _snake_to_pascal(attr_name) _pv_prefix = ":".join([pv_prefix] + path) if len(f"{_pv_prefix}:{pv_name}") > EPICS_MAX_NAME_LENGTH: print( diff --git a/src/fastcs/transport/epics/gui.py b/src/fastcs/transport/epics/gui.py index 0ad048fcd..3c8cfe20f 100644 --- a/src/fastcs/transport/epics/gui.py +++ b/src/fastcs/transport/epics/gui.py @@ -30,6 +30,7 @@ from fastcs.util import snake_to_pascal from .options import EpicsGUIFormat, EpicsGUIOptions +from .util import _snake_to_pascal class EpicsGUI: @@ -41,7 +42,7 @@ def __init__(self, controller_api: ControllerAPI, pv_prefix: str) -> None: def _get_pv(self, attr_path: list[str], name: str): attr_prefix = ":".join([self._pv_prefix] + attr_path) - return f"{attr_prefix}:{name.title().replace('_', '')}" + return f"{attr_prefix}:{_snake_to_pascal(name)}" @staticmethod def _get_read_widget(attribute: AttrR) -> ReadWidgetUnion | None: @@ -79,8 +80,7 @@ def _get_attribute_component( self, attr_path: list[str], name: str, attribute: Attribute ) -> SignalR | SignalW | SignalRW | None: pv = self._get_pv(attr_path, name) - name = name.title().replace("_", "") - + name = _snake_to_pascal(name) match attribute: case AttrRW(): read_widget = self._get_read_widget(attribute) @@ -109,7 +109,7 @@ def _get_attribute_component( def _get_command_component(self, attr_path: list[str], name: str): pv = self._get_pv(attr_path, name) - name = name.title().replace("_", "") + name = _snake_to_pascal(name) return SignalX( name=name, diff --git a/src/fastcs/transport/epics/pva/ioc.py b/src/fastcs/transport/epics/pva/ioc.py index 923dc618e..32b29db09 100644 --- a/src/fastcs/transport/epics/pva/ioc.py +++ b/src/fastcs/transport/epics/pva/ioc.py @@ -1,10 +1,10 @@ import asyncio -import re from p4p.server import Server, StaticProvider from fastcs.attributes import Attribute, AttrR, AttrRW, AttrW from fastcs.controller_api import ControllerAPI +from fastcs.transport.epics.util import _snake_to_pascal from ._pv_handlers import make_command_pv, make_shared_pv from .pvi_tree import AccessModeType, PviTree @@ -22,13 +22,6 @@ def _attribute_to_access(attribute: Attribute) -> AccessModeType: raise ValueError(f"Unknown attribute type {type(attribute)}") -def _snake_to_pascal(name: str) -> str: - name = re.sub( - r"(?:^|_)([a-z])", lambda match: match.group(1).upper(), name - ).replace("_", "") - return re.sub(r"_(\d+)$", r"\1", name) - - def get_pv_name(pv_prefix: str, *attribute_names: str) -> str: """Converts from an attribute name to a pv name.""" pv_formatted = ":".join([_snake_to_pascal(attr) for attr in attribute_names]) diff --git a/src/fastcs/transport/epics/util.py b/src/fastcs/transport/epics/util.py new file mode 100644 index 000000000..4bd1d09c9 --- /dev/null +++ b/src/fastcs/transport/epics/util.py @@ -0,0 +1,10 @@ +import re + + +def _snake_to_pascal(name: str) -> str: + name = ( + re.sub(r"(?:^|_)([a-z])", lambda match: match.group(1).upper(), name.title()) + .replace("_", "") + .replace("-", "") + ) + return re.sub(r"_(\d+)$", r"\1", name) From f198c5da4eb4dd654e1cb5be01064471e76e55f4 Mon Sep 17 00:00:00 2001 From: Luis Segalla Date: Mon, 24 Mar 2025 15:07:50 +0000 Subject: [PATCH 02/10] Changed tests --- tests/transport/epics/pva/test_p4p.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/transport/epics/pva/test_p4p.py b/tests/transport/epics/pva/test_p4p.py index bd98c4a80..8cc6f5aad 100644 --- a/tests/transport/epics/pva/test_p4p.py +++ b/tests/transport/epics/pva/test_p4p.py @@ -297,10 +297,10 @@ class SomeController(Controller): sub_controller = ChildController() controller.register_sub_controller("Child0", sub_controller) - sub_controller.register_sub_controller("ChildChild", ChildChildController()) + sub_controller.register_sub_controller("Child_Child", ChildChildController()) sub_controller = ChildController() controller.register_sub_controller("Child1", sub_controller) - sub_controller.register_sub_controller("ChildChild", ChildChildController()) + sub_controller.register_sub_controller("Child_Child", ChildChildController()) sub_controller = ChildController() controller.register_sub_controller("Child2", sub_controller) sub_controller.register_sub_controller("ChildChild", ChildChildController()) @@ -308,7 +308,7 @@ class SomeController(Controller): controller.register_sub_controller("another_child", sub_controller) sub_controller.register_sub_controller("ChildChild", ChildChildController()) sub_controller = ChildController() - controller.register_sub_controller("AdditionalChild", sub_controller) + controller.register_sub_controller("additional_child", sub_controller) sub_controller.register_sub_controller("ChildChild", ChildChildController()) sub_controller = ChildController() controller.register_sub_controller("child_attribute_same_name", sub_controller) From f52af1f1d20ad2fd5c7fb5d095f2a00042ad0991 Mon Sep 17 00:00:00 2001 From: Luis Segalla Date: Tue, 25 Mar 2025 16:15:17 +0000 Subject: [PATCH 03/10] PR suggestions Updated public snake_to_pascal function and added tests for it. --- src/fastcs/transport/epics/ca/ioc.py | 6 +++--- src/fastcs/transport/epics/gui.py | 7 +++---- src/fastcs/transport/epics/pva/ioc.py | 4 ++-- src/fastcs/transport/epics/util.py | 10 ---------- src/fastcs/util.py | 6 +++++- tests/test_util.py | 28 +++++++++++++++++++++++++++ 6 files changed, 41 insertions(+), 20 deletions(-) delete mode 100644 src/fastcs/transport/epics/util.py create mode 100644 tests/test_util.py diff --git a/src/fastcs/transport/epics/ca/ioc.py b/src/fastcs/transport/epics/ca/ioc.py index fd7c04486..c0cae76d2 100644 --- a/src/fastcs/transport/epics/ca/ioc.py +++ b/src/fastcs/transport/epics/ca/ioc.py @@ -17,7 +17,7 @@ record_metadata_from_datatype, ) from fastcs.transport.epics.options import EpicsIOCOptions -from fastcs.transport.epics.util import _snake_to_pascal +from fastcs.util import snake_to_pascal EPICS_MAX_NAME_LENGTH = 60 @@ -121,7 +121,7 @@ def _create_and_link_attribute_pvs( for controller_api in root_controller_api.walk_api(): path = controller_api.path for attr_name, attribute in controller_api.attributes.items(): - pv_name = _snake_to_pascal(attr_name) + pv_name = snake_to_pascal(attr_name) _pv_prefix = ":".join([pv_prefix] + path) full_pv_name_length = len(f"{_pv_prefix}:{pv_name}") @@ -220,7 +220,7 @@ def _create_and_link_command_pvs( for controller_api in root_controller_api.walk_api(): path = controller_api.path for attr_name, method in controller_api.command_methods.items(): - pv_name = _snake_to_pascal(attr_name) + pv_name = snake_to_pascal(attr_name) _pv_prefix = ":".join([pv_prefix] + path) if len(f"{_pv_prefix}:{pv_name}") > EPICS_MAX_NAME_LENGTH: print( diff --git a/src/fastcs/transport/epics/gui.py b/src/fastcs/transport/epics/gui.py index 3c8cfe20f..c7fbff30a 100644 --- a/src/fastcs/transport/epics/gui.py +++ b/src/fastcs/transport/epics/gui.py @@ -30,7 +30,6 @@ from fastcs.util import snake_to_pascal from .options import EpicsGUIFormat, EpicsGUIOptions -from .util import _snake_to_pascal class EpicsGUI: @@ -42,7 +41,7 @@ def __init__(self, controller_api: ControllerAPI, pv_prefix: str) -> None: def _get_pv(self, attr_path: list[str], name: str): attr_prefix = ":".join([self._pv_prefix] + attr_path) - return f"{attr_prefix}:{_snake_to_pascal(name)}" + return f"{attr_prefix}:{snake_to_pascal(name)}" @staticmethod def _get_read_widget(attribute: AttrR) -> ReadWidgetUnion | None: @@ -80,7 +79,7 @@ def _get_attribute_component( self, attr_path: list[str], name: str, attribute: Attribute ) -> SignalR | SignalW | SignalRW | None: pv = self._get_pv(attr_path, name) - name = _snake_to_pascal(name) + name = snake_to_pascal(name) match attribute: case AttrRW(): read_widget = self._get_read_widget(attribute) @@ -109,7 +108,7 @@ def _get_attribute_component( def _get_command_component(self, attr_path: list[str], name: str): pv = self._get_pv(attr_path, name) - name = _snake_to_pascal(name) + name = snake_to_pascal(name) return SignalX( name=name, diff --git a/src/fastcs/transport/epics/pva/ioc.py b/src/fastcs/transport/epics/pva/ioc.py index 32b29db09..8d9919712 100644 --- a/src/fastcs/transport/epics/pva/ioc.py +++ b/src/fastcs/transport/epics/pva/ioc.py @@ -4,7 +4,7 @@ from fastcs.attributes import Attribute, AttrR, AttrRW, AttrW from fastcs.controller_api import ControllerAPI -from fastcs.transport.epics.util import _snake_to_pascal +from fastcs.util import snake_to_pascal from ._pv_handlers import make_command_pv, make_shared_pv from .pvi_tree import AccessModeType, PviTree @@ -24,7 +24,7 @@ def _attribute_to_access(attribute: Attribute) -> AccessModeType: def get_pv_name(pv_prefix: str, *attribute_names: str) -> str: """Converts from an attribute name to a pv name.""" - pv_formatted = ":".join([_snake_to_pascal(attr) for attr in attribute_names]) + pv_formatted = ":".join([snake_to_pascal(attr) for attr in attribute_names]) return f"{pv_prefix}:{pv_formatted}" if pv_formatted else pv_prefix diff --git a/src/fastcs/transport/epics/util.py b/src/fastcs/transport/epics/util.py deleted file mode 100644 index 4bd1d09c9..000000000 --- a/src/fastcs/transport/epics/util.py +++ /dev/null @@ -1,10 +0,0 @@ -import re - - -def _snake_to_pascal(name: str) -> str: - name = ( - re.sub(r"(?:^|_)([a-z])", lambda match: match.group(1).upper(), name.title()) - .replace("_", "") - .replace("-", "") - ) - return re.sub(r"_(\d+)$", r"\1", name) diff --git a/src/fastcs/util.py b/src/fastcs/util.py index b8c7f1cd3..f088e24c8 100644 --- a/src/fastcs/util.py +++ b/src/fastcs/util.py @@ -1,5 +1,9 @@ +import re + + def snake_to_pascal(input: str) -> str: + # for part in re.split(r'[-_]', input): """Convert a snake_case string to PascalCase.""" return "".join( - part.title() if part.islower() else part for part in input.split("_") + part.title() if part.islower() else part for part in re.split(r"[_-]", input) ) diff --git a/tests/test_util.py b/tests/test_util.py new file mode 100644 index 000000000..7ae4ffbd1 --- /dev/null +++ b/tests/test_util.py @@ -0,0 +1,28 @@ +import pytest +from pvi.device import SignalR +from pydantic import ValidationError + +from fastcs.util import snake_to_pascal + + +def test_snake_to_pascal(): + name1 = "a-b-c-d-e" + name2 = "a_b_c_d_e" + name3 = "name_with-different_separators" + name4 = "1_2_3-a-b-c" + name5 = "NameAlreadyInPascalCase" + name6 = "Name-With_%_Invalid-&-Symbols_£_" + name7 = "123A_b-C-d-e_" + assert snake_to_pascal(name1) == "ABCDE" + assert snake_to_pascal(name2) == "ABCDE" + assert snake_to_pascal(name3) == "NameWithDifferentSeparators" + assert snake_to_pascal(name4) == "123ABC" + assert snake_to_pascal(name5) == "NameAlreadyInPascalCase" + assert snake_to_pascal(name6) == "NameWith%Invalid&Symbols£" + assert snake_to_pascal(name7) == "123ABCDE" + + +def test_pvi__validation_error(): + name = snake_to_pascal("Name-With_%_Invalid-&-Symbols_£_") + with pytest.raises(ValidationError): + SignalR(name=name, read_pv="test") From d8f3935368a5756603cc2eabc747114f580b4722 Mon Sep 17 00:00:00 2001 From: Luis Segalla Date: Thu, 27 Mar 2025 13:46:02 +0000 Subject: [PATCH 04/10] Reverted snake_to_pascal function Reverted to using regex logic on snake_to_pascal function and only handle "_" character --- src/fastcs/util.py | 11 +++++------ tests/test_util.py | 12 ++++++------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/fastcs/util.py b/src/fastcs/util.py index f088e24c8..70e92586f 100644 --- a/src/fastcs/util.py +++ b/src/fastcs/util.py @@ -1,9 +1,8 @@ import re -def snake_to_pascal(input: str) -> str: - # for part in re.split(r'[-_]', input): - """Convert a snake_case string to PascalCase.""" - return "".join( - part.title() if part.islower() else part for part in re.split(r"[_-]", input) - ) +def snake_to_pascal(name: str) -> str: + name = re.sub( + r"(?:^|_)([a-z])", lambda match: match.group(1).upper(), name + ).replace("_", "") + return re.sub(r"_(\d+)$", r"\1", name) diff --git a/tests/test_util.py b/tests/test_util.py index 7ae4ffbd1..f47e00b39 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -12,14 +12,14 @@ def test_snake_to_pascal(): name4 = "1_2_3-a-b-c" name5 = "NameAlreadyInPascalCase" name6 = "Name-With_%_Invalid-&-Symbols_£_" - name7 = "123A_b-C-d-e_" - assert snake_to_pascal(name1) == "ABCDE" + name7 = "name_in_lower_case" + assert snake_to_pascal(name1) == "A-b-c-d-e" assert snake_to_pascal(name2) == "ABCDE" - assert snake_to_pascal(name3) == "NameWithDifferentSeparators" - assert snake_to_pascal(name4) == "123ABC" + assert snake_to_pascal(name3) == "NameWith-differentSeparators" + assert snake_to_pascal(name4) == "123-a-b-c" assert snake_to_pascal(name5) == "NameAlreadyInPascalCase" - assert snake_to_pascal(name6) == "NameWith%Invalid&Symbols£" - assert snake_to_pascal(name7) == "123ABCDE" + assert snake_to_pascal(name6) == "Name-With%Invalid-&-Symbols£" + assert snake_to_pascal(name7) == "NameInLowerCase" def test_pvi__validation_error(): From 881d008afe35183c4cd5c27718e8b2c843ea7926 Mon Sep 17 00:00:00 2001 From: Luis Segalla Date: Mon, 31 Mar 2025 14:47:32 +0100 Subject: [PATCH 05/10] Reverting tests back --- tests/transport/epics/pva/test_p4p.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/transport/epics/pva/test_p4p.py b/tests/transport/epics/pva/test_p4p.py index 8cc6f5aad..bd98c4a80 100644 --- a/tests/transport/epics/pva/test_p4p.py +++ b/tests/transport/epics/pva/test_p4p.py @@ -297,10 +297,10 @@ class SomeController(Controller): sub_controller = ChildController() controller.register_sub_controller("Child0", sub_controller) - sub_controller.register_sub_controller("Child_Child", ChildChildController()) + sub_controller.register_sub_controller("ChildChild", ChildChildController()) sub_controller = ChildController() controller.register_sub_controller("Child1", sub_controller) - sub_controller.register_sub_controller("Child_Child", ChildChildController()) + sub_controller.register_sub_controller("ChildChild", ChildChildController()) sub_controller = ChildController() controller.register_sub_controller("Child2", sub_controller) sub_controller.register_sub_controller("ChildChild", ChildChildController()) @@ -308,7 +308,7 @@ class SomeController(Controller): controller.register_sub_controller("another_child", sub_controller) sub_controller.register_sub_controller("ChildChild", ChildChildController()) sub_controller = ChildController() - controller.register_sub_controller("additional_child", sub_controller) + controller.register_sub_controller("AdditionalChild", sub_controller) sub_controller.register_sub_controller("ChildChild", ChildChildController()) sub_controller = ChildController() controller.register_sub_controller("child_attribute_same_name", sub_controller) From 30e8ef4f97968912db381455d77f02448cc85c05 Mon Sep 17 00:00:00 2001 From: Luis Segalla Date: Fri, 4 Apr 2025 09:03:10 +0100 Subject: [PATCH 06/10] PR comments --- src/fastcs/util.py | 3 +++ tests/test_util.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/fastcs/util.py b/src/fastcs/util.py index 70e92586f..ae5847ba5 100644 --- a/src/fastcs/util.py +++ b/src/fastcs/util.py @@ -2,6 +2,9 @@ def snake_to_pascal(name: str) -> str: + """Converts string from snake case to Pascal case. + If string already in Pascal case it's returned unchanged + """ name = re.sub( r"(?:^|_)([a-z])", lambda match: match.group(1).upper(), name ).replace("_", "") diff --git a/tests/test_util.py b/tests/test_util.py index f47e00b39..7d4cd6935 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -22,7 +22,7 @@ def test_snake_to_pascal(): assert snake_to_pascal(name7) == "NameInLowerCase" -def test_pvi__validation_error(): +def test_pvi_validation_error(): name = snake_to_pascal("Name-With_%_Invalid-&-Symbols_£_") with pytest.raises(ValidationError): SignalR(name=name, read_pv="test") From c5143eedf87d807a6537d878cca2c1787c5585b3 Mon Sep 17 00:00:00 2001 From: Luis Segalla Date: Mon, 7 Apr 2025 15:25:28 +0100 Subject: [PATCH 07/10] PR comments Made function only convert strings that are valid snake case and removed redundant code parts. --- src/fastcs/util.py | 9 +++++---- tests/test_util.py | 30 +++++++++++++++++------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/fastcs/util.py b/src/fastcs/util.py index ae5847ba5..ffea6025c 100644 --- a/src/fastcs/util.py +++ b/src/fastcs/util.py @@ -5,7 +5,8 @@ def snake_to_pascal(name: str) -> str: """Converts string from snake case to Pascal case. If string already in Pascal case it's returned unchanged """ - name = re.sub( - r"(?:^|_)([a-z])", lambda match: match.group(1).upper(), name - ).replace("_", "") - return re.sub(r"_(\d+)$", r"\1", name) + if re.fullmatch(r"[a-z]+(?:_[a-z]+|_[0-9]+)*", name): + name = re.sub( + r"(?:^|_)([a-z]|[0-9])", lambda match: match.group(1).upper(), name + ) + return name diff --git a/tests/test_util.py b/tests/test_util.py index 7d4cd6935..27e5902ad 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -6,20 +6,24 @@ def test_snake_to_pascal(): - name1 = "a-b-c-d-e" - name2 = "a_b_c_d_e" + name1 = "name_in_snake_case" + name2 = "name-not-in-snake-case" name3 = "name_with-different_separators" - name4 = "1_2_3-a-b-c" - name5 = "NameAlreadyInPascalCase" - name6 = "Name-With_%_Invalid-&-Symbols_£_" - name7 = "name_in_lower_case" - assert snake_to_pascal(name1) == "A-b-c-d-e" - assert snake_to_pascal(name2) == "ABCDE" - assert snake_to_pascal(name3) == "NameWith-differentSeparators" - assert snake_to_pascal(name4) == "123-a-b-c" - assert snake_to_pascal(name5) == "NameAlreadyInPascalCase" - assert snake_to_pascal(name6) == "Name-With%Invalid-&-Symbols£" - assert snake_to_pascal(name7) == "NameInLowerCase" + name4 = "name_with_numbers_1_2_3" + name5 = "numbers_1_2_3_in_the_middle" + name6 = "1_2_3_starting_with_numbers" + name7 = "Name_With_%_Invalid_&_Symbols_£_" + name8 = "name_in_lower_case" + name9 = "NameAlreadyInPascalCase" + assert snake_to_pascal(name1) == "NameInSnakeCase" + assert snake_to_pascal(name2) == "name-not-in-snake-case" + assert snake_to_pascal(name3) == "name_with-different_separators" + assert snake_to_pascal(name4) == "NameWithNumbers123" + assert snake_to_pascal(name5) == "Numbers123InTheMiddle" + assert snake_to_pascal(name6) == "1_2_3_starting_with_numbers" + assert snake_to_pascal(name7) == "Name_With_%_Invalid_&_Symbols_£_" + assert snake_to_pascal(name8) == "NameInLowerCase" + assert snake_to_pascal(name9) == "NameAlreadyInPascalCase" def test_pvi_validation_error(): From c75aec6e2c7a8cd5ace4b827ff6575ef892f98a9 Mon Sep 17 00:00:00 2001 From: Luis Segalla Date: Tue, 8 Apr 2025 09:16:57 +0100 Subject: [PATCH 08/10] Updated function description --- src/fastcs/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fastcs/util.py b/src/fastcs/util.py index ffea6025c..bc5fa33c1 100644 --- a/src/fastcs/util.py +++ b/src/fastcs/util.py @@ -3,7 +3,7 @@ def snake_to_pascal(name: str) -> str: """Converts string from snake case to Pascal case. - If string already in Pascal case it's returned unchanged + If string is not a valid snake case it will be returned unchanged """ if re.fullmatch(r"[a-z]+(?:_[a-z]+|_[0-9]+)*", name): name = re.sub( From d17abbbf3c0c7cd1bd3a4e9fffa39f1925808834 Mon Sep 17 00:00:00 2001 From: Luis Segalla Date: Tue, 8 Apr 2025 09:45:43 +0100 Subject: [PATCH 09/10] PR comments --- src/fastcs/util.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/fastcs/util.py b/src/fastcs/util.py index bc5fa33c1..818f8a242 100644 --- a/src/fastcs/util.py +++ b/src/fastcs/util.py @@ -5,8 +5,6 @@ def snake_to_pascal(name: str) -> str: """Converts string from snake case to Pascal case. If string is not a valid snake case it will be returned unchanged """ - if re.fullmatch(r"[a-z]+(?:_[a-z]+|_[0-9]+)*", name): - name = re.sub( - r"(?:^|_)([a-z]|[0-9])", lambda match: match.group(1).upper(), name - ) + if re.fullmatch(r"[a-z]+(?:_[a-z0-9]+)*", name): + name = re.sub(r"(?:^|_)([a-z0-9])", lambda match: match.group(1).upper(), name) return name From 5e5932208e21d24ebffd969557fdd2d7810a4316 Mon Sep 17 00:00:00 2001 From: Luis Segalla Date: Fri, 23 May 2025 10:52:45 +0100 Subject: [PATCH 10/10] Updated regex and tests --- src/fastcs/util.py | 2 +- tests/test_util.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/fastcs/util.py b/src/fastcs/util.py index 818f8a242..1ec8c3925 100644 --- a/src/fastcs/util.py +++ b/src/fastcs/util.py @@ -5,6 +5,6 @@ def snake_to_pascal(name: str) -> str: """Converts string from snake case to Pascal case. If string is not a valid snake case it will be returned unchanged """ - if re.fullmatch(r"[a-z]+(?:_[a-z0-9]+)*", name): + if re.fullmatch(r"[a-z][a-z0-9]*(?:_[a-z0-9]+)*", name): name = re.sub(r"(?:^|_)([a-z0-9])", lambda match: match.group(1).upper(), name) return name diff --git a/tests/test_util.py b/tests/test_util.py index 27e5902ad..c60711386 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -12,18 +12,24 @@ def test_snake_to_pascal(): name4 = "name_with_numbers_1_2_3" name5 = "numbers_1_2_3_in_the_middle" name6 = "1_2_3_starting_with_numbers" - name7 = "Name_With_%_Invalid_&_Symbols_£_" + name7 = "name1_with2_a3_number4" name8 = "name_in_lower_case" name9 = "NameAlreadyInPascalCase" + name10 = "Name_With_%_Invalid_&_Symbols_£_" + name11 = "a_b_c_d" + name12 = "test" assert snake_to_pascal(name1) == "NameInSnakeCase" assert snake_to_pascal(name2) == "name-not-in-snake-case" assert snake_to_pascal(name3) == "name_with-different_separators" assert snake_to_pascal(name4) == "NameWithNumbers123" assert snake_to_pascal(name5) == "Numbers123InTheMiddle" assert snake_to_pascal(name6) == "1_2_3_starting_with_numbers" - assert snake_to_pascal(name7) == "Name_With_%_Invalid_&_Symbols_£_" + assert snake_to_pascal(name7) == "Name1With2A3Number4" assert snake_to_pascal(name8) == "NameInLowerCase" assert snake_to_pascal(name9) == "NameAlreadyInPascalCase" + assert snake_to_pascal(name10) == "Name_With_%_Invalid_&_Symbols_£_" + assert snake_to_pascal(name11) == "ABCD" + assert snake_to_pascal(name12) == "Test" def test_pvi_validation_error():