| Conditions | 16 |
| Total Lines | 59 |
| Code Lines | 25 |
| Lines | 0 |
| Ratio | 0 % |
| Changes | 0 | ||
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like pocketutils.tools.json_tools.JsonTools.preserve_inf() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | import base64 |
||
| 193 | @classmethod |
||
| 194 | def preserve_inf(cls: type[Self], data: Any) -> Any: |
||
| 195 | """ |
||
| 196 | Recursively replaces infinite float and numpy values with strings. |
||
| 197 | Orjson encodes NaN, inf, and +inf as JSON null. |
||
| 198 | This function converts to string as needed to preserve infinite values. |
||
| 199 | Any float scalar (`np.floating` and `float`) will be replaced with a string. |
||
| 200 | Any `np.ndarray`, whether it contains an infinite value or not, will be converted |
||
| 201 | to an ndarray of strings. |
||
| 202 | The returned result may still not be serializable with orjson or :meth:`orjson_bytes`. |
||
| 203 | Trying those methods is the best way to test for serializablity. |
||
| 204 | """ |
||
| 205 | # we go to great lengths to avoid importing numpy |
||
| 206 | # no np.isinf, np.isneginf, or np.isnan allowed |
||
| 207 | # we can use the fact that Numpy float types compare to float, |
||
| 208 | # including to -inf and +inf, where all comparisons between Inf/-Inf and NaN are False |
||
| 209 | # So our logic is is_infinite := (data > NEG_INF) != (data < INF) |
||
| 210 | # Meanwhile, we only need to deal with floats: |
||
| 211 | # - int and bool stay as-is |
||
| 212 | # - str stays as-is |
||
| 213 | # - complex gets converted to |
||
| 214 | if isinstance(data, Mapping): |
||
| 215 | return {str(k): cls.preserve_inf(v) for k, v in data.items()} |
||
| 216 | elif ( |
||
| 217 | (isinstance(data, Sequence) or type(data).__name__ == "ndarray") |
||
| 218 | and not isinstance(data, str) |
||
| 219 | and not isinstance(data, ByteString) |
||
| 220 | ): |
||
| 221 | is_np_float_array = hasattr(data, "dtype") and str(data.dtype).startswith( |
||
| 222 | "dtype(float", |
||
| 223 | ) |
||
| 224 | if ( |
||
| 225 | is_np_float_array |
||
| 226 | or all(isinstance(v, float) for v in data) |
||
| 227 | and all((v > NEG_INF) != (v < INF) for v in data) |
||
| 228 | ): |
||
| 229 | # it's a list or array of floats containing -Inf or +Inf |
||
| 230 | # ==> convert to str to preserve |
||
| 231 | return [str(v) for v in data] |
||
| 232 | elif is_np_float_array: |
||
| 233 | # it's an array of other types, or of floats containing neither -Inf nor +Inf |
||
| 234 | # ==> convert to list (faster than recursing) |
||
| 235 | # noinspection PyUnresolvedReferences |
||
| 236 | return data.tolist() |
||
| 237 | else: |
||
| 238 | # it's an array of other types, or of floats containing neither -Inf nor +Inf |
||
| 239 | # ==> return float list as-is |
||
| 240 | return data |
||
| 241 | elif (isinstance(data, float) or (hasattr(data, "dtype") and str(data.dtype).startswith("dtype(float"))) and ( |
||
| 242 | data > NEG_INF |
||
| 243 | ) != (data < INF): |
||
| 244 | # single float value with -Inf or +Inf |
||
| 245 | # ==> preserve inf |
||
| 246 | return str(data) |
||
| 247 | elif type(data).__name__ == "ndarray" and hasattr(data, "dtype"): |
||
| 248 | # it's a non-float Numpy array |
||
| 249 | # ==> convert to list |
||
| 250 | return data.astype(str).tolist() |
||
| 251 | return data |
||
| 252 | |||
| 255 |