haiway 0.26.0__py3-none-any.whl → 0.27.0__py3-none-any.whl

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.
@@ -1,14 +1,18 @@
1
- from collections.abc import ItemsView, Mapping, Sequence
1
+ from collections.abc import ItemsView, Mapping, Sequence, Set
2
+ from datetime import datetime
2
3
  from typing import Any
4
+ from uuid import UUID
3
5
 
4
6
  from haiway.types.missing import MISSING
5
7
 
6
8
  __all__ = ("format_str",)
7
9
 
8
10
 
9
- def format_str( # noqa: PLR0911
11
+ def format_str( # noqa: PLR0911 PLR0912 C901
10
12
  value: Any,
11
13
  /,
14
+ *,
15
+ indent: int = 0,
12
16
  ) -> str:
13
17
  """
14
18
  Format any Python value into a readable string representation.
@@ -37,108 +41,153 @@ def format_str( # noqa: PLR0911
37
41
  - MISSING values are converted to empty strings
38
42
  - Nested structures maintain proper indentation
39
43
  """
40
- # check for string
41
- if isinstance(value, str):
44
+ if value is None:
45
+ return "None"
46
+
47
+ elif isinstance(value, str):
42
48
  if "\n" in value:
43
- return f'"""\n{value.replace("\n", "\n ")}\n"""'
49
+ indent_str = " " * (indent + 2)
50
+ indented_value = value.replace("\n", f"\n{indent_str}")
51
+ return f'"""\n{indent_str}{indented_value}\n{" " * indent}"""'
44
52
 
45
53
  else:
46
54
  return f'"{value}"'
47
55
 
48
- # check for bytes
49
- elif isinstance(value, bytes):
50
- return f"b'{value!r}'"
56
+ elif isinstance(value, int | float | complex):
57
+ return str(value)
58
+
59
+ elif isinstance(value, bool):
60
+ return str(value)
61
+
62
+ elif isinstance(value, set | frozenset | Set):
63
+ return _set_str(
64
+ value,
65
+ indent=indent,
66
+ )
51
67
 
52
- # try unpack mapping
53
68
  elif isinstance(value, Mapping):
54
- return _mapping_str(value)
69
+ return _mapping_str(
70
+ value,
71
+ indent=indent,
72
+ )
55
73
 
56
- # try unpack sequence
57
74
  elif isinstance(value, Sequence):
58
- return _sequence_str(value)
75
+ return _sequence_str(
76
+ value,
77
+ indent=indent,
78
+ )
59
79
 
60
80
  elif value is MISSING:
61
81
  return ""
62
82
 
83
+ elif isinstance(value, UUID):
84
+ return str(value)
85
+
86
+ elif isinstance(value, datetime):
87
+ return value.isoformat()
88
+
89
+ elif isinstance(value, bytes):
90
+ return repr(value)
91
+
63
92
  else: # fallback to object
64
- return _object_str(value)
93
+ return _object_str(
94
+ value,
95
+ indent=indent,
96
+ )
65
97
 
66
98
 
67
99
  def _attribute_str(
68
100
  *,
69
101
  key: str,
70
102
  value: str,
103
+ indent: int,
71
104
  ) -> str:
105
+ indent_str = " " * indent
72
106
  if "\n" in value:
73
- formatted_value: str = value.replace("\n", "\n| ")
74
- return f"┝ {key}:\n{formatted_value}"
107
+ # Don't add extra indentation - value should already handle it
108
+ return f"{indent_str}┝ {key}:\n{value}"
75
109
 
76
110
  else:
77
- return f"┝ {key}: {value}"
111
+ return f"{indent_str}┝ {key}: {value}"
78
112
 
79
113
 
80
114
  def _element_str(
81
115
  *,
82
116
  key: Any,
83
- value: Any,
117
+ value: str,
118
+ indent: int,
84
119
  ) -> str:
120
+ indent_str = " " * indent
85
121
  if "\n" in value:
86
- formatted_value: str = value.replace("\n", "\n ")
87
- return f"[{key}]:\n{formatted_value}"
122
+ # Don't add extra indentation - value should already handle it
123
+ return f"{indent_str}[{key}]:\n{value}"
88
124
 
89
125
  else:
90
- return f"[{key}]: {value}"
126
+ return f"{indent_str}[{key}]: {value}"
91
127
 
92
128
 
93
129
  def _object_str(
94
130
  other: object,
95
131
  /,
132
+ *,
133
+ indent: int,
96
134
  ) -> str:
135
+ indent_str: str = " " * indent
97
136
  if not hasattr(other, "__dict__"):
98
- return str(other)
137
+ return f"{indent_str}{other}"
99
138
 
100
139
  variables: ItemsView[str, Any] = vars(other).items()
101
-
102
- parts: list[str] = [f"┍━ {type(other).__name__}:"]
140
+ header = f"{indent_str}┍━ {type(other).__name__}:"
141
+ parts: list[str] = [header]
103
142
  for key, value in variables:
104
143
  if key.startswith("_"):
105
144
  continue # skip private and dunder
106
145
 
107
- value_string: str = format_str(value)
146
+ value_string: str = format_str(
147
+ value,
148
+ indent=indent + 2,
149
+ )
108
150
 
109
151
  if value_string:
110
152
  parts.append(
111
153
  _attribute_str(
112
154
  key=key,
113
155
  value=value_string,
156
+ indent=indent,
114
157
  )
115
158
  )
116
159
 
117
160
  else:
118
161
  continue # skip empty elements
119
162
 
120
- if parts:
121
- return "\n".join(parts) + "\n┕━"
122
-
123
- else:
124
- return ""
163
+ return "\n".join(parts) + f"\n{indent_str}┕━"
125
164
 
126
165
 
127
166
  def _mapping_str(
128
167
  mapping: Mapping[Any, Any],
129
168
  /,
169
+ *,
170
+ indent: int,
130
171
  ) -> str:
131
172
  items: ItemsView[Any, Any] = mapping.items()
132
173
 
174
+ indent_str = " " * indent
133
175
  parts: list[str] = []
134
176
  for key, value in items:
135
- value_string: str = format_str(value)
177
+ value_string: str = format_str(
178
+ value,
179
+ indent=indent + 2,
180
+ )
136
181
 
137
182
  if value_string:
138
183
  parts.append(
139
184
  _element_str(
140
- key=key,
185
+ key=format_str(
186
+ key,
187
+ indent=indent + 2,
188
+ ),
141
189
  value=value_string,
190
+ indent=indent + 2,
142
191
  )
143
192
  )
144
193
 
@@ -146,25 +195,64 @@ def _mapping_str(
146
195
  continue # skip empty items
147
196
 
148
197
  if parts:
149
- return "{\n" + "\n".join(parts) + "\n}"
198
+ open_brace = "{\n" if indent == 0 else f"{indent_str}{{\n"
199
+ close_brace = "\n}" if indent == 0 else f"\n{indent_str}}}"
200
+ return open_brace + "\n".join(parts) + close_brace
150
201
 
151
202
  else:
152
- return "{}"
203
+ return "{}" if indent == 0 else f"{indent_str}{{}}"
204
+
205
+
206
+ def _set_str(
207
+ set_value: Set[Any] | set[Any] | frozenset[Any],
208
+ /,
209
+ *,
210
+ indent: int,
211
+ ) -> str:
212
+ indent_str: str = " " * indent
213
+ element_indent_str: str = " " * (indent + 2)
214
+ parts: list[str] = []
215
+ for element in set_value:
216
+ element_string: str = format_str(
217
+ element,
218
+ indent=indent + 2,
219
+ )
220
+
221
+ if element_string:
222
+ parts.append(f"{element_indent_str}{element_string}")
223
+
224
+ else:
225
+ continue # skip empty elements
226
+
227
+ if parts:
228
+ open_brace: str = f"{indent_str}{{\n"
229
+ close_brace: str = f"\n{indent_str}}}"
230
+ return open_brace + ",\n".join(parts) + close_brace
231
+
232
+ else:
233
+ return f"{indent_str}{{}}"
153
234
 
154
235
 
155
236
  def _sequence_str(
156
237
  sequence: Sequence[Any],
157
238
  /,
239
+ *,
240
+ indent: int,
158
241
  ) -> str:
242
+ indent_str: str = " " * indent
159
243
  parts: list[str] = []
160
244
  for idx, element in enumerate(sequence):
161
- element_string: str = format_str(element)
245
+ element_string: str = format_str(
246
+ element,
247
+ indent=indent + 2,
248
+ )
162
249
 
163
250
  if element_string:
164
251
  parts.append(
165
252
  _element_str(
166
253
  key=idx,
167
254
  value=element_string,
255
+ indent=indent + 2,
168
256
  )
169
257
  )
170
258
 
@@ -172,7 +260,9 @@ def _sequence_str(
172
260
  continue # skip empty elements
173
261
 
174
262
  if parts:
175
- return "[\n" + "\n".join(parts) + "\n]"
263
+ open_bracket: str = f"{indent_str}[\n"
264
+ close_bracket: str = f"\n{indent_str}]"
265
+ return open_bracket + "\n".join(parts) + close_bracket
176
266
 
177
267
  else:
178
- return "[]"
268
+ return f"{indent_str}[]"