-
Notifications
You must be signed in to change notification settings - Fork 145
chore: improve token string representations #1307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
tech0priyanshu
wants to merge
6
commits into
hiero-ledger:main
Choose a base branch
from
tech0priyanshu:feat/auto-str
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+615
−4
Draft
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| """ | ||
| hiero_sdk_python.utils.dataclass_strings | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| Provides automatic __str__ and __repr__ generation for dataclasses. | ||
|
|
||
| This module eliminates manual maintenance of string methods by dynamically | ||
| generating them based on dataclass fields. When new fields are added, they | ||
| are automatically included in the string representation. | ||
|
|
||
| Usage: | ||
| @dataclass | ||
| class MyTokenClass(DataclassStringMixin): | ||
| field1: str | ||
| field2: Optional[int] = None | ||
|
|
||
| # Or use the decorator approach: | ||
| @auto_str_repr | ||
| @dataclass | ||
| class MyTokenClass: | ||
| field1: str | ||
| field2: Optional[int] = None | ||
| """ | ||
|
|
||
| import dataclasses | ||
| import enum | ||
| from typing import Any | ||
|
|
||
|
|
||
| def _is_enum(value: Any) -> bool: | ||
| """Check if a value is an Enum instance.""" | ||
| return isinstance(value, enum.Enum) | ||
|
|
||
|
|
||
| def _format_bytes(value: bytes) -> str: | ||
| """Format bytes value with truncation for display.""" | ||
| if len(value) > 20: | ||
| return f"b'{value[:20].hex()}...'" | ||
| return f"b'{value.hex()}'" | ||
|
|
||
|
|
||
| def _format_value_helper(value: Any) -> str: | ||
| """Format a field value for string representation.""" | ||
| if value is None: | ||
| return "None" | ||
|
|
||
| if isinstance(value, str): | ||
| return f"'{value}'" | ||
|
|
||
| if isinstance(value, bytes): | ||
| return _format_bytes(value) | ||
|
|
||
| if _is_enum(value): | ||
| return f"{value.__class__.__name__}.{value.name}" | ||
|
|
||
| return str(value) | ||
|
|
||
|
|
||
| class DataclassStringMixin: | ||
| """ | ||
| Mixin class that provides automatic __str__ and __repr__ implementations | ||
| for dataclasses based on their fields. | ||
|
|
||
| This mixin automatically generates string representations that include | ||
| all dataclass fields, handling None values and nested objects appropriately. | ||
|
|
||
| Features: | ||
| - Zero maintenance: new fields are automatically included | ||
| - Proper None handling for optional fields | ||
| - Clean formatting (multi-line for >3 fields) | ||
| - Enum value formatting (shows EnumClass.VALUE) | ||
| - String quoting for clarity | ||
| - Dictionary conversion for serialization | ||
|
|
||
| Example: | ||
| >>> @dataclass | ||
| ... class TokenInfo(DataclassStringMixin): | ||
| ... token_id: str | ||
| ... symbol: Optional[str] = None | ||
| ... balance: int = 0 | ||
| ... | ||
| >>> token = TokenInfo("0.0.123", "TEST", 1000) | ||
| >>> str(token) | ||
| "TokenInfo(token_id='0.0.123', symbol='TEST', balance=1000)" | ||
| """ | ||
|
|
||
| def __str__(self) -> str: | ||
| """Generate string representation dynamically from dataclass fields.""" | ||
| if not dataclasses.is_dataclass(self): | ||
| return f"{self.__class__.__name__}()" | ||
|
|
||
| field_strings = [] | ||
| for field in dataclasses.fields(self.__class__): | ||
| field_value = getattr(self, field.name) | ||
| formatted_value = self._format_field_value(field_value) | ||
| field_strings.append(f"{field.name}={formatted_value}") | ||
|
|
||
| # Choose formatting based on number of fields | ||
| class_name = self.__class__.__name__ | ||
| if len(field_strings) <= 3: | ||
| return f"{class_name}({', '.join(field_strings)})" | ||
| else: | ||
| fields_str = ",\n ".join(field_strings) | ||
| return f"{class_name}(\n {fields_str}\n)" | ||
|
|
||
| def __repr__(self) -> str: | ||
| """Return string representation for debugging.""" | ||
| return self.__str__() | ||
|
|
||
| def _format_field_value(self, value: Any) -> str: | ||
| """Format a field value for string representation.""" | ||
| return _format_value_helper(value) | ||
|
|
||
| def to_dict(self) -> dict[str, Any]: | ||
| """Convert dataclass to dictionary for serialization. | ||
|
|
||
| Returns: | ||
| dict[str, Any]: Dictionary representation of the dataclass. | ||
| """ | ||
| if not dataclasses.is_dataclass(self): | ||
| return {} | ||
|
|
||
| result = {} | ||
| for field in dataclasses.fields(self.__class__): | ||
| value = getattr(self, field.name) | ||
| if hasattr(value, 'to_dict'): | ||
| result[field.name] = value.to_dict() | ||
| elif dataclasses.is_dataclass(value) and not isinstance(value, type): | ||
| result[field.name] = dataclasses.asdict(value) | ||
| else: | ||
| result[field.name] = value | ||
| return result | ||
|
|
||
|
|
||
| def auto_str_repr(cls): | ||
| """ | ||
| Class decorator that automatically adds __str__ and __repr__ methods | ||
| to dataclasses using dynamic field introspection. | ||
|
|
||
| This decorator is an alternative to using DataclassStringMixin inheritance. | ||
|
|
||
| Usage: | ||
| @auto_str_repr | ||
| @dataclass | ||
| class TokenClass: | ||
| field1: str | ||
| field2: Optional[int] = None | ||
|
|
||
| Args: | ||
| cls: The dataclass to decorate. | ||
|
|
||
| Returns: | ||
| The decorated class with __str__ and __repr__ methods. | ||
|
|
||
| Raises: | ||
| TypeError: If the decorated class is not a dataclass. | ||
| """ | ||
| if not dataclasses.is_dataclass(cls): | ||
| raise TypeError(f"@auto_str_repr can only be applied to dataclasses, got {cls.__name__}") | ||
|
|
||
| def _format_value(value: Any) -> str: | ||
| """Format a field value for string representation.""" | ||
| return _format_value_helper(value) | ||
|
|
||
| def __str__(self) -> str: | ||
| """Generate string representation dynamically from dataclass fields.""" | ||
| field_strings = [] | ||
| for field in dataclasses.fields(self.__class__): | ||
| field_value = getattr(self, field.name) | ||
| formatted_value = _format_value(field_value) | ||
| field_strings.append(f"{field.name}={formatted_value}") | ||
|
|
||
| if len(field_strings) <= 3: | ||
| return f"{cls.__name__}({', '.join(field_strings)})" | ||
| else: | ||
| fields_str = ",\n ".join(field_strings) | ||
| return f"{cls.__name__}(\n {fields_str}\n)" | ||
|
|
||
| def __repr__(self) -> str: | ||
| """Return string representation for debugging.""" | ||
| return self.__str__() | ||
|
|
||
| cls.__str__ = __str__ | ||
| cls.__repr__ = __repr__ | ||
|
|
||
| return cls | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
_format_valuefunction duplicates logic fromDataclassStringMixin._format_field_value. Consider extracting this shared formatting logic into a standalone function to reduce duplication and improve maintainability.