golem-vm-provider 0.1.56__py3-none-any.whl → 0.1.57__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.
- {golem_vm_provider-0.1.56.dist-info → golem_vm_provider-0.1.57.dist-info}/METADATA +1 -1
- {golem_vm_provider-0.1.56.dist-info → golem_vm_provider-0.1.57.dist-info}/RECORD +10 -10
- provider/api/routes.py +14 -12
- provider/config.py +2 -1
- provider/container.py +2 -3
- provider/main.py +114 -20
- provider/utils/logging.py +18 -3
- provider/utils/pricing.py +12 -1
- {golem_vm_provider-0.1.56.dist-info → golem_vm_provider-0.1.57.dist-info}/WHEEL +0 -0
- {golem_vm_provider-0.1.56.dist-info → golem_vm_provider-0.1.57.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,9 @@
|
|
1
1
|
provider/__init__.py,sha256=HO1fkPpZqPO3z8O8-eVIyx8xXSMIVuTR_b1YF0RtXOg,45
|
2
2
|
provider/api/__init__.py,sha256=ssX1ugDqEPt8Fn04IymgmG-Ev8PiXLsCSaiZVvHQnec,344
|
3
3
|
provider/api/models.py,sha256=CmfgXqSH3m0HLqY6JvUFI-2IrdGf3EhNKtZ5kbIAX-U,4304
|
4
|
-
provider/api/routes.py,sha256=
|
5
|
-
provider/config.py,sha256=
|
6
|
-
provider/container.py,sha256=
|
4
|
+
provider/api/routes.py,sha256=tH6_msflEgx4O6nMku_Lgg4OW-JonqXHv89NibDFc94,13678
|
5
|
+
provider/config.py,sha256=nQzYBujgn-Z7Rqh6q0eOsTpk6R9-V-YF1OysmPpSH0Q,28993
|
6
|
+
provider/container.py,sha256=xN1a9qClciGomppCBnEGuPPNzGQkYIWlw1lzexrjptM,3726
|
7
7
|
provider/data/deployments/l2.json,sha256=XTNN2C5LkBfp4YbDKdUKfWMdp1fKnfv8D3TgcwVWxtQ,249
|
8
8
|
provider/discovery/__init__.py,sha256=Y6o8RxGevBpuQS3k32y-zSVbP6HBXG3veBl9ElVPKaU,349
|
9
9
|
provider/discovery/advertiser.py,sha256=o-LiDl1j0lXMUU0-zPe3qerjpoD2360EA60Y_V_VeBc,6571
|
@@ -13,7 +13,7 @@ provider/discovery/multi_advertiser.py,sha256=_J79wA1-XQ4GsLzt9KrKpWigGSGBqtut7D
|
|
13
13
|
provider/discovery/resource_monitor.py,sha256=AmiEc7yBGEGXCunQ-QKmVgosDX3gOhK1Y58LJZXrwAs,949
|
14
14
|
provider/discovery/resource_tracker.py,sha256=MP7IXd3aIMsjB4xz5Oj9zFDTEnvrnw-Cyxpl33xcJcc,6006
|
15
15
|
provider/discovery/service.py,sha256=vX_mVSxvn3arnb2cKDM_SeJp1ZgPdImP2aUubeXgdRg,915
|
16
|
-
provider/main.py,sha256=
|
16
|
+
provider/main.py,sha256=2FicpbL8113Gvw3qQzhVHdpYixrNgIYbqwYI0nJaqRI,55746
|
17
17
|
provider/network/port_verifier.py,sha256=mlSzr9Z-W5Z5mL3EYg4zemgGoi8Z5ebNoeFgLGRaoH4,13253
|
18
18
|
provider/payments/blockchain_service.py,sha256=4GrzDKwCSUVoENqjD4RLyJ0qwBOJKMyVk5Li-XNsyTc,3567
|
19
19
|
provider/payments/monitor.py,sha256=seo8vE622IdbcRE3x69IpvHn2mel_tlMNGt_DxOIoww,5386
|
@@ -24,9 +24,9 @@ provider/security/l2_faucet.py,sha256=yRV4xdPBgU8-LDTLqtuAijfgIoe2kYxvXqJLxFd-BV
|
|
24
24
|
provider/service.py,sha256=hlQn0woppsYFHZDMEgq-40cOjmiPWruiWLy_dQvaCRU,6859
|
25
25
|
provider/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
26
26
|
provider/utils/ascii_art.py,sha256=ykBFsztk57GIiz1NJ-EII5UvN74iECqQL4h9VmiW6Z8,3161
|
27
|
-
provider/utils/logging.py,sha256=
|
27
|
+
provider/utils/logging.py,sha256=1Br806ohJyYpDIw1i8NsNpg8Xc-8-rUYwKBU4LFomLk,2623
|
28
28
|
provider/utils/port_display.py,sha256=u1HWQFA2kPbsM-TnsQfL6Hr4KmjIZWZfsjoxarHpbW0,11981
|
29
|
-
provider/utils/pricing.py,sha256=
|
29
|
+
provider/utils/pricing.py,sha256=eDEjt0s7REyTR-7b_3D_a_yPCnQ4req2KvtemYrE2Kw,6673
|
30
30
|
provider/utils/retry.py,sha256=GvBjpr0DpTOgw28M2hI0yt17dpYLRwrxUUqVxWHQPtM,3148
|
31
31
|
provider/utils/setup.py,sha256=Z5dLuBQkb5vdoQsu1HJZwXmu9NWsiBYJ7Vq9-C-_tY8,2932
|
32
32
|
provider/vm/__init__.py,sha256=LJL504QGbqZvBbMN3G9ixMgAwvOWAKW37zUm_EiaW9M,508
|
@@ -39,7 +39,7 @@ provider/vm/port_manager.py,sha256=iYSwjTjD_ziOhG8aI7juKHw1OwwRUTJQyQoRUNQvz9w,1
|
|
39
39
|
provider/vm/provider.py,sha256=A7QN89EJjcSS40_SmKeinG1Jp_NGffJaLse-XdKciAs,1164
|
40
40
|
provider/vm/proxy_manager.py,sha256=n4NTsyz2rtrvjtf_ceKBk-g2q_mzqPwruB1q7UlQVBc,14928
|
41
41
|
provider/vm/service.py,sha256=Ki4SGNIZUq3XmaPMwAOoNzdZzKQsmFXid374wgjFPes,4636
|
42
|
-
golem_vm_provider-0.1.
|
43
|
-
golem_vm_provider-0.1.
|
44
|
-
golem_vm_provider-0.1.
|
45
|
-
golem_vm_provider-0.1.
|
42
|
+
golem_vm_provider-0.1.57.dist-info/METADATA,sha256=qsWTEj2YWwwpv_NTw4f6cFqFulMm5NZvMUP2iAK8-B8,20932
|
43
|
+
golem_vm_provider-0.1.57.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
44
|
+
golem_vm_provider-0.1.57.dist-info/entry_points.txt,sha256=5Jiie1dIXygmxmDW66bKKxQpmBLJ7leSKRrb8bkQALw,52
|
45
|
+
golem_vm_provider-0.1.57.dist-info/RECORD,,
|
provider/api/routes.py
CHANGED
@@ -7,8 +7,7 @@ from fastapi import APIRouter, HTTPException, Request
|
|
7
7
|
from dependency_injector.wiring import inject, Provide
|
8
8
|
from fastapi import APIRouter, HTTPException, Depends
|
9
9
|
|
10
|
-
from
|
11
|
-
from ..config import Settings as _Cfg
|
10
|
+
from typing import TYPE_CHECKING, Any
|
12
11
|
from ..container import Container
|
13
12
|
from ..utils.logging import setup_logger
|
14
13
|
from ..utils.ascii_art import vm_creation_animation, vm_status_change
|
@@ -27,7 +26,7 @@ router = APIRouter()
|
|
27
26
|
async def create_vm(
|
28
27
|
request: CreateVMRequest,
|
29
28
|
vm_service: VMService = Depends(Provide[Container.vm_service]),
|
30
|
-
settings:
|
29
|
+
settings: Any = Depends(Provide[Container.config]),
|
31
30
|
stream_map = Depends(Provide[Container.stream_map]),
|
32
31
|
) -> VMInfo:
|
33
32
|
"""Create a new VM."""
|
@@ -39,11 +38,12 @@ async def create_vm(
|
|
39
38
|
# If payments are enabled, require a valid stream before starting
|
40
39
|
# Determine if we should enforce gating
|
41
40
|
enforce = False
|
42
|
-
spa = settings
|
41
|
+
spa = (settings.get("STREAM_PAYMENT_ADDRESS") if isinstance(settings, dict) else getattr(settings, "STREAM_PAYMENT_ADDRESS", None))
|
43
42
|
if spa and spa != "0x0000000000000000000000000000000000000000":
|
44
43
|
if os.environ.get("PYTEST_CURRENT_TEST"):
|
45
44
|
# In pytest, skip gating only when using default deployment address
|
46
45
|
try:
|
46
|
+
from ..config import Settings as _Cfg # type: ignore
|
47
47
|
default_spa, _ = _Cfg._load_l2_deployment() # type: ignore[attr-defined]
|
48
48
|
except Exception:
|
49
49
|
default_spa = None
|
@@ -54,8 +54,10 @@ async def create_vm(
|
|
54
54
|
if enforce:
|
55
55
|
if request.stream_id is None:
|
56
56
|
raise HTTPException(status_code=400, detail="stream_id required when payments are enabled")
|
57
|
-
|
58
|
-
|
57
|
+
rpc_url = settings.get("POLYGON_RPC_URL") if isinstance(settings, dict) else getattr(settings, "POLYGON_RPC_URL", None)
|
58
|
+
reader = StreamPaymentReader(rpc_url, spa)
|
59
|
+
expected_recipient = settings.get("PROVIDER_ID") if isinstance(settings, dict) else getattr(settings, "PROVIDER_ID", None)
|
60
|
+
ok, reason = reader.verify_stream(int(request.stream_id), expected_recipient)
|
59
61
|
try:
|
60
62
|
s = reader.get_stream(int(request.stream_id))
|
61
63
|
now = int(reader.web3.eth.get_block("latest")["timestamp"]) # type: ignore[attr-defined]
|
@@ -73,7 +75,7 @@ async def create_vm(
|
|
73
75
|
# Create VM config
|
74
76
|
config = VMConfig(
|
75
77
|
name=request.name,
|
76
|
-
image=request.image or settings
|
78
|
+
image=request.image or (settings.get("DEFAULT_VM_IMAGE") if isinstance(settings, dict) else getattr(settings, "DEFAULT_VM_IMAGE", "")),
|
77
79
|
resources=resources,
|
78
80
|
ssh_key=request.ssh_key
|
79
81
|
)
|
@@ -143,7 +145,7 @@ async def get_vm_status(
|
|
143
145
|
async def get_vm_access(
|
144
146
|
requestor_name: str,
|
145
147
|
vm_service: VMService = Depends(Provide[Container.vm_service]),
|
146
|
-
settings:
|
148
|
+
settings: Any = Depends(Provide[Container.config]),
|
147
149
|
) -> VMAccessInfo:
|
148
150
|
"""Get VM access information."""
|
149
151
|
try:
|
@@ -156,7 +158,7 @@ async def get_vm_access(
|
|
156
158
|
raise HTTPException(404, "VM mapping not found")
|
157
159
|
|
158
160
|
return VMAccessInfo(
|
159
|
-
ssh_host=settings
|
161
|
+
ssh_host=((settings.get("PUBLIC_IP") if isinstance(settings, dict) else getattr(settings, "PUBLIC_IP", None)) or "localhost"),
|
160
162
|
ssh_port=vm.ssh_port,
|
161
163
|
vm_id=requestor_name,
|
162
164
|
multipass_name=multipass_name
|
@@ -222,7 +224,7 @@ async def delete_vm(
|
|
222
224
|
raise HTTPException(status_code=500, detail="An unexpected error occurred")
|
223
225
|
@router.get("/provider/info", response_model=ProviderInfoResponse)
|
224
226
|
@inject
|
225
|
-
async def provider_info(settings:
|
227
|
+
async def provider_info(settings: Any = Depends(Provide[Container.config])) -> ProviderInfoResponse:
|
226
228
|
return ProviderInfoResponse(
|
227
229
|
provider_id=settings["PROVIDER_ID"],
|
228
230
|
stream_payment_address=settings["STREAM_PAYMENT_ADDRESS"],
|
@@ -234,7 +236,7 @@ async def provider_info(settings: Settings = Depends(Provide[Container.config]))
|
|
234
236
|
@inject
|
235
237
|
async def get_vm_stream_status(
|
236
238
|
requestor_name: str,
|
237
|
-
settings:
|
239
|
+
settings: Any = Depends(Provide[Container.config]),
|
238
240
|
stream_map = Depends(Provide[Container.stream_map]),
|
239
241
|
) -> StreamStatus:
|
240
242
|
"""Return on-chain stream status for a VM (if mapped)."""
|
@@ -266,7 +268,7 @@ async def get_vm_stream_status(
|
|
266
268
|
@router.get("/payments/streams", response_model=List[StreamStatus])
|
267
269
|
@inject
|
268
270
|
async def list_stream_statuses(
|
269
|
-
settings:
|
271
|
+
settings: Any = Depends(Provide[Container.config]),
|
270
272
|
stream_map = Depends(Provide[Container.stream_map]),
|
271
273
|
) -> List[StreamStatus]:
|
272
274
|
"""List stream status for all mapped VMs."""
|
provider/config.py
CHANGED
@@ -38,7 +38,8 @@ def ensure_config() -> None:
|
|
38
38
|
created = True
|
39
39
|
|
40
40
|
if created:
|
41
|
-
|
41
|
+
# Inform the user, but write to stderr so JSON outputs on stdout remain clean
|
42
|
+
logger.info("Using default settings – run with --help to customize")
|
42
43
|
|
43
44
|
|
44
45
|
if not os.environ.get("GOLEM_PROVIDER_SKIP_BOOTSTRAP") and not os.environ.get("PYTEST_CURRENT_TEST"):
|
provider/container.py
CHANGED
@@ -2,7 +2,6 @@ import os
|
|
2
2
|
from dependency_injector import containers, providers
|
3
3
|
from pathlib import Path
|
4
4
|
|
5
|
-
from .config import settings
|
6
5
|
from .discovery.resource_tracker import ResourceTracker
|
7
6
|
from .discovery.golem_base_advertiser import GolemBaseAdvertiser
|
8
7
|
from .discovery.advertiser import DiscoveryServerAdvertiser
|
@@ -49,12 +48,12 @@ class Container(containers.DeclarativeContainer):
|
|
49
48
|
|
50
49
|
vm_name_mapper = providers.Singleton(
|
51
50
|
VMNameMapper,
|
52
|
-
db_path=Path(
|
51
|
+
db_path=providers.Callable(lambda base: Path(base) / "vm_names.json", config.VM_DATA_DIR),
|
53
52
|
)
|
54
53
|
|
55
54
|
stream_map = providers.Singleton(
|
56
55
|
StreamMap,
|
57
|
-
storage_path=Path(
|
56
|
+
storage_path=providers.Callable(lambda base: Path(base) / "streams.json", config.VM_DATA_DIR),
|
58
57
|
)
|
59
58
|
|
60
59
|
port_manager = providers.Singleton(
|
provider/main.py
CHANGED
@@ -1,32 +1,43 @@
|
|
1
1
|
import asyncio
|
2
2
|
import os
|
3
|
+
import sys as _sys
|
3
4
|
import socket
|
4
5
|
from fastapi import FastAPI
|
5
6
|
from fastapi.middleware.cors import CORSMiddleware
|
6
7
|
from typing import Optional
|
7
8
|
|
8
|
-
from .config import settings, ensure_config
|
9
9
|
from .utils.logging import setup_logger
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
|
11
|
+
|
12
|
+
# If the invocation includes --json, mute logs as early as possible
|
13
|
+
if "--json" in _sys.argv:
|
14
|
+
os.environ["GOLEM_SILENCE_LOGS"] = "1"
|
15
|
+
|
16
|
+
# Defer heavy local imports (may import config) until after we decide on silence
|
13
17
|
from .container import Container
|
14
18
|
from .service import ProviderService
|
15
19
|
|
16
|
-
|
17
20
|
logger = setup_logger(__name__)
|
18
21
|
|
19
22
|
app = FastAPI(title="VM on Golem Provider")
|
20
23
|
container = Container()
|
21
|
-
# Load configuration using a dict to avoid version-specific adapters
|
22
|
-
try:
|
23
|
-
container.config.from_dict(settings.model_dump())
|
24
|
-
except Exception:
|
25
|
-
# Fallback for environments without pydantic v2 model_dump
|
26
|
-
container.config.from_pydantic(settings)
|
27
24
|
app.container = container
|
28
25
|
container.wire(modules=[".api.routes"])
|
29
26
|
|
27
|
+
# Minimal safe defaults so DI providers that rely on config have paths before runtime
|
28
|
+
try:
|
29
|
+
from pathlib import Path as _Path
|
30
|
+
container.config.from_dict({
|
31
|
+
"VM_DATA_DIR": str(_Path.home() / ".golem" / "provider" / "vms"),
|
32
|
+
"PROXY_STATE_DIR": str(_Path.home() / ".golem" / "provider" / "proxy"),
|
33
|
+
"PORT_RANGE_START": 50800,
|
34
|
+
"PORT_RANGE_END": 50900,
|
35
|
+
"PORT": 7466,
|
36
|
+
"SKIP_PORT_VERIFICATION": True,
|
37
|
+
})
|
38
|
+
except Exception:
|
39
|
+
pass
|
40
|
+
|
30
41
|
from .vm.models import VMNotFoundError
|
31
42
|
from fastapi import Request
|
32
43
|
from fastapi.responses import JSONResponse
|
@@ -57,6 +68,13 @@ app.add_middleware(
|
|
57
68
|
@app.on_event("startup")
|
58
69
|
async def startup_event():
|
59
70
|
"""Handle application startup."""
|
71
|
+
# Load configuration into container lazily at runtime
|
72
|
+
from .config import settings as _settings
|
73
|
+
try:
|
74
|
+
container.config.from_dict(_settings.model_dump())
|
75
|
+
except Exception:
|
76
|
+
# Fallback for environments without pydantic v2 model_dump
|
77
|
+
container.config.from_pydantic(_settings)
|
60
78
|
provider_service = container.provider_service()
|
61
79
|
await provider_service.setup(app)
|
62
80
|
|
@@ -136,9 +154,8 @@ config_app = typer.Typer(help="Configure stream monitoring and withdrawals")
|
|
136
154
|
cli.add_typer(config_app, name="config")
|
137
155
|
|
138
156
|
@cli.callback()
|
139
|
-
def main():
|
157
|
+
def main(ctx: typer.Context):
|
140
158
|
"""VM on Golem Provider CLI"""
|
141
|
-
ensure_config()
|
142
159
|
# No-op callback to initialize config; avoid custom --version flag to keep help stable
|
143
160
|
return
|
144
161
|
|
@@ -173,15 +190,24 @@ def status(json_out: bool = typer.Option(False, "--json", help="Output machine-r
|
|
173
190
|
from rich.panel import Panel
|
174
191
|
from rich import box
|
175
192
|
|
176
|
-
#
|
193
|
+
# For JSON, set a process-wide mute env that setup_logger() respects
|
194
|
+
import os as _os
|
195
|
+
if json_out:
|
196
|
+
_os.environ["GOLEM_SILENCE_LOGS"] = "1"
|
197
|
+
|
198
|
+
# Temporarily quiet logs; when --json, suppress near everything
|
177
199
|
prev_level = _logger.level
|
200
|
+
import logging as _logging
|
201
|
+
_root_logger = _logging.getLogger()
|
202
|
+
_prev_root_level = _root_logger.level
|
178
203
|
try:
|
179
204
|
_logger.setLevel("WARNING")
|
205
|
+
if json_out:
|
206
|
+
_root_logger.setLevel(_logging.CRITICAL)
|
180
207
|
except Exception:
|
181
208
|
pass
|
182
209
|
|
183
210
|
# Silence port_verifier warnings during status checks for clean UI
|
184
|
-
import logging as _logging
|
185
211
|
try:
|
186
212
|
_pv_logger = _logging.getLogger("provider.network.port_verifier")
|
187
213
|
_prev_pv_level = _pv_logger.level
|
@@ -507,6 +533,22 @@ def status(json_out: bool = typer.Option(False, "--json", help="Output machine-r
|
|
507
533
|
_cfg_logger.setLevel(_prev_cfg_level)
|
508
534
|
except Exception:
|
509
535
|
pass
|
536
|
+
# Restore root logger
|
537
|
+
try:
|
538
|
+
_root_logger.setLevel(_prev_root_level)
|
539
|
+
except Exception:
|
540
|
+
pass
|
541
|
+
# Restore root logger
|
542
|
+
try:
|
543
|
+
_root_logger.setLevel(_prev_root_level)
|
544
|
+
except Exception:
|
545
|
+
pass
|
546
|
+
# Unset mute env if we set it
|
547
|
+
if json_out:
|
548
|
+
try:
|
549
|
+
del _os.environ["GOLEM_SILENCE_LOGS"]
|
550
|
+
except Exception:
|
551
|
+
pass
|
510
552
|
return
|
511
553
|
|
512
554
|
console = Console()
|
@@ -675,6 +717,16 @@ def status(json_out: bool = typer.Option(False, "--json", help="Output machine-r
|
|
675
717
|
_cfg_logger.setLevel(_prev_cfg_level)
|
676
718
|
except Exception:
|
677
719
|
pass
|
720
|
+
try:
|
721
|
+
_root_logger.setLevel(_prev_root_level)
|
722
|
+
except Exception:
|
723
|
+
pass
|
724
|
+
# Unset mute env if we set it
|
725
|
+
if json_out:
|
726
|
+
try:
|
727
|
+
del _os.environ["GOLEM_SILENCE_LOGS"]
|
728
|
+
except Exception:
|
729
|
+
pass
|
678
730
|
|
679
731
|
|
680
732
|
@wallet_app.command("faucet-l2")
|
@@ -712,8 +764,13 @@ def streams_list(json_out: bool = typer.Option(False, "--json", help="Output in
|
|
712
764
|
from web3 import Web3
|
713
765
|
import json as _json
|
714
766
|
try:
|
767
|
+
if json_out:
|
768
|
+
os.environ["GOLEM_SILENCE_LOGS"] = "1"
|
715
769
|
if not settings.STREAM_PAYMENT_ADDRESS or settings.STREAM_PAYMENT_ADDRESS == "0x0000000000000000000000000000000000000000":
|
716
|
-
|
770
|
+
if json_out:
|
771
|
+
print(_json.dumps({"error": "streaming_disabled"}, indent=2))
|
772
|
+
else:
|
773
|
+
print("Streaming payments are disabled on this provider.")
|
717
774
|
raise typer.Exit(code=1)
|
718
775
|
c = Container()
|
719
776
|
c.config.from_pydantic(settings)
|
@@ -813,8 +870,17 @@ def streams_list(json_out: bool = typer.Option(False, "--json", help="Output in
|
|
813
870
|
for row in table_rows:
|
814
871
|
print(fmt_row(row))
|
815
872
|
except Exception as e:
|
816
|
-
|
873
|
+
if json_out:
|
874
|
+
print(_json.dumps({"error": str(e)}, indent=2))
|
875
|
+
else:
|
876
|
+
print(f"Error: {e}")
|
817
877
|
raise typer.Exit(code=1)
|
878
|
+
finally:
|
879
|
+
if json_out:
|
880
|
+
try:
|
881
|
+
del os.environ["GOLEM_SILENCE_LOGS"]
|
882
|
+
except Exception:
|
883
|
+
pass
|
818
884
|
|
819
885
|
|
820
886
|
@streams_app.command("show")
|
@@ -828,12 +894,17 @@ def streams_show(vm_id: str = typer.Argument(..., help="VM id (requestor_name)")
|
|
828
894
|
from web3 import Web3
|
829
895
|
import json as _json
|
830
896
|
try:
|
897
|
+
if json_out:
|
898
|
+
os.environ["GOLEM_SILENCE_LOGS"] = "1"
|
831
899
|
c = Container()
|
832
900
|
c.config.from_pydantic(settings)
|
833
901
|
stream_map = c.stream_map()
|
834
902
|
sid = asyncio.run(stream_map.get(vm_id))
|
835
903
|
if sid is None:
|
836
|
-
|
904
|
+
if json_out:
|
905
|
+
print(_json.dumps({"error": "no_stream_mapping", "vm_id": vm_id}, indent=2))
|
906
|
+
else:
|
907
|
+
print("No stream mapped for this VM.")
|
837
908
|
raise typer.Exit(code=1)
|
838
909
|
reader = StreamPaymentReader(settings.POLYGON_RPC_URL, settings.STREAM_PAYMENT_ADDRESS)
|
839
910
|
s = reader.get_stream(int(sid))
|
@@ -896,8 +967,17 @@ def streams_show(vm_id: str = typer.Argument(..., help="VM id (requestor_name)")
|
|
896
967
|
print(" ".join("-" * wi for wi in w))
|
897
968
|
print(" ".join(str(cols[i]).ljust(w[i]) for i in range(len(w))))
|
898
969
|
except Exception as e:
|
899
|
-
|
970
|
+
if json_out:
|
971
|
+
print(_json.dumps({"error": str(e), "vm_id": vm_id}, indent=2))
|
972
|
+
else:
|
973
|
+
print(f"Error: {e}")
|
900
974
|
raise typer.Exit(code=1)
|
975
|
+
finally:
|
976
|
+
if json_out:
|
977
|
+
try:
|
978
|
+
del os.environ["GOLEM_SILENCE_LOGS"]
|
979
|
+
except Exception:
|
980
|
+
pass
|
901
981
|
|
902
982
|
@streams_app.command("earnings")
|
903
983
|
def streams_earnings(json_out: bool = typer.Option(False, "--json", help="Output in JSON")):
|
@@ -910,6 +990,8 @@ def streams_earnings(json_out: bool = typer.Option(False, "--json", help="Output
|
|
910
990
|
from web3 import Web3
|
911
991
|
import json as _json
|
912
992
|
try:
|
993
|
+
if json_out:
|
994
|
+
os.environ["GOLEM_SILENCE_LOGS"] = "1"
|
913
995
|
c = Container()
|
914
996
|
c.config.from_pydantic(settings)
|
915
997
|
stream_map = c.stream_map()
|
@@ -1001,8 +1083,20 @@ def streams_earnings(json_out: bool = typer.Option(False, "--json", help="Output
|
|
1001
1083
|
for row in table:
|
1002
1084
|
print(" ".join(str(row[i]).ljust(w2[i]) for i in range(4)))
|
1003
1085
|
except Exception as e:
|
1004
|
-
|
1086
|
+
if json_out:
|
1087
|
+
try:
|
1088
|
+
print(_json.dumps({"error": str(e)}, indent=2))
|
1089
|
+
except Exception:
|
1090
|
+
print("{\"error\": \"unexpected\"}")
|
1091
|
+
else:
|
1092
|
+
print(f"Error: {e}")
|
1005
1093
|
raise typer.Exit(code=1)
|
1094
|
+
finally:
|
1095
|
+
if json_out:
|
1096
|
+
try:
|
1097
|
+
del os.environ["GOLEM_SILENCE_LOGS"]
|
1098
|
+
except Exception:
|
1099
|
+
pass
|
1006
1100
|
|
1007
1101
|
|
1008
1102
|
@streams_app.command("withdraw")
|
provider/utils/logging.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import logging
|
2
2
|
import colorlog
|
3
3
|
import sys
|
4
|
+
import os
|
4
5
|
from typing import Optional
|
5
6
|
|
6
7
|
# Import standard logging levels
|
@@ -39,10 +40,23 @@ def setup_logger(name: Optional[str] = None, debug: bool = False) -> logging.Log
|
|
39
40
|
Configured logger instance
|
40
41
|
"""
|
41
42
|
logger = logging.getLogger(name or __name__)
|
43
|
+
|
44
|
+
# Global hard mute for JSON commands or other machine output scenarios
|
45
|
+
silence = os.getenv("GOLEM_SILENCE_LOGS", "").lower() in ("1", "true", "yes")
|
46
|
+
|
47
|
+
# If already configured, still adjust level according to silence/debug
|
42
48
|
if logger.handlers:
|
43
|
-
|
49
|
+
target_level = logging.CRITICAL if silence else (logging.DEBUG if debug else logging.INFO)
|
50
|
+
logger.setLevel(target_level)
|
51
|
+
for h in logger.handlers:
|
52
|
+
try:
|
53
|
+
h.setLevel(target_level)
|
54
|
+
except Exception:
|
55
|
+
pass
|
56
|
+
return logger # Already configured (levels updated)
|
44
57
|
|
45
|
-
|
58
|
+
# Send logs to stderr so stdout can be reserved for machine output (e.g., --json)
|
59
|
+
handler = colorlog.StreamHandler(sys.stderr)
|
46
60
|
formatter = colorlog.ColoredFormatter(
|
47
61
|
"%(log_color)s[%(asctime)s] %(levelname)s: %(message)s",
|
48
62
|
datefmt="%Y-%m-%d %H:%M:%S",
|
@@ -58,7 +72,8 @@ def setup_logger(name: Optional[str] = None, debug: bool = False) -> logging.Log
|
|
58
72
|
)
|
59
73
|
handler.setFormatter(formatter)
|
60
74
|
logger.addHandler(handler)
|
61
|
-
|
75
|
+
# Apply level based on silence/debug
|
76
|
+
logger.setLevel(logging.CRITICAL if silence else (logging.DEBUG if debug else logging.INFO))
|
62
77
|
|
63
78
|
return logger
|
64
79
|
|
provider/utils/pricing.py
CHANGED
@@ -7,11 +7,15 @@ import time
|
|
7
7
|
import requests
|
8
8
|
|
9
9
|
from ..vm.models import VMResources
|
10
|
-
from ..config import settings
|
11
10
|
from .logging import setup_logger
|
12
11
|
|
13
12
|
logger = setup_logger(__name__)
|
14
13
|
|
14
|
+
def _get_settings():
|
15
|
+
# Lazy import to avoid side effects during module import (e.g., JSON CLI quieting)
|
16
|
+
from ..config import settings as _s
|
17
|
+
return _s
|
18
|
+
|
15
19
|
# Increase precision for financial calcs
|
16
20
|
getcontext().prec = 28
|
17
21
|
|
@@ -21,6 +25,7 @@ def quantize_money(value: Decimal) -> Decimal:
|
|
21
25
|
|
22
26
|
|
23
27
|
def _coingecko_simple_price(ids: str) -> Optional[Decimal]:
|
28
|
+
settings = _get_settings()
|
24
29
|
base = settings.COINGECKO_API_URL.rstrip("/")
|
25
30
|
url = f"{base}/simple/price"
|
26
31
|
try:
|
@@ -44,6 +49,7 @@ def fetch_glm_usd_price() -> Optional[Decimal]:
|
|
44
49
|
|
45
50
|
Tries multiple IDs to hedge against slug changes.
|
46
51
|
"""
|
52
|
+
settings = _get_settings()
|
47
53
|
return _coingecko_simple_price(settings.COINGECKO_IDS)
|
48
54
|
|
49
55
|
|
@@ -70,6 +76,7 @@ def calculate_monthly_cost(resources: VMResources) -> Decimal:
|
|
70
76
|
|
71
77
|
Uses the GLM-denominated price-per-unit values configured in settings.
|
72
78
|
"""
|
79
|
+
settings = _get_settings()
|
73
80
|
core_price = Decimal(str(settings.PRICE_GLM_PER_CORE_MONTH))
|
74
81
|
ram_price = Decimal(str(settings.PRICE_GLM_PER_GB_RAM_MONTH))
|
75
82
|
storage_price = Decimal(str(settings.PRICE_GLM_PER_GB_STORAGE_MONTH))
|
@@ -98,6 +105,7 @@ def update_glm_unit_prices_from_usd(glm_usd: Decimal) -> Tuple[Decimal, Decimal,
|
|
98
105
|
|
99
106
|
Returns a tuple of (core_glm, ram_glm, storage_glm).
|
100
107
|
"""
|
108
|
+
settings = _get_settings()
|
101
109
|
core_usd = Decimal(str(settings.PRICE_USD_PER_CORE_MONTH))
|
102
110
|
ram_usd = Decimal(str(settings.PRICE_USD_PER_GB_RAM_MONTH))
|
103
111
|
storage_usd = Decimal(str(settings.PRICE_USD_PER_GB_STORAGE_MONTH))
|
@@ -107,6 +115,7 @@ def update_glm_unit_prices_from_usd(glm_usd: Decimal) -> Tuple[Decimal, Decimal,
|
|
107
115
|
storage_glm = usd_to_glm(storage_usd, glm_usd)
|
108
116
|
|
109
117
|
# Persist on settings instance (in-memory)
|
118
|
+
settings = _get_settings()
|
110
119
|
settings.PRICE_GLM_PER_CORE_MONTH = float(core_glm)
|
111
120
|
settings.PRICE_GLM_PER_GB_RAM_MONTH = float(ram_glm)
|
112
121
|
settings.PRICE_GLM_PER_GB_STORAGE_MONTH = float(storage_glm)
|
@@ -129,6 +138,7 @@ class PricingAutoUpdater:
|
|
129
138
|
self._last_price: Optional[Decimal] = None
|
130
139
|
|
131
140
|
async def start(self):
|
141
|
+
settings = _get_settings()
|
132
142
|
if not settings.PRICING_UPDATE_ENABLED:
|
133
143
|
return
|
134
144
|
|
@@ -173,6 +183,7 @@ class PricingAutoUpdater:
|
|
173
183
|
self._last_price = new_price
|
174
184
|
return True
|
175
185
|
delta = abs((new_price - old) / old) * Decimal("100")
|
186
|
+
settings = _get_settings()
|
176
187
|
if delta >= Decimal(str(settings.PRICING_UPDATE_MIN_DELTA_PERCENT)):
|
177
188
|
self._last_price = new_price
|
178
189
|
return True
|
File without changes
|
File without changes
|