Skip to content

Commit c3adbfc

Browse files
fix: update
1 parent a2680a6 commit c3adbfc

12 files changed

Lines changed: 172 additions & 34 deletions

File tree

liveweb_arena/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@
44

55
# Core components
66
from .core.models import BrowserObservation, BrowserAction, CompositeTask, TrajectoryStep
7-
from .core.browser import BrowserEngine, BrowserSession
87
from .plugins.base import BasePlugin, SubTask, ValidationResult
98

9+
# Optional browser layer (depends on playwright). Keep submodules usable for
10+
# tooling that doesn't require browser execution (e.g., template registry).
11+
try:
12+
from .core.browser import BrowserEngine, BrowserSession # type: ignore
13+
except ModuleNotFoundError:
14+
BrowserEngine = None # type: ignore
15+
BrowserSession = None # type: ignore
16+
1017
__all__ = [
1118
"__version__",
1219
# Models

liveweb_arena/core/cache.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
"""
1919

2020
import asyncio
21-
import fcntl
2221
import json
2322
import logging
2423
import os
@@ -39,6 +38,12 @@
3938
# Default TTL: 48 hours
4039
DEFAULT_TTL = 72 * 3600 # 3 days
4140

41+
_IS_WINDOWS = os.name == "nt"
42+
if not _IS_WINDOWS:
43+
import fcntl # type: ignore
44+
else:
45+
import msvcrt # type: ignore
46+
4247

4348
class CacheFatalError(Exception):
4449
"""
@@ -130,26 +135,50 @@ async def async_file_lock_acquire(lock_path: Path, timeout: float = 60.0) -> int
130135
start = time.time()
131136

132137
while True:
133-
fd = open(lock_path, 'w')
138+
fd = open(lock_path, "a+b")
134139
try:
135-
# Try non-blocking lock
136-
fcntl.flock(fd.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
140+
# Ensure the lock file has at least one byte for Windows locking.
141+
try:
142+
fd.seek(0, os.SEEK_END)
143+
if fd.tell() == 0:
144+
fd.write(b"\0")
145+
fd.flush()
146+
except Exception:
147+
pass
148+
149+
if _IS_WINDOWS:
150+
msvcrt.locking(fd.fileno(), msvcrt.LK_NBLCK, 1)
151+
else:
152+
fcntl.flock(fd.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
137153
return fd # Lock acquired, return file object
138-
except BlockingIOError:
139-
fd.close()
154+
except (BlockingIOError, OSError):
155+
try:
156+
fd.close()
157+
except Exception:
158+
pass
140159
# Lock held by another process, wait and retry
141160
if time.time() - start > timeout:
142161
raise TimeoutError(f"Could not acquire lock {lock_path} within {timeout}s")
143162
await asyncio.sleep(0.1) # Yield to event loop
144163
except Exception:
145-
fd.close()
164+
try:
165+
fd.close()
166+
except Exception:
167+
pass
146168
raise
147169

148170

149171
def async_file_lock_release(fd):
150172
"""Release file lock acquired by async_file_lock_acquire()."""
151173
try:
152-
fcntl.flock(fd.fileno(), fcntl.LOCK_UN)
174+
if _IS_WINDOWS:
175+
try:
176+
fd.seek(0)
177+
except Exception:
178+
pass
179+
msvcrt.locking(fd.fileno(), msvcrt.LK_UNLCK, 1)
180+
else:
181+
fcntl.flock(fd.fileno(), fcntl.LOCK_UN)
153182
finally:
154183
fd.close()
155184

liveweb_arena/plugins/openmeteo/templates/common.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,26 @@
88
DOCS_HOME_URL = "https://open-meteo.com/en/docs"
99

1010

11+
def docs_url_from_coord_key(coord_key: str) -> str:
12+
"""
13+
Build an Open-Meteo docs URL for a specific (lat,lon) pair.
14+
15+
Uses query params for cache key uniqueness and repeats them in the hash
16+
fragment for the client-side form state, mirroring City.docs_url().
17+
"""
18+
lat_str, lon_str = (coord_key or "").split(",", 1)
19+
lat = float(lat_str)
20+
lon = float(lon_str)
21+
return (
22+
f"{DOCS_HOME_URL}"
23+
f"?latitude={lat}&longitude={lon}"
24+
f"#latitude={lat}&longitude={lon}"
25+
f"&current=temperature_2m,wind_speed_10m,relative_humidity_2m"
26+
f"&hourly=temperature_2m,precipitation_probability,wind_speed_10m"
27+
f"&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max,sunrise,sunset"
28+
)
29+
30+
1131
def get_collected_location_data(
1232
coord_key: str,
1333
city_name: str,

liveweb_arena/plugins/openmeteo/templates/comparison.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,10 @@ def get_cache_source(cls) -> str:
145145

146146
def get_gt_source(self) -> GTSourceType:
147147
return self.GT_SOURCE
148+
149+
def get_probe_urls(self, validation_info: Dict[str, Any]) -> list[str]:
150+
urls: list[str] = []
151+
city2_url = validation_info.get("city2_url")
152+
if isinstance(city2_url, str) and city2_url.startswith("https://"):
153+
urls.append(city2_url)
154+
return urls

liveweb_arena/plugins/openmeteo/templates/current_weather.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
)
2020
from liveweb_arena.core.gt_collector import GTSourceType
2121

22-
from .common import DOCS_HOME_URL, get_collected_location_data
22+
from .common import DOCS_HOME_URL, docs_url_from_coord_key, get_collected_location_data
2323
from .variables import CITIES, CurrentMetric
2424

2525

@@ -135,3 +135,9 @@ def get_cache_source(cls) -> str:
135135

136136
def get_gt_source(self) -> GTSourceType:
137137
return self.GT_SOURCE
138+
139+
def get_probe_urls(self, validation_info: Dict[str, Any]) -> list[str]:
140+
coord_key = validation_info.get("coord_key", "")
141+
if isinstance(coord_key, str) and "," in coord_key:
142+
return [docs_url_from_coord_key(coord_key)]
143+
return []

liveweb_arena/plugins/openmeteo/templates/forecast_trend.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
)
2323
from liveweb_arena.core.gt_collector import GTSourceType
2424

25-
from .common import DOCS_HOME_URL, get_collected_location_data
25+
from .common import DOCS_HOME_URL, docs_url_from_coord_key, get_collected_location_data
2626
from .variables import CITIES, DailyMetric
2727

2828

@@ -165,3 +165,9 @@ def get_cache_source(cls) -> str:
165165

166166
def get_gt_source(self) -> GTSourceType:
167167
return self.GT_SOURCE
168+
169+
def get_probe_urls(self, validation_info: Dict[str, Any]) -> list[str]:
170+
coord_key = validation_info.get("coord_key", "")
171+
if isinstance(coord_key, str) and "," in coord_key:
172+
return [docs_url_from_coord_key(coord_key)]
173+
return []

liveweb_arena/plugins/openmeteo/templates/hourly_extrema.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
)
2323
from liveweb_arena.core.gt_collector import GTSourceType
2424

25-
from .common import DOCS_HOME_URL, get_collected_location_data, get_today_hourly_series
25+
from .common import DOCS_HOME_URL, docs_url_from_coord_key, get_collected_location_data, get_today_hourly_series
2626
from .variables import CITIES, HourlyMetric
2727

2828

@@ -159,3 +159,9 @@ def get_cache_source(cls) -> str:
159159

160160
def get_gt_source(self) -> GTSourceType:
161161
return self.GT_SOURCE
162+
163+
def get_probe_urls(self, validation_info: Dict[str, Any]) -> list[str]:
164+
coord_key = validation_info.get("coord_key", "")
165+
if isinstance(coord_key, str) and "," in coord_key:
166+
return [docs_url_from_coord_key(coord_key)]
167+
return []

liveweb_arena/plugins/openmeteo/templates/hourly_threshold.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
)
3030
from liveweb_arena.core.gt_collector import GTSourceType
3131

