mm-std 0.3.28__py3-none-any.whl → 0.3.30__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.
mm_std/data_result.py CHANGED
@@ -10,41 +10,33 @@ T = TypeVar("T")
10
10
  U = TypeVar("U")
11
11
 
12
12
 
13
+ type Data = dict[str, object] | None
14
+
15
+
13
16
  class DataResult(Generic[T]):
14
17
  """
15
18
  A result wrapper that encapsulates either a successful result (`ok`) or an error message (`err`).
16
- Optionally carries auxiliary `data` field regardless of success or failure.
19
+ Optionally carries `data` field regardless of success or failure.
17
20
  """
18
21
 
19
- def __init__(
20
- self,
21
- ok: T | None = None,
22
- err: str | None = None,
23
- data: object = None,
24
- ok_is_none: bool = False, # Allow None as a valid success value
25
- ) -> None:
26
- # Sanity check: at least one of ok or err must be provided, unless explicitly allowed via `ok_is_none`
27
- if ok is None and err is None and not ok_is_none:
28
- raise ValueError("Either ok or err must be set")
29
- # You can't set both ok and err unless ok_is_none is True (used to explicitly accept None as success)
30
- if (ok_is_none or ok is not None) and err is not None:
31
- raise ValueError("Cannot set both ok and err")
32
-
33
- self.ok = ok
34
- self.err = err
35
- self.data = data
22
+ _ok: T | None
23
+ _err: str | None
24
+ data: Data | None
25
+
26
+ def __init__(self) -> None:
27
+ raise RuntimeError("DataResult is not intended to be instantiated directly. Use the static methods instead.")
36
28
 
37
29
  def is_ok(self) -> bool:
38
30
  """
39
31
  Returns True if the result represents a success.
40
32
  """
41
- return self.err is None
33
+ return self._err is None
42
34
 
43
35
  def is_err(self) -> bool:
44
36
  """
45
37
  Returns True if the result represents an error.
46
38
  """
47
- return self.err is not None
39
+ return self._err is not None
48
40
 
49
41
  def unwrap(self) -> T:
50
42
  """
@@ -52,7 +44,7 @@ class DataResult(Generic[T]):
52
44
  """
53
45
  if self.is_err():
54
46
  raise RuntimeError(f"Called `unwrap()` on an `Err` value: {self.err!r}")
55
- return cast(T, self.ok)
47
+ return cast(T, self._ok)
56
48
 
57
49
  def unwrap_ok_or(self, default: T) -> T:
58
50
  """
@@ -66,7 +58,7 @@ class DataResult(Generic[T]):
66
58
  The success value or the default value.
67
59
  """
68
60
  if self.is_ok():
69
- return cast(T, self.ok)
61
+ return cast(T, self._ok)
70
62
  return default
71
63
 
72
64
  def unwrap_err(self) -> str:
@@ -75,13 +67,13 @@ class DataResult(Generic[T]):
75
67
  """
76
68
  if self.is_ok():
77
69
  raise RuntimeError(f"Called `unwrap_err()` on an `Ok` value: {self.ok!r}")
78
- return cast(str, self.err)
70
+ return cast(str, self._err)
79
71
 
80
72
  def dict(self) -> dict[str, object]:
81
73
  """
82
74
  Returns a dictionary representation of the result.
83
75
  """
84
- return {"ok": self.ok, "err": self.err, "data": self.data}
76
+ return {"ok": self._ok, "err": self._err, "data": self.data}
85
77
 
86
78
  def map(self, fn: Callable[[T], U]) -> DataResult[U]:
87
79
  """
@@ -96,13 +88,13 @@ class DataResult(Generic[T]):
96
88
  A new DataResult with the transformed success value or an error.
97
89
  """
98
90
  if self.is_err():
99
- return DataResult[U](err=self.err, data=self.data)
91
+ return DataResult[U].err(self.unwrap_err(), self.data)
100
92
 
101
93
  try:
102
94
  mapped_ok = fn(self.unwrap())
103
- return DataResult[U](ok=mapped_ok, data=self.data)
95
+ return DataResult[U].ok(mapped_ok, self.data)
104
96
  except Exception as e:
105
- return DataResult[U](err=f"Error in map: {e!s}", data={"original_data": self.data, "original_ok": self.ok})
97
+ return DataResult[U].exception(e, data={"original_data": self.data, "original_ok": self.ok})
106
98
 
107
99
  async def map_async(self, fn: Callable[[T], Awaitable[U]]) -> DataResult[U]:
108
100
  """
@@ -117,13 +109,13 @@ class DataResult(Generic[T]):
117
109
  A new DataResult with the transformed success value or an error.
