request-vm-on-golem 0.1.55__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: request-vm-on-golem
3
- Version: 0.1.55
3
+ Version: 0.1.57
4
4
  Summary: VM on Golem Requestor CLI - Create and manage virtual machines on the Golem Network
5
5
  Keywords: golem,vm,cloud,decentralized,cli
6
6
  Author: Phillip Jensen
@@ -296,12 +296,12 @@ Then, run any `golem` command. For example: `golem vm providers`
296
296
  Alternatively, you can prepend the environment variables directly to the command:
297
297
 
298
298
  ```bash
299
- GOLEM_REQUESTOR_ENVIRONMENT="development" GOLEM_REQUESTOR_FORCE_LOCALHOST="true" poetry run golem vm providers
299
+ GOLEM_ENVIRONMENT="development" GOLEM_REQUESTOR_FORCE_LOCALHOST="true" poetry run golem vm providers
300
300
  ```
301
301
 
302
302
  ### Mode vs. Network
303
303
 
304
- - Development Mode (`GOLEM_REQUESTOR_ENVIRONMENT=development`)
304
+ - Development Mode (`GOLEM_ENVIRONMENT=development`)
305
305
  - Improves local workflows: prefixes central discovery URL with `DEVMODE-` and, when using the central driver, maps provider IPs to `localhost` for easier testing.
306
306
  - Does not determine chain selection.
307
307
 
@@ -428,7 +428,7 @@ export GOLEM_REQUESTOR_SSH_KEY_DIR="/path/to/keys"
428
428
  export GOLEM_REQUESTOR_DB_PATH="/path/to/database.db"
429
429
 
430
430
  # Environment Mode (defaults to "production")
431
- export GOLEM_REQUESTOR_ENVIRONMENT="development" # Optional: Switch to development mode
431
+ export GOLEM_ENVIRONMENT="development" # Optional: Switch to development mode
432
432
  export GOLEM_REQUESTOR_FORCE_LOCALHOST="true" # Optional: Force localhost in development mode
433
433
  export GOLEM_REQUESTOR_NETWORK="testnet" # Or "mainnet"; optional filter for listing/creation
