ygg 0.1.32__py3-none-any.whl → 0.1.34__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.
@@ -330,6 +330,12 @@ class Workspace:
330
330
  if local_cache:
331
331
  os.remove(local_cache)
332
332
 
333
+ @property
334
+ def safe_host(self):
335
+ if not self.host:
336
+ return self.connect().host
337
+ return self.host
338
+
333
339
  @property
334
340
  def current_user(self):
335
341
  """Return the current Databricks user.
@@ -625,7 +631,12 @@ class Workspace:
625
631
  """
626
632
  from ..compute.cluster import Cluster
627
633
 
628
- return Cluster(workspace=self, cluster_id=cluster_id, cluster_name=cluster_name, **kwargs)
634
+ return Cluster(
635
+ workspace=self,
636
+ cluster_id=cluster_id,
637
+ cluster_name=cluster_name,
638
+ **kwargs
639
+ )
629
640
 
630
641
  # ---------------------------------------------------------------------------
631
642
  # Workspace-bound base class
@@ -7,12 +7,13 @@ import dis
7
7
  import importlib
8
8
  import inspect
9
9
  import json
10
+ import os
10
11
  import struct
11
12
  import sys
12
13
  import zlib
13
14
  from dataclasses import dataclass
14
15
  from pathlib import Path
15
- from typing import Any, Callable, Dict, Optional, Set, Tuple, TypeVar, Union
16
+ from typing import Any, Callable, Dict, Optional, Set, Tuple, TypeVar, Union, Iterable
16
17
 
17
18
  import dill
18
19
 
@@ -378,6 +379,8 @@ class CallableSerde:
378
379
  prefer: str = "import", # "import" | "dill"
379
380
  dump_env: str = "none", # "none" | "globals" | "closure" | "both"
380
381
  filter_used_globals: bool = True,
382
+ env_keys: Optional[Iterable[str]] = None,
383
+ env_variables: Optional[Dict[str, str]] = None,
381
384
  ) -> Dict[str, Any]:
382
385
  """Serialize the callable into a dict for transport.
383
386
 
@@ -385,6 +388,8 @@ class CallableSerde:
385
388
  prefer: Preferred serialization kind.
386
389
  dump_env: Environment payload selection.
387
390
  filter_used_globals: Filter globals to referenced names.
391
+ env_keys: environment keys
392
+ env_variables: environment key values
388
393
 
389
394
  Returns:
390
395
  Serialized payload dict.
@@ -411,6 +416,17 @@ class CallableSerde:
411
416
  self._dill_b64 = base64.b64encode(payload).decode("ascii")
412
417
  out["dill_b64"] = self._dill_b64
413
418
 
419
+ env_variables = env_variables or {}
420
+ if env_keys:
421
+ for env_key in env_keys:
422
+ existing = os.getenv(env_key)
423
+
424
+ if existing:
425
+ env_variables[env_key] = existing
426
+
427
+ if env_variables:
428
+ out["osenv"] = env_variables
429
+
414
430
  if dump_env != "none":
415
431
  if self.fn is None:
416
432
  raise ValueError("dump_env requested but fn is not present.")
@@ -523,6 +539,8 @@ class CallableSerde:
523
539
  byte_limit: int = 256_000,
524
540
  dump_env: str = "none", # "none" | "globals" | "closure" | "both"
525
541
  filter_used_globals: bool = True,
542
+ env_keys: Optional[Iterable[str]] = None,
543
+ env_variables: Optional[Dict[str, str]] = None,
526
544
  ) -> str:
527
545
  """
528
546
  Returns Python code string to execute in another interpreter.
@@ -536,6 +554,8 @@ class CallableSerde:
536
554
  prefer=prefer,
537
555
  dump_env=dump_env,
538
556
  filter_used_globals=filter_used_globals,
557
+ env_keys=env_keys,
558
+ env_variables=env_variables
539
559
  )
540
560
  serde_json = json.dumps(serde_dict, ensure_ascii=False)
541
561
 
@@ -545,7 +565,7 @@ class CallableSerde:
545
565
 
546
566
  # NOTE: plain string template + replace. No f-string. No brace escaping.
