mm-balance 0.6.0__py3-none-any.whl → 0.6.2__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.
@@ -39,7 +39,7 @@ class BalanceFetcher:
39
39
 
40
40
  async def process(self) -> None:
41
41
  with self.progress_bar:
42
- runner = AsyncTaskRunner(max_concurrent_tasks=10)
42
+ runner = AsyncTaskRunner(concurrency=10)
43
43
  for network in self.config.networks():
44
44
  runner.add(f"process_{network}", self._process_network(network))
45
45
  await runner.run()
@@ -54,7 +54,7 @@ class BalanceFetcher:
54
54
  return result
55
55
 
56
56
  async def _process_network(self, network: Network) -> None:
57
- runner = AsyncTaskRunner(max_concurrent_tasks=self.config.workers[network])
57
+ runner = AsyncTaskRunner(concurrency=self.config.workers[network])
58
58
  for idx, task in enumerate(self.tasks[network]):
59
59
  runner.add(str(idx), self._get_balance(network, task.wallet_address, task.token_address))
60
60
  res = await runner.run()
mm_balance/cli.py CHANGED
@@ -4,13 +4,13 @@ import pkgutil
4
4
  from pathlib import Path
5
5
  from typing import Annotated
6
6
 
7
- import mm_print
8
7
  import typer
8
+ from mm_print import print_toml
9
9
 
10
10
  from mm_balance import command_runner
11
11
  from mm_balance.command_runner import CommandParameters
12
12
  from mm_balance.constants import NETWORKS
13
- from mm_balance.utils import PrintFormat
13
+ from mm_balance.utils import PrintFormat, fatal
14
14
 
15
15
  app = typer.Typer(no_args_is_help=True, pretty_exceptions_enable=False, add_completion=False)
16
16
 
@@ -25,8 +25,8 @@ def example_callback(value: bool) -> None:
25
25
  if value:
26
26
  data = pkgutil.get_data(__name__, "config/example.toml")
27
27
  if data is None:
28
- mm_print.exit_with_error("Example config not found")
29
- mm_print.toml(data.decode("utf-8"))
28
+ fatal("Example config not found")
29
+ print_toml(data.decode("utf-8"))
30
30
  raise typer.Exit
31
31
 
32
32
 
@@ -1,7 +1,6 @@
1
1
  import getpass
2
2
  from pathlib import Path
3
3
 
4
- import mm_print
5
4
  from pydantic import BaseModel
6
5
 
7
6
  from mm_balance.balance_fetcher import BalanceFetcher
@@ -11,7 +10,7 @@ from mm_balance.output.formats import json_format, table_format
11
10
  from mm_balance.price import Prices, get_prices
12
11
  from mm_balance.result import create_balances_result
13
12
  from mm_balance.token_decimals import get_token_decimals
14
- from mm_balance.utils import PrintFormat
13
+ from mm_balance.utils import PrintFormat, fatal
15
14
 
16
15
 
17
16
  class CommandParameters(BaseModel):
@@ -63,7 +62,7 @@ async def run(params: CommandParameters) -> None:
63
62
  elif config.settings.print_format is PrintFormat.JSON:
64
63
  json_format.print_result(config, token_decimals, prices, workers, result)
65
64
  else:
66
- mm_print.exit_with_error("Unsupported print format")
65
+ fatal("Unsupported print format")
67
66
 
68
67
  if params.save_balances:
69
68
  BalancesDict.from_balances_result(result).save_to_path(params.save_balances)
mm_balance/config.py CHANGED
@@ -1,16 +1,13 @@
1
- from __future__ import annotations
2
-
3
1
  from decimal import Decimal
4
2
  from pathlib import Path
5
3
  from typing import Annotated, Self
6
4
 
7
- import mm_print
8
5
  import pydash
9
6
  from mm_web3 import ConfigValidators, Web3CliConfig
10
7
  from pydantic import BeforeValidator, Field, StringConstraints, model_validator
11
8
 
12
9
  from mm_balance.constants import DEFAULT_NODES, TOKEN_ADDRESS, Network