434
434
  ```
@@ -2,7 +2,7 @@ requestor/__init__.py,sha256=OqSUAh1uZBMx7GW0MoSMg967PVdmT8XdPJx3QYjwkak,116
2
2
  requestor/api/main.py,sha256=CTnaM7KyBtDwVlyclYbNDy-nGi5_xt9GTcGusRasDVY,2493
3
3
  requestor/cli/__init__.py,sha256=e3E4oEGxmGj-STPtFkQwg_qIWhR0JAiAQdw3G1hXciU,37
4
4
  requestor/cli/commands.py,sha256=Obr8Z1ZX3f6WThooIInlD7HqMl_pJmOqawzazLmhke0,51253
5
- requestor/config.py,sha256=FMSRKdyo3nEdn62CRonnwrw4Hsy4VGcuvvfg60aORsI,12809
5
+ requestor/config.py,sha256=gawIdSbv7GVReXoFB-IoCc5mw8n9Gmvk0xWndzxk9Ho,14021
6
6
  requestor/data/deployments/l2.json,sha256=XTNN2C5LkBfp4YbDKdUKfWMdp1fKnfv8D3TgcwVWxtQ,249
7
7
  requestor/db/__init__.py,sha256=Gm5DfWls6uvCZZ3HGGnyRHswbUQdeA5OGN8yPwH0hc8,88
8
8
  requestor/db/sqlite.py,sha256=l5pWbx2qlHuar1N_a0B9tVnmumLJY1w5rp3yZ7jmsC0,4146
@@ -11,18 +11,19 @@ requestor/payments/blockchain_service.py,sha256=CACvZH2ZstutX7f0L_PXl8K_V5WlIkxN
11
11
  requestor/payments/monitor.py,sha256=JtSnh2plFf-f8sJU-bkOpadhoK_R82_ULwkDRmBYSbc,6012
12
12
  requestor/provider/__init__.py,sha256=fmW23aYUVciF8-gmBZkG-PLhn22upmcDzdPfAOLHG6g,103
13
13
  requestor/provider/client.py,sha256=bWj4sNQ8w4F2sSRcMlHbWVjeBLAflDhHxy0BNMqzj8s,5021
14
- requestor/run.py,sha256=sR9GgylQWbYPc60wRr3rUpemlNrWqIPNFIJ8WCz6YwE,2120
14
+ requestor/run.py,sha256=ErbYJFZ6v-FagI6Fb-1FIKe775u6wsA_etelhagXHaI,2444
15
15
  requestor/security/faucet.py,sha256=XF_13b66SKAaY0-40hNRcSgC8AZA4mD5gyXl3qaBLpQ,2320
16
16
  requestor/services/__init__.py,sha256=1qSn_6RMn0KB0A7LCnY2IW6_tC3HBQsdfkFeV-h94eM,172
17
17
  requestor/services/database_service.py,sha256=GlSrzzzd7PSYQJNup00sxkB-B2PMr1__04K8k5QSWvs,2996
18
- requestor/services/provider_service.py,sha256=XMZtdkrpEKiGg0iFW3v_cf3zu7BZcYp1wjaphxo2SCU,16250
18
+ requestor/services/provider_service.py,sha256=jUpIyEMUb2OK_5_qOyeUte3X-xwThyWukqdUQ1hIlDk,16612
19
19
  requestor/services/ssh_service.py,sha256=tcOCtk2SlB9Uuv-P2ghR22e7BJ9kigQh5b4zSGdPFns,4280
20
20
  requestor/services/vm_service.py,sha256=e05OgMZwz3NcCiIwmz4EnB6joMK6S3G_RT3VajD92lw,9438
21
+ requestor/settings.py,sha256=OzBVwtnCRn8qg_ByEJDGpQowz4zkdA81uIWU4_OsPjQ,1183
21
22
  requestor/ssh/__init__.py,sha256=hNgSqJ5s1_AwwxVRyFjUqh_LTBpI4Hmzq0F-f_wXN9g,119
22
23
  requestor/ssh/manager.py,sha256=3jQtbbK7CVC2yD1zCO88jGXh2fBcuv3CzWEqDLuaQVk,9758
23
24
  requestor/utils/logging.py,sha256=lgAswzYvO9M0EOET0cFZvuAsGI4lInh_wln_6bI-fJk,4281
24
25
  requestor/utils/spinner.py,sha256=X0jfPfs5ricglTS4_XmacrM2Z1DDHR7zGk2KqYZDpXg,2541
25
- request_vm_on_golem-0.1.55.dist-info/METADATA,sha256=wepR8dMN4ehyEx4zmQjjTl3fuXs_OdGGiBCZPn3ZZ8I,15780
26
- request_vm_on_golem-0.1.55.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
27
- request_vm_on_golem-0.1.55.dist-info/entry_points.txt,sha256=Z-skRNpJ8aZcIl_En9mEm1ygkp9FKy0bzQoL3zO52-0,44
28
- request_vm_on_golem-0.1.55.dist-info/RECORD,,
26
+ request_vm_on_golem-0.1.57.dist-info/METADATA,sha256=XurwaN_AT6HMxo3dt-oeW3X07IYXtvGvjhK5VcGKWhc,15750
27
+ request_vm_on_golem-0.1.57.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
28
+ request_vm_on_golem-0.1.57.dist-info/entry_points.txt,sha256=Z-skRNpJ8aZcIl_En9mEm1ygkp9FKy0bzQoL3zO52-0,44
29
+ request_vm_on_golem-0.1.57.dist-info/RECORD,,
requestor/config.py CHANGED
@@ -22,7 +22,7 @@ def ensure_config() -> None:
22
22
  created = True
23
23
 
24
24
  if not env_file.exists():
25
- env_file.write_text("GOLEM_REQUESTOR_ENVIRONMENT=production\n")
25
+ env_file.write_text("GOLEM_ENVIRONMENT=production\n")
26
26
  created = True
27
27
 
28
28
  private_key = ssh_dir / "id_rsa"
@@ -91,6 +91,29 @@ class RequestorConfig(BaseSettings):
91
91
  return f"DEVMODE-{v}"
92
92
  return v
93
93
 
94
+ @field_validator("environment", mode="before")
95
+ @classmethod
96
+ def prefer_global_env(cls, v: str) -> str:
97
+ """Prefer unified GOLEM_ENVIRONMENT when provided; fallback to service-specific env."""
98
+ ge = os.environ.get("GOLEM_ENVIRONMENT")
99
+ if ge:
100
+ return ge
101
+ return v
102
+
103
+ @field_validator("network", mode="before")
104
+ @classmethod
105
+ def default_network_for_dev(cls, v: str, info: ValidationInfo) -> str:
106
+ """Use 'testnet' by default when in development environment unless explicitly set."""
107
+ if v:
108
+ return v
109
+ try:
110
+ env = info.data.get("environment")
111
+ except Exception:
112
+ env = os.environ.get("GOLEM_ENVIRONMENT")
113
+ if (env or "").lower() == "development":
114
+ return "testnet"
115
+ return v or "mainnet"
116
+
94
117
  # Golem Base Settings
95
118
  golem_base_rpc_url: str = Field(
96
119
  default="https://ethwarsaw.holesky.golemdb.io/rpc",
@@ -101,8 +124,8 @@ class RequestorConfig(BaseSettings):
101
124
  description="Golem Base WebSocket URL"
102
125
  )
103
126
  advertisement_interval: int = Field(
104
- default=240,
105
- description="Advertisement interval in seconds (should match provider)"
127
+ default=3600,
128
+ description="Advertisement interval in seconds (should match provider's GOLEM_BASE_ADVERTISEMENT_INTERVAL)"
106
129
  )
107
130
  ethereum_private_key: str = Field(
108
131
  default="0x0000000000000000000000000000000000000000000000000000000000000001",
@@ -323,6 +346,10 @@ class RequestorConfig(BaseSettings):
323
346
  # Allow overriding to dev mode with golem_dev_mode
324
347
  if os.environ.get('golem_dev_mode', 'false').lower() in ('true', '1', 't'):
325
348
  kwargs['environment'] = "development"
349
+ # Fallback: if requestor env not provided but provider env signals development, mirror it
350
+ if not kwargs.get('environment') and os.environ.get('GOLEM_ENVIRONMENT'):
351
+ # Mirror unified GOLEM_ENVIRONMENT into requestor environment if not explicitly provided
352
+ kwargs['environment'] = os.environ.get('GOLEM_ENVIRONMENT')
326
353
 
327
354
  # Set dependent paths before validation
328
355
  if 'ssh_key_dir' not in kwargs:
requestor/run.py CHANGED
@@ -48,9 +48,15 @@ def check_requirements() -> bool:
48
48
  def main():
49
49
  """Run the requestor CLI."""
50
50
  try:
51
- # Load environment variables from .env.dev file if it exists, otherwise use .env
52
- dev_env_path = Path(__file__).parent.parent / '.env.dev'
53
- env_path = dev_env_path if dev_env_path.exists() else Path(__file__).parent.parent / '.env'
51
+ # Load environment variables based on unified environment
52
+ env_mode = (os.environ.get('GOLEM_ENVIRONMENT') or os.environ.get('GOLEM_REQUESTOR_ENVIRONMENT') or '').lower()
53
+ base_dir = Path(__file__).parent.parent
54
+ env_file = '.env.dev' if env_mode == 'development' else '.env'
55
+ env_path = base_dir / env_file
56
+ # If chosen file does not exist, fallback to the other
57
+ if not env_path.exists():
58
+ alt = base_dir / ('.env' if env_file == '.env.dev' else '.env.dev')
59
+ env_path = alt if alt.exists() else env_path
54
60
  load_dotenv(dotenv_path=env_path)
55
61
  logger.info(f"Loading environment variables from: {env_path}")
56
62
 
@@ -272,13 +272,22 @@ class ProviderService:
272
272
  )
273
273
 
274
274
  async def _format_block_timestamp(self, block_number: int) -> str:
275
- """Format a block number into a human-readable 'time ago' string."""
275
+ """Format a block number into a human-readable 'time ago' string.
276
+
277
+ The Golem Base chain uses ~2 seconds per block.
278
+ We also guard against negative diffs that can occur when adverts are
279
+ extended before expiry (expires_at increases), which would otherwise
280
+ make the derived "created_at" appear in the future.
281
+ """
276
282
  if not self.golem_base_client:
277
283
  return "N/A"
278
284
  try:
279
285
  latest_block = await self.golem_base_client.http_client().eth.get_block('latest')
280
286
  block_diff = latest_block.number - block_number
281
- seconds_ago = block_diff * 2 # Approximate block time
287
+ if block_diff < 0:
288
+ block_diff = 0
289
+ # Approximate time: ~2s per block
290
+ seconds_ago = block_diff * 2
282
291
 
283
292
  if seconds_ago < 60:
284
293
  return f"{int(seconds_ago)}s ago"
requestor/settings.py ADDED
@@ -0,0 +1,49 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Literal, TypedDict
4
+
5
+ from .config import config
6
+
7
+
8
+ class _Prefs(TypedDict, total=False):
9
+ price_display: Literal["fiat", "native"]
10
+
11
+
12
+ _PREFS_FILE = config.base_dir / "settings.json"
13
+
14
+
15
+ def _read_prefs() -> _Prefs:
16
+ try:
17
+ if _PREFS_FILE.exists():
18
+ data = json.loads(_PREFS_FILE.read_text())
19
+ if isinstance(data, dict):
20
+ return data # type: ignore[return-value]
21
+ except Exception:
22
+ pass
23
+ return {}
24
+
25
+
26
+ def _write_prefs(p: _Prefs) -> None:
27
+ try:
28
+ _PREFS_FILE.parent.mkdir(parents=True, exist_ok=True)
29
+ tmp = Path(str(_PREFS_FILE) + ".tmp")
30
+ tmp.write_text(json.dumps(p, indent=2))
31
+ tmp.replace(_PREFS_FILE)
32
+ except Exception:
33
+ # Best-effort persistence; ignore write errors
34
+ pass
35
+
36
+
37
+ def get_price_display() -> Literal["fiat", "native"]:
38
+ prefs = _read_prefs()
39
+ mode = prefs.get("price_display")
40
+ if mode in ("fiat", "native"):
41
+ return mode
42
+ return "fiat"
43
+
44
+
45
+ def set_price_display(mode: Literal["fiat", "native"]) -> None:
46
+ prefs = _read_prefs()
47
+ prefs["price_display"] = mode
48
+ _write_prefs(prefs)
49
+