32-
from .common import DOCS_HOME_URL, get_collected_location_data, get_today_hourly_series
32+
from .common import DOCS_HOME_URL, docs_url_from_coord_key, get_collected_location_data, get_today_hourly_series
3333
from .variables import CITIES, HourlyMetric, HOURLY_THRESHOLDS
3434

3535
# Per-metric jitter half-range applied to each base threshold.
@@ -196,3 +196,9 @@ def get_cache_source(cls) -> str:
196196

197197
def get_gt_source(self) -> GTSourceType:
198198
return self.GT_SOURCE
199+
200+
def get_probe_urls(self, validation_info: Dict[str, Any]) -> list[str]:
201+
coord_key = validation_info.get("coord_key", "")
202+
if isinstance(coord_key, str) and "," in coord_key:
203+
return [docs_url_from_coord_key(coord_key)]
204+
return []

liveweb_arena/plugins/openmeteo/templates/hourly_time_of.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
)
3131
from liveweb_arena.core.gt_collector import GTSourceType
3232

33-
from .common import DOCS_HOME_URL, get_collected_location_data, get_today_hourly_pairs
33+
from .common import DOCS_HOME_URL, docs_url_from_coord_key, get_collected_location_data, get_today_hourly_pairs
3434
from .variables import CITIES, HourlyMetric
3535

3636
# Exclude TEMPERATURE — its diurnal cycle (peak ~14:00, min ~05:00) is a
@@ -203,3 +203,9 @@ def get_cache_source(cls) -> str:
203203

204204
def get_gt_source(self) -> GTSourceType:
205205
return self.GT_SOURCE
206+
207+
def get_probe_urls(self, validation_info: Dict[str, Any]) -> list[str]:
208+
coord_key = validation_info.get("coord_key", "")
209+
if isinstance(coord_key, str) and "," in coord_key:
210+
return [docs_url_from_coord_key(coord_key)]
211+
return []

liveweb_arena/plugins/openmeteo/templates/sunrise_sunset.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
)
3333
from liveweb_arena.core.gt_collector import GTSourceType
3434

35-
from .common import DOCS_HOME_URL, get_collected_location_data
35+
from .common import DOCS_HOME_URL, docs_url_from_coord_key, get_collected_location_data
3636
from .variables import CITIES
3737

3838

@@ -188,3 +188,9 @@ def get_cache_source(cls) -> str:
188188

189189
def get_gt_source(self) -> GTSourceType:
190190
return self.GT_SOURCE
191+
192+
def get_probe_urls(self, validation_info: Dict[str, Any]) -> list[str]:
193+
coord_key = validation_info.get("coord_key", "")
194+
if isinstance(coord_key, str) and "," in coord_key:
195+
return [docs_url_from_coord_key(coord_key)]
196+
return []

0 commit comments

Comments
 (0)