547
567
  template = r"""
548
- import base64, json, sys, struct, zlib, importlib, dis
568
+ import base64, json, sys, struct, zlib, importlib, dis, os
549
569
  import dill
550
570
 
551
571
  RESULT_TAG = __RESULT_TAG__
@@ -626,6 +646,11 @@ else:
626
646
  else:
627
647
  fn = dill.loads(base64.b64decode(serde["dill_b64"]))
628
648
 
649
+ osenv = serde.get("osenv")
650
+ if osenv:
651
+ for k, v in osenv.items():
652
+ os.environ[k] = v
653
+
629
654
  env_b64 = serde.get("env_b64")
630
655
  if env_b64:
631
656
  env = dill.loads(base64.b64decode(env_b64))
@@ -0,0 +1,176 @@
1
+ from __future__ import annotations
2
+
3
+ import heapq
4
+ import itertools
5
+ import threading
6
+ import time
7
+ from collections.abc import MutableMapping, Iterator
8
+ from dataclasses import dataclass
9
+ from typing import Callable, Generic, Optional, TypeVar, Dict, Tuple
10
+
11
+ K = TypeVar("K")
12
+ V = TypeVar("V")
13
+
14
+
15
+ __all__ = [
16
+ "ExpiringDict"
17
+ ]
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class _Entry(Generic[V]):
22
+ value: V
23
+ expires_at: float # monotonic timestamp
24
+
25
+
26
+ class ExpiringDict(MutableMapping[K, V]):
27
+ """
28
+ Dict with per-key TTL expiration.
29
+
30
+ - Uses time.monotonic() (safe against system clock changes)
31
+ - O(log n) cleanup amortized via a min-heap of expirations
32
+ - Overwrites are handled (stale heap entries are ignored)
33
+ - Optional refresh_on_get: touching a key extends its TTL
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ default_ttl: Optional[float] = None,
39
+ *,
40
+ refresh_on_get: bool = False,
41
+ on_expire: Optional[Callable[[K, V], None]] = None,
42
+ thread_safe: bool = False,
43
+ ) -> None:
44
+ """
45
+ default_ttl: seconds, if provided used when ttl isn't passed to set()
46
+ refresh_on_get: if True, get()/__getitem__ extends TTL using default_ttl
47
+ on_expire: callback(key, value) called when an item is expired during cleanup
48
+ thread_safe: wrap operations in an RLock (extra overhead, but safe)
49
+ """
50
+ self.default_ttl = default_ttl
51
+ self.refresh_on_get = refresh_on_get
52
+ self.on_expire = on_expire
53
+
54
+ self._store: Dict[K, _Entry[V]] = {}
55
+ self._heap: list[Tuple[float, int, K]] = [] # (expires_at, seq, key)
56
+ self._seq = itertools.count()
57
+
58
+ self._lock = threading.RLock() if thread_safe else None
59
+
60
+ def _now(self) -> float:
61
+ return time.monotonic()
62
+
63
+ def _with_lock(self):
64
+ # tiny helper to avoid repeating if/else everywhere
65
+ return self._lock or _NoopLock()
66
+
67
+ def _prune(self) -> None:
68
+ """Remove expired entries. Ignores stale heap rows from overwrites."""
69
+ now = self._now()
70
+ while self._heap and self._heap[0][0] <= now:
71
+ exp, _, key = heapq.heappop(self._heap)
72
+ entry = self._store.get(key)
73
+ if entry is None:
74
+ continue
75
+ # Only expire if this heap expiry matches current entry expiry
76
+ if entry.expires_at == exp:
77
+ del self._store[key]
78
+ if self.on_expire:
79
+ self.on_expire(key, entry.value)
80
+
81
+ def set(self, key: K, value: V, ttl: Optional[float] = None) -> None:
82
+ with self._with_lock():
83
+ self._prune()
84
+ if ttl is None:
85
+ ttl = self.default_ttl
86
+ if ttl is None:
87
+ # no expiration
88
+ expires_at = float("inf")
89
+ else:
90
+ if ttl <= 0:
91
+ # immediate expiration: just delete if exists
92
+ self._store.pop(key, None)
93
+ return
94
+ expires_at = self._now() + ttl
95
+
96
+ self._store[key] = _Entry(value=value, expires_at=expires_at)
97
+ heapq.heappush(self._heap, (expires_at, next(self._seq), key))
98
+
99
+ # --- MutableMapping interface ---
100
+ def __setitem__(self, key: K, value: V) -> None:
101
+ # Uses default_ttl (if any)
102
+ self.set(key, value, ttl=self.default_ttl)
103
+
104
+ def __getitem__(self, key: K) -> V:
105
+ with self._with_lock():
106
+ self._prune()
107
+ entry = self._store[key] # may raise KeyError
108
+ if entry.expires_at <= self._now():
109
+ # edge case: expired but not yet pruned (rare)
110
+ del self._store[key]
111
+ raise KeyError(key)
112
+
113
+ if self.refresh_on_get:
114
+ if self.default_ttl is None:
115
+ raise ValueError("refresh_on_get=True requires default_ttl")
116
+ # refresh TTL
117
+ self.set(key, entry.value, ttl=self.default_ttl)
118
+ return entry.value
119
+
120
+ return entry.value
121
+
122
+ def get(self, key: K, default: Optional[V] = None) -> Optional[V]:
123
+ try:
124
+ return self[key]
125
+ except KeyError:
126
+ return default
127
+
128
+ def __delitem__(self, key: K) -> None:
129
+ with self._with_lock():
130
+ self._prune()
131
+ del self._store[key]
132
+ # heap keeps stale rows; they'll be ignored during prune
133
+
134
+ def __iter__(self) -> Iterator[K]:
135
+ with self._with_lock():
136
+ self._prune()
137
+ return iter(list(self._store.keys()))
138
+
139
+ def __len__(self) -> int:
140
+ with self._with_lock():
141
+ self._prune()
142
+ return len(self._store)
143
+
144
+ def __contains__(self, key: object) -> bool:
145
+ with self._with_lock():
146
+ self._prune()
147
+ if key in self._store:
148
+ entry = self._store[key] # type: ignore[index]
149
+ return entry.expires_at > self._now()
150
+ return False
151
+
152
+ def cleanup(self) -> int:
153
+ """Force prune and return number of remaining items."""
154
+ with self._with_lock():
155
+ self._prune()
156
+ return len(self._store)
157
+
158
+ def items(self):
159
+ with self._with_lock():
160
+ self._prune()
161
+ return [(k, e.value) for k, e in self._store.items()]
162
+
163
+ def keys(self):
164
+ with self._with_lock():
165
+ self._prune()
166
+ return list(self._store.keys())
167
+
168
+ def values(self):
169
+ with self._with_lock():
170
+ self._prune()
171
+ return [e.value for e in self._store.values()]
172
+
173
+
174
+ class _NoopLock:
175
+ def __enter__(self): return self
176
+ def __exit__(self, exc_type, exc, tb): return False
yggdrasil/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.32"
1
+ __version__ = "0.1.34"
File without changes