13
- from mm_balance.utils import PrintFormat, evaluate_share_expression
10
+ from mm_balance.utils import PrintFormat, evaluate_share_expression, fatal
14
11
 
15
12
 
16
13
  class Validators(ConfigValidators):
@@ -62,7 +59,7 @@ class AssetGroup(Web3CliConfig):
62
59
  if path.is_file():
63
60
  result += path.read_text().strip().splitlines()
64
61
  else:
65
- mm_print.exit_with_error(f"File with addresses not found: {path}")
62
+ fatal(f"File with addresses not found: {path}")
66
63
  elif line.startswith("group:"):
67
64
  group_name = line.removeprefix("group:").strip()
68
65
  address_group = next((ag for ag in address_groups if ag.name == group_name), None)
mm_balance/diff.py CHANGED
@@ -1,12 +1,10 @@
1
- from __future__ import annotations
2
-
3
1
  import json
4
2
  import re
5
3
  from decimal import Decimal
6
4
  from pathlib import Path
7
5
 
8
- import mm_print
9
6
  from deepdiff.diff import DeepDiff
7
+ from mm_print import print_json, print_plain, print_table
10
8
  from pydantic import BaseModel, RootModel
11
9
 
12
10
  from mm_balance.result import Balance, BalancesResult
@@ -21,7 +19,8 @@ class BalancesDict(RootModel[dict[str, dict[str, dict[str, Decimal]]]]): # netw
21
19
  return set(self.model_dump()[network].keys())
22
20
 
23
21
  def save_to_path(self, balances_file: Path) -> None:
24
- json.dump(self.model_dump(), balances_file.open("w"), default=str, indent=2)
22
+ with balances_file.open("w") as f:
23
+ json.dump(self.model_dump(), f, default=str, indent=2)
25
24
 
26
25
  @staticmethod
27
26
  def from_balances_result(result: BalancesResult) -> BalancesDict:
@@ -38,7 +37,8 @@ class BalancesDict(RootModel[dict[str, dict[str, dict[str, Decimal]]]]): # netw
38
37
 
39
38
  @staticmethod
40
39
  def from_file(path: Path) -> BalancesDict:
41
- return BalancesDict(json.load(path.open("r")))
40
+ with path.open("r") as f:
41
+ return BalancesDict(json.load(f))
42
42
 
43
43
 
44
44
  class Diff(BaseModel):
@@ -72,23 +72,23 @@ class Diff(BaseModel):
72
72
  and not self.address_removed
73
73
  and not self.balance_changed
74
74
  ):
75
- mm_print.plain("Diff: no changes")
75
+ print_plain("Diff: no changes")
76
76
  return
77
77
 
78
- mm_print.plain("\nDiff")
78
+ print_plain("\nDiff")
79
79
 
80
80
  if self.network_added:
81
- mm_print.plain(f"networks added: {self.network_added}")
81
+ print_plain(f"networks added: {self.network_added}")
82
82
  if self.network_removed:
83
- mm_print.plain(f"networks removed: {self.network_removed}")
83
+ print_plain(f"networks removed: {self.network_removed}")
84
84
  if self.ticker_added:
85
- mm_print.plain(f"tickers added: {self.ticker_added}")
85
+ print_plain(f"tickers added: {self.ticker_added}")
86
86
  if self.ticker_removed:
87
- mm_print.plain(f"tickers removed: {self.ticker_removed}")
87
+ print_plain(f"tickers removed: {self.ticker_removed}")
88
88
  if self.address_added:
89
- mm_print.plain(f"addresses added: {self.address_added}")
89
+ print_plain(f"addresses added: {self.address_added}")
90
90
  if self.address_removed:
91
- mm_print.plain(f"addresses removed: {self.address_removed}")
91
+ print_plain(f"addresses removed: {self.address_removed}")
92
92
 
93
93
  if self.balance_changed:
94
94
  rows = []
@@ -97,11 +97,11 @@ class Diff(BaseModel):
97
97
  for address in self.balance_changed[network][ticker]:
