diff --git a/CHANGELOG.md b/CHANGELOG.md index bf289c5b23..688a5c7ee8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). +## [UNRELEASED] + +## Fixed + +- [#3239](https://github.com/plotly/dash/pull/3239) Remove stringcase dependency, fix [#3238](https://github.com/plotly/dash/issues/3238) + ## [3.0.0] - 2025-03-17 ## Added diff --git a/dash/_utils.py b/dash/_utils.py index 38ff0c39ac..f1056d0130 100644 --- a/dash/_utils.py +++ b/dash/_utils.py @@ -11,6 +11,8 @@ import secrets import string import inspect +import re + from html import escape from functools import wraps from typing import Union @@ -302,3 +304,14 @@ def get_caller_name(): return s.frame.f_locals.get("__name__", "__main__") return "__main__" + + +def pascal_case(name: Union[str, None]): + s = re.sub(r"\s", "_", str(name)) + # Replace leading `_` + s = re.sub("^[_]+", "", s) + if not s: + return s + return s[0].upper() + re.sub( + r"[\-_\.]+([a-z])", lambda match: match.group(1).upper(), s[1:] + ) diff --git a/dash/development/_py_prop_typing.py b/dash/development/_py_prop_typing.py index fcd4c58961..1c3f673977 100644 --- a/dash/development/_py_prop_typing.py +++ b/dash/development/_py_prop_typing.py @@ -4,7 +4,7 @@ import textwrap import importlib -import stringcase +from .._utils import pascal_case shapes = {} @@ -54,7 +54,7 @@ def generate_any(*_): def generate_shape(type_info, component_name: str, prop_name: str): props = [] - name = stringcase.pascalcase(prop_name) + name = pascal_case(prop_name) for prop_key, prop_type in type_info["value"].items(): typed = get_prop_typing( diff --git a/dash/py.typed b/dash/py.typed index e69de29bb2..b648ac9233 100644 --- a/dash/py.typed +++ b/dash/py.typed @@ -0,0 +1 @@ +partial diff --git a/requirements/install.txt b/requirements/install.txt index 8a02fa781a..65fccc279d 100644 --- a/requirements/install.txt +++ b/requirements/install.txt @@ -7,4 +7,3 @@ requests retrying nest-asyncio setuptools -stringcase>=1.2.0 diff --git a/tests/unit/library/test_utils.py b/tests/unit/library/test_utils.py index f643442dd4..cb677e8355 100644 --- a/tests/unit/library/test_utils.py +++ b/tests/unit/library/test_utils.py @@ -58,3 +58,19 @@ def test_ddut001_attribute_dict(): a.x = 4 assert err.value.args == ("Object is final: No new keys may be added.", "x") assert "x" not in a + + +@pytest.mark.parametrize( + "value,expected", + [ + ("foo_bar", "FooBar"), + ("", ""), + ("fooBarFoo", "FooBarFoo"), + ("foo bar", "FooBar"), + ("foo-bar", "FooBar"), + ("__private_prop", "PrivateProp"), + ("double__middle___triple", "DoubleMiddleTriple"), + ], +) +def test_ddut002_pascal_case(value, expected): + assert utils.pascal_case(value) == expected