118
110
  """
119
111
  if self.is_err():
120
- return DataResult[U](err=self.err, data=self.data)
112
+ return DataResult[U].err(self.unwrap_err(), self.data)
121
113
 
122
114
  try:
123
115
  mapped_ok = await fn(self.unwrap())
124
- return DataResult[U](ok=mapped_ok, data=self.data)
116
+ return DataResult[U].ok(mapped_ok, self.data)
125
117
  except Exception as e:
126
- return DataResult[U](err=f"Error in map_async: {e!s}", data={"original_data": self.data, "original_ok": self.ok})
118
+ return DataResult[U].exception(e, data={"original_data": self.data, "original_ok": self.ok})
127
119
 
128
120
  def and_then(self, fn: Callable[[T], DataResult[U]]) -> DataResult[U]:
129
121
  """
@@ -139,12 +131,12 @@ class DataResult(Generic[T]):
139
131
  The result of the function application or the original error.
140
132
  """
141
133
  if self.is_err():
142
- return DataResult[U](err=self.err, data=self.data)
134
+ return DataResult[U].err(self.unwrap_err(), self.data)
143
135
 
144
136
  try:
145
137
  return fn(self.unwrap())
146
138
  except Exception as e:
147
- return DataResult[U](err=f"Error in and_then: {e!s}", data={"original_data": self.data, "original_ok": self.ok})
139
+ return DataResult[U].exception(e, data={"original_data": self.data, "original_ok": self.ok})
148
140
 
149
141
  async def and_then_async(self, fn: Callable[[T], Awaitable[DataResult[U]]]) -> DataResult[U]:
150
142
  """
@@ -160,18 +152,18 @@ class DataResult(Generic[T]):
160
152
  The result of the function application or the original error.
161
153
  """
162
154
  if self.is_err():
163
- return DataResult[U](err=self.err, data=self.data)
155
+ return DataResult[U].err(self.unwrap_err(), self.data)
164
156
 
165
157
  try:
166
158
  return await fn(self.unwrap())
167
159
  except Exception as e:
168
- return DataResult[U](err=f"Error in and_then_async: {e!s}", data={"original_data": self.data, "original_ok": self.ok})
160
+ return DataResult[U].exception(e, {"original_data": self.data, "original_ok": self.ok})
169
161
 
170
162
  def __repr__(self) -> str:
171
163
  """
172
164
  Returns the debug representation of the result.
173
165
  """
174
- result = f"DataResult(ok={self.ok!r}" if self.is_ok() else f"DataResult(err={self.err!r}"
166
+ result = f"DataResult(ok={self._ok!r}" if self.is_ok() else f"DataResult(err={self._err!r}"
175
167
  if self.data is not None:
176
168
  result += f", data={self.data!r}"
177
169
  return result + ")"
@@ -188,7 +180,7 @@ class DataResult(Generic[T]):
188
180
  """
189
181
  if not isinstance(other, DataResult):
190
182
  return NotImplemented
191
- return self.ok == other.ok and self.err == other.err and self.data == other.data
183
+ return self._ok == other._ok and self._err == other._err and self.data == other.data
192
184
 
193
185
  @classmethod
194
186
  def __get_pydantic_core_schema__(cls, _source_type: type[Any], _handler: GetCoreSchemaHandler) -> CoreSchema:
@@ -210,9 +202,48 @@ class DataResult(Generic[T]):
210
202
  if isinstance(v, cls):
211
203
  return v
212
204
  if isinstance(v, dict):