98
98
  old_value, new_value = self.balance_changed[network][ticker][address]
99
99
  rows.append([network, ticker, address, old_value, new_value, new_value - old_value])
100
- mm_print.table(["Network", "Ticker", "Address", "Old", "New", "Change"], rows)
100
+ print_table(["Network", "Ticker", "Address", "Old", "New", "Change"], rows)
101
101
 
102
102
  def _print_json(self) -> None:
103
- # mm_print.json(data=self.model_dump(), type_handlers=str) ?? default?
104
- mm_print.json(data=self.model_dump())
103
+ # print_json(data=self.model_dump(), type_handlers=str) ?? default?
104
+ print_json(data=self.model_dump())
105
105
 
106
106
  @staticmethod
107
107
  def calc(balances1: BalancesDict, balances2: BalancesDict) -> Diff:
@@ -1,4 +1,4 @@
1
- import mm_print
1
+ from mm_print import print_json
2
2
 
3
3
  from mm_balance.balance_fetcher import BalanceFetcher
4
4
  from mm_balance.config import Config
@@ -27,4 +27,4 @@ def print_result(
27
27
  if errors:
28
28
  data["errors"] = errors
29
29
 
30
- mm_print.json(data)
30
+ print_json(data)
@@ -1,6 +1,6 @@
1
1
  from decimal import Decimal
2
2
 
3
- import mm_print
3
+ from mm_print import print_table
4
4
 
5
5
  from mm_balance.balance_fetcher import BalanceFetcher
6
6
  from mm_balance.config import Config
@@ -14,18 +14,18 @@ def print_nodes(config: Config) -> None:
14
14
  rows = []
15
15
  for network, nodes in config.nodes.items():
16
16
  rows.append([network, "\n".join(nodes)])
17
- mm_print.table(["network", "nodes"], rows, title="Nodes")
17
+ print_table(["network", "nodes"], rows, title="Nodes")
18
18
 
19
19
 
20
20
  def print_proxy_count(config: Config) -> None:
21
- mm_print.table(["count"], [[len(config.settings.proxies)]], title="Proxies")
21
+ print_table(["count"], [[len(config.settings.proxies)]], title="Proxies")
22
22
 
23
23
 
24
24
  def print_token_decimals(token_decimals: TokenDecimals) -> None:
25
25
  rows = []
26
26
  for network, decimals in token_decimals.items():
27
27
  rows.append([network, decimals])
28
- mm_print.table(["network", "decimals"], rows, title="Token Decimals")
28
+ print_table(["network", "decimals"], rows, title="Token Decimals")
29
29
 
30
30
 
31
31
  def print_prices(config: Config, prices: Prices) -> None:
@@ -35,7 +35,7 @@ def print_prices(config: Config, prices: Prices) -> None:
35
35
  rows.append(
36
36
  [ticker, format_number(price, config.settings.format_number_separator, "$", config.settings.round_ndigits)]
37
37
  )
38
- mm_print.table(["coin", "usd"], rows, title="Prices")
38
+ print_table(["coin", "usd"], rows, title="Prices")
39
39
 
40
40
 
41
41
  def print_result(config: Config, result: BalancesResult, workers: BalanceFetcher) -> None:
@@ -56,8 +56,10 @@ def _print_errors(config: Config, workers: BalanceFetcher) -> None:
56
56
  rows = []
57
57
  for task in error_tasks:
58
58
  group = config.groups[task.group_index]
59
- rows.append([group.ticker + " / " + group.network, task.wallet_address, task.balance.error]) # type: ignore[union-attr]
60
- mm_print.table(["coin", "address", "error"], rows, title="Errors")
59
+ # task.balance is guaranteed to be non-None ResultErr here since get_errors() filters for is_err()
60
+ if task.balance is not None:
61
+ rows.append([group.ticker + " / " + group.network, task.wallet_address, task.balance.unwrap_err()])
62
+ print_table(["coin", "address", "error"], rows, title="Errors")
61
63
 
62
64
 
63
65
  def _print_total(config: Config, total: Total, is_share_total: bool) -> None:
@@ -80,7 +82,7 @@ def _print_total(config: Config, total: Total, is_share_total: bool) -> None:
80
82
  rows.append(["stablecoin_sum", format_number(total.stablecoin_sum, config.settings.format_number_separator, "$")])
81
83
  rows.append(["total_usd_sum", format_number(total.total_usd_sum, config.settings.format_number_separator, "$")])
82
84
 
83
- mm_print.table(headers, rows, title=table_name)
85
+ print_table(headers, rows, title=table_name)
84
86
 
85
87
 
86
88
  def _print_group(config: Config, group: GroupResult) -> None:
@@ -118,4 +120,4 @@ def _print_group(config: Config, group: GroupResult) -> None:
118
120
  table_headers = ["address", "balance"]
119
121
  if config.settings.price:
120
122
  table_headers += ["usd"]
121
- mm_print.table(table_headers, rows, title=group_name)
123
+ print_table(table_headers, rows, title=group_name)
mm_balance/price.py CHANGED
@@ -34,7 +34,7 @@ async def get_prices(config: Config) -> Prices:
34
34
  if res.status_code != 200:
35
35
  continue
36
36
 
37
- json_body = res.parse_json()
37
+ json_body = res.json_body().unwrap_or({})
38
38
 
39
39
  for ticker, coingecko_id in coingecko_map.items():
40
40
  if coingecko_id in json_body:
mm_balance/result.py CHANGED
@@ -80,6 +80,8 @@ def _create_total(use_share: bool, groups: list[GroupResult]) -> Total:
80
80
  if total_usd_sum > 0:
81
81
  for ticker, usd_value in coin_usd_values.items():
82
82
  if ticker in USD_STABLECOINS:
83
+ # Intentionally show total stablecoin share for all stablecoins.
84
+ # We care about combined stablecoin allocation, not individual USDT/USDC breakdown.
83
85
  portfolio_share[ticker] = round(stablecoin_sum * 100 / total_usd_sum, 2)
84
86
  else:
85
87
  portfolio_share[ticker] = round(usd_value * 100 / total_usd_sum, 2)
@@ -1,8 +1,7 @@
1
- import mm_print
2
-
3
1
  from mm_balance import rpc
4
2
  from mm_balance.config import Config
5
3
  from mm_balance.constants import NETWORK_APTOS, NETWORK_BITCOIN, NETWORK_SOLANA, Network
4
+ from mm_balance.utils import fatal
6
5
 
7
6
 
8
7
  class TokenDecimals(dict[Network, dict[str | None, int]]): # {network: {None: 18}} -- None is for native token, ex. ETH
@@ -30,7 +29,7 @@ async def get_token_decimals(config: Config) -> TokenDecimals:
30
29
  elif group.network in (NETWORK_BITCOIN, NETWORK_APTOS):
31
30
  result[group.network][None] = 8
32
31
  else:
33
- mm_print.exit_with_error(f"Can't get token decimals for native token on network: {group.network}")
32
+ fatal(f"Can't get token decimals for native token on network: {group.network}")
34
33
  continue
35
34
 
36
35
  # get token_decimals via RPC
@@ -45,8 +44,8 @@ async def get_token_decimals(config: Config) -> TokenDecimals:
45
44
  if res.is_err():
46
45
  msg = f"can't get decimals for token {group.ticker} / {group.token}, error={res.unwrap_err()}"
47
46
  if config.settings.print_debug:
48
- msg += f"\n{res.extra}"
49
- mm_print.exit_with_error(msg)
47
+ msg += f"\n{res.context}"
48
+ fatal(msg)
50
49
 
51
50
  result[group.network][group.token] = res.unwrap()
52
51
 
mm_balance/utils.py CHANGED
@@ -1,6 +1,9 @@
1
1
  import re
2
2
  from decimal import Decimal
3
3
  from enum import StrEnum, unique
4
+ from typing import NoReturn
5
+
6
+ import typer
4
7
 
5
8
 
6
9
  def fnumber(value: Decimal, separator: str, extra: str | None = None) -> str:
@@ -15,7 +18,7 @@ def fnumber(value: Decimal, separator: str, extra: str | None = None) -> str:
15
18
  def scale_and_round(value: int, decimals: int, round_ndigits: int) -> Decimal:
16
19
  if value == 0:
17
20
  return Decimal(0)
18
- return round(Decimal(value / 10**decimals), round_ndigits)
21
+ return round(Decimal(value) / Decimal(10**decimals), round_ndigits)
19
22
 
20
23
 
21
24
  def round_decimal(value: Decimal, round_ndigits: int) -> Decimal:
@@ -161,3 +164,9 @@ class PrintFormat(StrEnum):
161
164
  PLAIN = "plain"
162
165
  TABLE = "table"
163
166
  JSON = "json"
167
+
168
+
169
+ def fatal(message: str) -> NoReturn:
170
+ """Print an error message and exit with code 1."""
171
+ typer.echo(message)
172
+ raise typer.Exit(1)
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: mm-balance
3
+ Version: 0.6.2
4
+ Requires-Python: >=3.14
5
+ Requires-Dist: deepdiff==8.6.1
6
+ Requires-Dist: mm-apt==0.6.0
7
+ Requires-Dist: mm-btc==0.6.0
8
+ Requires-Dist: mm-concurrency~=0.2.0
9
+ Requires-Dist: mm-eth==0.8.0
10
+ Requires-Dist: mm-sol==0.8.0
@@ -0,0 +1,22 @@
1
+ mm_balance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ mm_balance/balance_fetcher.py,sha256=SbS1jFySKeAzctT7adBTFoTBpTC6ieemscdkAWmBm9M,3221
3
+ mm_balance/cli.py,sha256=Nwef_-FzyX_VkFJ6GIAGYWaR8XOlZFQE8LBGwIpB2yg,2715
4
+ mm_balance/command_runner.py,sha256=271o0dgW66bMPo0xxXTpUrODvHwSw3BROxcbweh-RZk,2857
5
+ mm_balance/config.py,sha256=Y-zUTngIe2RXKaTsgMhaZJu_jLL7t_QJg6GJYQkxJUw,5089
6
+ mm_balance/constants.py,sha256=LhyW1EfNP5E8st-RPvD7p3QoIowFH2W14tkCf6NzveU,2468
7
+ mm_balance/diff.py,sha256=0z0SqY8GBXb__xO1wnhx8BvgL6DGtWEXQE5dauIlFFw,7191
8
+ mm_balance/price.py,sha256=ypdeif9tJRadFv71MYSn-q4-9y7tmoOC1szuMdPZIG4,1580
9
+ mm_balance/result.py,sha256=0GodPzbfLn7YC56yUVNT2yucZayLTssupIPHXdirNp0,5374
10
+ mm_balance/rpc.py,sha256=Z-rHJdhD8ScghUchV2M6rb8jyiPfB3nChEWFXjcBQOo,6116
11
+ mm_balance/token_decimals.py,sha256=X3iS1fvWtOq-w-tE7UVOPoTsVco1pFmSnOLBw0MvetA,2069
12
+ mm_balance/utils.py,sha256=rAa7EETCUiqUvn_GyH5XKQdlYSCz5cXal0M3vkJu21c,5652
13
+ mm_balance/config/example.toml,sha256=tqYx9gxhXYa5uKWFi-MDHYkPOgxqgrO_ZuU7T5tLzLM,1864
14
+ mm_balance/output/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ mm_balance/output/utils.py,sha256=zyN9igdaXGY_vKfc-3dJ13mH1T7JDke3AeB4MY_3AsA,842
16
+ mm_balance/output/formats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ mm_balance/output/formats/json_format.py,sha256=jSRVxxt8sVw_6sEdyDSmiMiVNsllQn5oNm8BN3Mwdd0,923
18
+ mm_balance/output/formats/table_format.py,sha256=xw5DiJ3rcOwd-3XRA7AEvmL5Bz3GHv9rEdA6VIAySnY,4951
19
+ mm_balance-0.6.2.dist-info/METADATA,sha256=niNF1sdtEHGoZpLI4h9W8m2ca2cu-RmaCfcVeZN-fVk,262
20
+ mm_balance-0.6.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
21
+ mm_balance-0.6.2.dist-info/entry_points.txt,sha256=rSnP0ZW1a3ACNwTWM7T53CmOycKbzhG43m2_wseENng,50
22
+ mm_balance-0.6.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,10 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: mm-balance
3
- Version: 0.6.0
4
- Requires-Python: >=3.13
5
- Requires-Dist: deepdiff==8.6.1
6
- Requires-Dist: mm-apt==0.5.0
7
- Requires-Dist: mm-btc==0.5.5
8
- Requires-Dist: mm-concurrency~=0.1.0
9
- Requires-Dist: mm-eth==0.7.3
10
- Requires-Dist: mm-sol==0.7.3
@@ -1,22 +0,0 @@
1
- mm_balance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- mm_balance/balance_fetcher.py,sha256=OCRQz_8lYxBKIicwXR3ysQOYxbLfYKRfaLWiFLnR0c8,3239
3
- mm_balance/cli.py,sha256=t_pshfbHEtzkmV9U6HrTAQy9HBVUqdf4jStUJ_s5Omg,2714
4
- mm_balance/command_runner.py,sha256=JvFHDzG6cEOic648PGlsNa3PwnA_rHRPB940WGPmWZo,2885
5
- mm_balance/config.py,sha256=eeE44dXnhekN4c4ZYcIpwQZiYr54Lku97RYasoE2IkY,5153
6
- mm_balance/constants.py,sha256=LhyW1EfNP5E8st-RPvD7p3QoIowFH2W14tkCf6NzveU,2468
7
- mm_balance/diff.py,sha256=CLaqXvFFZjxv9zJUE0O-rWyvSZNDi-jM1Md0LfgNOf8,7168
8
- mm_balance/price.py,sha256=jOwjD-7pIdo894iNH4pK-7LPuz6KnHDR23ghfmOedvU,1567
9
- mm_balance/result.py,sha256=qW2MRNiPt1JlFUSOJCFCcp-dEirZ2RK3RG5wyE1JAGQ,5193
10
- mm_balance/rpc.py,sha256=Z-rHJdhD8ScghUchV2M6rb8jyiPfB3nChEWFXjcBQOo,6116
11
- mm_balance/token_decimals.py,sha256=g8z2ahdVY5nlgmHyvlK9Dk9ush6G7eBkp31k74e9rKE,2087
12
- mm_balance/utils.py,sha256=GElNtCgE5jb2idak8l6_TnL7JhvmK9mdwObjVwdgE9o,5459
13
- mm_balance/config/example.toml,sha256=tqYx9gxhXYa5uKWFi-MDHYkPOgxqgrO_ZuU7T5tLzLM,1864
14
- mm_balance/output/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- mm_balance/output/utils.py,sha256=zyN9igdaXGY_vKfc-3dJ13mH1T7JDke3AeB4MY_3AsA,842
16
- mm_balance/output/formats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- mm_balance/output/formats/json_format.py,sha256=uNs2P1J6QhxCsHo8TOqG6aQbDfBkVjIwYyo96d2K17Q,910
18
- mm_balance/output/formats/table_format.py,sha256=2Y-21O7q-ghTrVSVewRPlhM-M86MoKw_arIYP5wALWE,4828
19
- mm_balance-0.6.0.dist-info/METADATA,sha256=3F0EMDxp6X84Wf1sb4cBnIUJ0QfnAk0K7Orsa6VtLUA,262
20
- mm_balance-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
- mm_balance-0.6.0.dist-info/entry_points.txt,sha256=rSnP0ZW1a3ACNwTWM7T53CmOycKbzhG43m2_wseENng,50
22
- mm_balance-0.6.0.dist-info/RECORD,,