iwa 0.1.4__py3-none-any.whl → 0.1.6__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.
@@ -8,6 +8,7 @@ from pydantic import BaseModel, Field
8
8
 
9
9
  from iwa.core.models import Config
10
10
  from iwa.plugins.olas.models import OlasConfig
11
+ from iwa.web.cache import CacheTTL, response_cache
11
12
  from iwa.web.dependencies import verify_auth, wallet
12
13
 
13
14
  router = APIRouter(tags=["olas"])
@@ -140,6 +141,11 @@ def create_service(req: CreateServiceRequest, auth: bool = Depends(verify_auth))
140
141
  # Get final state
141
142
  final_state = manager.get_service_state()
142
143
 
144
+ # Invalidate caches for services list
145
+ response_cache.invalidate("service_state:")
146
+ response_cache.invalidate("staking_status:")
147
+ response_cache.invalidate("balances:")
148
+
143
149
  return {
144
150
  "status": "success",
145
151
  "service_id": service_id,
@@ -220,6 +226,12 @@ def deploy_service(
220
226
  )
221
227
 
222
228
  final_state = manager.get_service_state()
229
+
230
+ # Invalidate caches for this service
231
+ response_cache.invalidate(f"service_state:{service_key}")
232
+ response_cache.invalidate(f"staking_status:{service_key}")
233
+ response_cache.invalidate(f"balances:{service_key}")
234
+
223
235
  return {
224
236
  "status": "success",
225
237
  "service_key": service_key,
@@ -303,13 +315,63 @@ def _resolve_service_balances(service, chain: str) -> dict:
303
315
  return balances
304
316
 
305
317
 
318
+ def _get_balances_cached(
319
+ service_key: str, service, chain: str, force_refresh: bool = False
320
+ ) -> dict:
321
+ """Get service balances with caching to prevent excessive RPC calls."""
322
+ cache_key = f"balances:{service_key}:{chain}"
323
+
324
+ if force_refresh:
325
+ response_cache.invalidate(cache_key)
326
+
327
+ def fetch_balances():
328
+ return _resolve_service_balances(service, chain)
329
+
330
+ return response_cache.get_or_compute(
331
+ cache_key, fetch_balances, CacheTTL.BALANCES
332
+ )
333
+
334
+
335
+ def _staking_status_to_dict(status) -> Optional[dict]:
336
+ """Convert StakingStatus dataclass to dict for JSON serialization."""
337
+ if not status:
338
+ return None
339
+ return {
340
+ "is_staked": status.is_staked,
341
+ "staking_state": status.staking_state,
342
+ "staking_contract_address": status.staking_contract_address,
343
+ "staking_contract_name": status.staking_contract_name,
344
+ "accrued_reward_olas": status.accrued_reward_olas,
345
+ "accrued_reward_wei": status.accrued_reward_wei,
346
+ "epoch_number": status.epoch_number,
347
+ "epoch_end_utc": status.epoch_end_utc,
348
+ "remaining_epoch_seconds": status.remaining_epoch_seconds,
349
+ "mech_requests_this_epoch": status.mech_requests_this_epoch,
350
+ "required_mech_requests": status.required_mech_requests,
351
+ "has_enough_requests": status.has_enough_requests,
352
+ "liveness_ratio_passed": status.liveness_ratio_passed,
353
+ "unstake_available_at": status.unstake_available_at,
354
+ }
355
+
356
+
306
357
  @router.get(
307
358
  "/services/basic",
308
359
  summary="Get Basic Services",
309
- description="Get a lightweight list of configured Olas services without RPC calls.",
360
+ description="Get a lightweight list of configured Olas services.",
310
361
  )
311
- def get_olas_services_basic(chain: str = "gnosis", auth: bool = Depends(verify_auth)):
312
- """Get basic Olas service info from config (fast, no RPC calls)."""
362
+ def get_olas_services_basic(
363
+ chain: str = "gnosis",
364
+ refresh: bool = False,
365
+ auth: bool = Depends(verify_auth),
366
+ ):
367
+ """Get basic Olas service info from config.
368
+
369
+ Args:
370
+ chain: Chain name to filter services.
371
+ refresh: If true, bypass cache and fetch fresh data.
372
+ auth: Authentication dependency (injected).
373
+
374
+ """
313
375
  if not chain.replace("-", "").isalnum():
314
376
  raise HTTPException(status_code=400, detail="Invalid chain name")
315
377
 
@@ -327,14 +389,15 @@ def get_olas_services_basic(chain: str = "gnosis", auth: bool = Depends(verify_a
327
389
  if service.chain_name != chain:
328
390
  continue
329
391
 
330
- # Get service state from registry
331
- state = "UNKNOWN"
392
+ # Get service state using ServiceManager's built-in caching
332
393
  try:
333
394
  manager = ServiceManager(wallet)
334
395
  manager.service = service
335
- state = manager.get_service_state()
396
+ manager._init_contracts(service.chain_name)
397
+ state = manager.get_service_state(force_refresh=refresh)
336
398
  except Exception as e:
337
399
  logger.warning(f"Could not get state for {service_key}: {e}")
400
+ state = "UNKNOWN"
338
401
 
339
402
  # Get tags from wallet storage (fast, local lookup)
340
403
  accounts = _resolve_service_accounts(service)
@@ -367,8 +430,19 @@ def get_olas_services_basic(chain: str = "gnosis", auth: bool = Depends(verify_a
367
430
  summary="Get Service Details",
368
431
  description="Get detailed status, balances, and staking info for a specific Olas service.",
369
432
  )
370
- def get_olas_service_details(service_key: str, auth: bool = Depends(verify_auth)):
371
- """Get full details for a single Olas service (staking, balances)."""
433
+ def get_olas_service_details(
434
+ service_key: str,
435
+ refresh: bool = False,
436
+ auth: bool = Depends(verify_auth),
437
+ ):
438
+ """Get full details for a single Olas service (staking, balances).
439
+
440
+ Args:
441
+ service_key: The service key (chain:id format).
442
+ refresh: If true, bypass cache and fetch fresh data.
443
+ auth: Authentication dependency (injected).
444
+
445
+ """
372
446
  try:
373
447
  from iwa.plugins.olas.service_manager import ServiceManager
374
448
 
@@ -378,43 +452,28 @@ def get_olas_service_details(service_key: str, auth: bool = Depends(verify_auth)
378
452
 
379
453
  olas_config = OlasConfig.model_validate(config.plugins["olas"])
380
454
  if service_key not in olas_config.services:
381
- raise HTTPException(status_code=404, detail=f"Service '{service_key}' not found")
455
+ raise HTTPException(
456
+ status_code=404, detail=f"Service '{service_key}' not found"
457
+ )
382
458
 
383
459
  service = olas_config.services[service_key]
384
460
  chain = service.chain_name
385
461
 
462
+ # Use ServiceManager with built-in caching
386
463
  manager = ServiceManager(wallet)
387
464
  manager.service = service
388
- staking_status = manager.get_staking_status()
389
- service_state = manager.get_service_state()
390
-
391
- # Get balances
392
- balances = _resolve_service_balances(service, chain)
393
-
394
- staking = None
395
- if staking_status:
396
- staking = {
397
- "is_staked": staking_status.is_staked,
398
- "staking_state": staking_status.staking_state,
399
- "staking_contract_address": staking_status.staking_contract_address,
400
- "staking_contract_name": staking_status.staking_contract_name,
401
- "accrued_reward_olas": staking_status.accrued_reward_olas,
402
- "accrued_reward_wei": staking_status.accrued_reward_wei,
403
- "epoch_number": staking_status.epoch_number,
404
- "epoch_end_utc": staking_status.epoch_end_utc,
405
- "remaining_epoch_seconds": staking_status.remaining_epoch_seconds,
406
- "mech_requests_this_epoch": staking_status.mech_requests_this_epoch,
407
- "required_mech_requests": staking_status.required_mech_requests,
408
- "has_enough_requests": staking_status.has_enough_requests,
409
- "liveness_ratio_passed": staking_status.liveness_ratio_passed,
410
- "unstake_available_at": staking_status.unstake_available_at,
411
- }
465
+ manager._init_contracts(service.chain_name)
466
+
467
+ # Get data with force_refresh if requested
468
+ service_state = manager.get_service_state(force_refresh=refresh)
469
+ staking_status = manager.get_staking_status(force_refresh=refresh)
470
+ balances = _get_balances_cached(service_key, service, chain, refresh)
412
471
 
413
472
  return {
414
473
  "key": service_key,
415
474
  "state": service_state,
416
475
  "accounts": balances,
417
- "staking": staking,
476
+ "staking": _staking_status_to_dict(staking_status),
418
477
  }
419
478
 
420
479
  except HTTPException:
@@ -427,21 +486,30 @@ def get_olas_service_details(service_key: str, auth: bool = Depends(verify_auth)
427
486
  @router.get(
428
487
  "/services",
429
488
  summary="Get All Services",
430
- description="Get comprehensive list of Olas services with full details (slower than basic).",
489
+ description="Get comprehensive list of Olas services with full details.",
431
490
  )
432
- def get_olas_services(chain: str = "gnosis", auth: bool = Depends(verify_auth)):
433
- """Get all Olas services with staking status for a specific chain."""
491
+ def get_olas_services(
492
+ chain: str = "gnosis",
493
+ refresh: bool = False,
494
+ auth: bool = Depends(verify_auth),
495
+ ):
496
+ """Get all Olas services with staking status for a specific chain.
497
+
498
+ Args:
499
+ chain: Chain name to filter services.
500
+ refresh: If true, bypass cache and fetch fresh data.
501
+ auth: Authentication dependency (injected).
502
+
503
+ """
434
504
  if not chain.replace("-", "").isalnum():
435
505
  raise HTTPException(status_code=400, detail="Invalid chain name")
436
506
 
437
507
  try:
438
- # Re-using detail logic iteratively (inefficient but safe for now)
439
- # Ideally we refactor this to be more efficient bulk query later
440
- basic = get_olas_services_basic(chain, auth)
508
+ basic = get_olas_services_basic(chain, refresh, auth)
441
509
  result = []
442
510
  for svc in basic:
443
511
  try:
444
- details = get_olas_service_details(svc["key"], auth)
512
+ details = get_olas_service_details(svc["key"], refresh, auth)
445
513
  # Merge details into basic info
446
514
  svc["staking"] = details["staking"]
447
515
  svc["accounts"] = details["accounts"]
@@ -253,6 +253,7 @@ def stake_service(
253
253
  success = manager.stake(staking)
254
254
 
255
255
  if success:
256
+ # Cache invalidation handled by ServiceManager.stake()
256
257
  return {"status": "success"}
257
258
  else:
258
259
  raise HTTPException(status_code=400, detail="Failed to stake service")
@@ -286,6 +287,7 @@ def claim_rewards(service_key: str, auth: bool = Depends(verify_auth)):
286
287
 
287
288
  success, amount = manager.claim_rewards()
288
289
  if success:
290
+ # Cache invalidation handled by ServiceManager.claim_rewards()
289
291
  return {"status": "success", "amount": amount}
290
292
  else:
291
293
  raise HTTPException(status_code=400, detail="Failed to claim rewards")
@@ -323,6 +325,7 @@ def unstake_service(service_key: str, auth: bool = Depends(verify_auth)):
323
325
 
324
326
  success = manager.unstake(staking_contract)
325
327
  if success:
328
+ # Cache invalidation handled by ServiceManager.unstake()
326
329
  return {"status": "success"}
327
330
  else:
328
331
  raise HTTPException(status_code=400, detail="Failed to unstake")
@@ -359,6 +362,7 @@ def checkpoint_service(service_key: str, auth: bool = Depends(verify_auth)):
359
362
 
360
363
  success = manager.call_checkpoint(staking_contract)
361
364
  if success:
365
+ # Cache invalidation handled by ServiceManager.call_checkpoint()
362
366
  return {"status": "success"}
363
367
  else:
364
368
  # Check if it was just not needed
iwa/web/server.py CHANGED
@@ -162,6 +162,7 @@ async def run_server_async(host: str = "127.0.0.1", port: int = 8000):
162
162
  Args:
163
163
  host: Bind address. Default "127.0.0.1" (localhost only for security).
164
164
  port: Port number. Default 8000.
165
+
165
166
  """
166
167
  import uvicorn
167
168