213
- return cls(
205
+ return cls._create(
214
206
  ok=v.get("ok"),
215
207
  err=v.get("err"),
216
208
  data=v.get("data"),
217
209
  )
218
210
  raise TypeError(f"Cannot parse value as {cls.__name__}: {v}")
211
+
212
+ @classmethod
213
+ def _create(cls, ok: T | None, err: str | None, data: Data) -> DataResult[T]:
214
+ """
215
+ Internal method to create a DataResult instance.
216
+ """
217
+ obj = object.__new__(cls)
218
+ obj._ok = ok # noqa: SLF001
219
+ obj._err = err # noqa: SLF001
220
+ obj.data = data
221
+ return obj
222
+
223
+ @staticmethod
224
+ def ok(value: T, data: Data = None) -> DataResult[T]:
225
+ """
226
+ Static method to create a successful DataResult.
227
+ """
228
+ return DataResult._create(ok=value, err=None, data=data)
229
+
230
+ @staticmethod
231
+ def err(error: str, data: Data = None) -> DataResult[T]:
232
+ """
233
+ Static method to create an error DataResult.
234
+ """
235
+ return DataResult._create(ok=None, err=error, data=data)
236
+
237
+ @staticmethod
238
+ def exception(err: Exception, data: Data = None) -> DataResult[T]:
239
+ """
240
+ Static method to create an error DataResult from an exception.
241
+ """
242
+ if data is None:
243
+ data = {}
244
+ key = "exception_message"
245
+ while key in data:
246
+ key += "_"
247
+ data[key] = str(err)
248
+
249
+ return DataResult._create(ok=None, err="exception", data=data)
@@ -74,7 +74,7 @@ async def _request_with_http_or_none_proxy(
74
74
  method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, proxy=proxy, timeout=timeout
75
75
  ) as res:
76
76
  return HttpResponse(
77
- status=res.status,
77
+ status_code=res.status,
78
78
  error=None,
79
79
  error_message=None,
80
80
  body=(await res.read()).decode(),
@@ -102,7 +102,7 @@ async def _request_with_socks_proxy(
102
102
  ) as res,
103
103
  ):
104
104
  return HttpResponse(
105
- status=res.status,
105
+ status_code=res.status,
106
106
  error=None,
107
107
  error_message=None,
108
108
  body=(await res.read()).decode(),
mm_std/http/response.py CHANGED
@@ -3,8 +3,9 @@ import json
3
3
  from typing import Any
4
4
 
5
5
  import pydash
6
- from pydantic import GetCoreSchemaHandler
7
- from pydantic_core import CoreSchema, core_schema
6
+ from pydantic import BaseModel
7
+
8
+ from mm_std.data_result import DataResult
8
9
 
9
10
 
10
11
  @enum.unique
@@ -15,79 +16,32 @@ class HttpError(str, enum.Enum):
15
16
  ERROR = "error"
16
17
 
17
18
 
18
- class HttpResponse:
19
- def __init__(
20
- self,
21
- status: int | None = None,
22
- error: HttpError | None = None,
23
- error_message: str | None = None,
24
- body: str | None = None,
25
- headers: dict[str, str] | None = None,
26
- ) -> None:
27
- self.status = status
28
- self.error = error
29
- self.error_message = error_message
30
- self.body = body
31
- self.headers = headers
32
-
33
- self._json_data: Any = None
34
- self._json_parsed = False
35
- self._json_parsed_error = False
19
+ class HttpResponse(BaseModel):
20
+ status_code: int | None = None
21
+ error: HttpError | None = None
22
+ error_message: str | None = None
23
+ body: str | None = None
24
+ headers: dict[str, str] | None = None
36
25
 
37
- def _parse_json(self) -> None:
26
+ def parse_json_body(self, path: str | None = None, none_on_error: bool = False) -> Any: # noqa: ANN401
38
27
  if self.body is None:
39
- self._json_parsed_error = True
40
- return
28
+ if none_on_error:
29
+ return None
30
+ raise ValueError("Body is None")
31
+
41
32
  try:
42
- self._json_data = None
43
- self._json_data = json.loads(self.body)
44
- self._json_parsed_error = False
33
+ res = json.loads(self.body)
34
+ return pydash.get(res, path, None) if path else res
45
35
  except json.JSONDecodeError:
46
- self._json_parsed_error = True
47
- self._json_parsed = True
48
-
49
- def json(self, path: str | None = None) -> Any: # noqa: ANN401
50
- if not self._json_parsed:
51
- self._parse_json()
52
- if path:
53
- return pydash.get(self._json_data, path, None)
54
- return self._json_data
55
-
56
- def dict(self) -> dict[str, object]:
57
- return {
58
- "status": self.status,
59
- "error": self.error,
60
- "error_message": self.error_message,
61
- "body": self.body,
62
- "headers": self.headers,
63
- }
64
-
65
- def is_json_parse_error(self) -> bool:
66
- if not self._json_parsed:
67
- self._parse_json()
68
- return self._json_parsed_error
36
+ if none_on_error:
37
+ return None
38
+ raise
69
39
 
70
- def __repr__(self) -> str:
71
- return f"HttpResponse(status={self.status}, error={self.error}, error_message={self.error_message}, body={self.body}, headers={self.headers})" # noqa: E501
40
+ def is_error(self) -> bool:
41
+ return self.error is not None or (self.status_code is not None and self.status_code >= 400)
72
42
 
73
- @classmethod
74
- def __get_pydantic_core_schema__(cls, source_type: type[Any], handler: GetCoreSchemaHandler) -> CoreSchema:
75
- return core_schema.no_info_after_validator_function(
76
- cls._validate,
77
- core_schema.any_schema(),
78
- serialization=core_schema.plain_serializer_function_ser_schema(lambda x: x.dict()),
79
- )
43
+ def to_data_result_err[T](self, error: str | None = None) -> DataResult[T]:
44
+ return DataResult.err(error or self.error or "error", self.model_dump())
80
45
 
81
- @classmethod
82
- def _validate(cls, v: object) -> "HttpResponse":
83
- if isinstance(v, cls):
84
- return v
85
- if isinstance(v, dict):
86
- return cls(
87
- status=v.get("status"),
88
- error=HttpError(v["error"]) if v.get("error") else None,
89
- error_message=v.get("error_message"),
90
- body=v.get("body"),
91
- headers=v.get("headers"),
92
- )
93
- raise TypeError(f"Cannot parse value as {cls.__name__}: {v}")
46
+ def to_data_result_ok[T](self, result: T) -> DataResult[T]:
47
+ return DataResult.ok(result, self.model_dump())
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mm-std
3
- Version: 0.3.28
3
+ Version: 0.3.30
4
4
  Requires-Python: >=3.12
5
5
  Requires-Dist: aiohttp-socks~=0.10.1
6
6
  Requires-Dist: aiohttp~=3.11.16
7
7
  Requires-Dist: cryptography~=44.0.2
8
+ Requires-Dist: multidict~=6.4.3
8
9
  Requires-Dist: pydantic-settings>=2.8.1
9
10
  Requires-Dist: pydantic~=2.11.3
10
11
  Requires-Dist: pydash~=8.0.5
@@ -2,7 +2,7 @@ mm_std/__init__.py,sha256=ePkjykjImp2Wu10lFzK3Fl1HpGqqXFb_HZK_BSiU3MM,3316
2
2
  mm_std/command.py,sha256=ze286wjUjg0QSTgIu-2WZks53_Vclg69UaYYgPpQvCU,1283
3
3
  mm_std/config.py,sha256=4ox4D2CgGR76bvZ2n2vGQOYUDagFnlKEDb87to5zpxE,1871
4
4
  mm_std/crypto.py,sha256=jdk0_TCmeU0pPXMyz9xH6kQHSjjZ9GcGClBwQps5vBo,340
5
- mm_std/data_result.py,sha256=AMG6ttFTa9wxOS2U3kgPI2Z1shntdhF-7KxvfYYILoo,8104
5
+ mm_std/data_result.py,sha256=aUDhnxp5EagYFkCiOGKYReQshDrK_3zXfGYla5mJ8Yk,8739
6
6
  mm_std/date.py,sha256=976eEkSONuNqHQBgSRu8hrtH23tJqztbmHFHLdbP2TY,1879
7
7
  mm_std/dict.py,sha256=6GkhJPXD0LiJDxPcYe6jPdEDw-MN7P7mKu6U5XxwYDk,675
8
8
  mm_std/env.py,sha256=5zaR9VeIfObN-4yfgxoFeU5IM1GDeZZj9SuYf7t9sOA,125
@@ -27,8 +27,8 @@ mm_std/concurrency/sync_decorators.py,sha256=syCQBOmN7qPO55yzgJB2rbkh10CVww376hm
27
27
  mm_std/concurrency/sync_scheduler.py,sha256=j4tBL_cBI1spr0cZplTA7N2CoYsznuORMeRN8rpR6gY,2407
28
28
  mm_std/concurrency/sync_task_runner.py,sha256=s5JPlLYLGQGHIxy4oDS-PN7O9gcy-yPZFoNm8RQwzcw,1780
29
29
  mm_std/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- mm_std/http/http_request.py,sha256=mJak332gv9NvY0JLO3hNrBxroQGS1HlNBzd1yq4Dy24,3694
31
- mm_std/http/response.py,sha256=vvv5COTjJula9t33mFyrruhrFC4dr_Uy0jDKj6t1JxM,2923
32
- mm_std-0.3.28.dist-info/METADATA,sha256=NbbHHlhKM4WMRWCSpEvDdQhfDiH9L6-TUQQBP6Px4Co,415
33
- mm_std-0.3.28.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
34
- mm_std-0.3.28.dist-info/RECORD,,
30
+ mm_std/http/http_request.py,sha256=h74_ZjACwdbeINjzhuEwNUpvnaHZvyGl7TExjlBwMGg,3704
31
+ mm_std/http/response.py,sha256=WNQWd1HC134WvjEOSvikHCmujbsvs2332VzUPp9DnyM,1377
32
+ mm_std-0.3.30.dist-info/METADATA,sha256=wv2Gs6_yVnt-f58T01_Dt6-cP8btIij1MFm91G04xYM,447
33
+ mm_std-0.3.30.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
34
+ mm_std-0.3.30.dist-info/RECORD,,