ygg 0.1.21__py3-none-any.whl → 0.1.23__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.4
2
2
  Name: ygg
3
- Version: 0.1.21
3
+ Version: 0.1.23
4
4
  Summary: Type-friendly utilities for moving data between Python objects, Arrow, Polars, Pandas, Spark, and Databricks
5
5
  Author: Yggdrasil contributors
6
6
  License: Apache License
@@ -1,9 +1,9 @@
1
- ygg-0.1.21.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
1
+ ygg-0.1.23.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
2
2
  yggdrasil/__init__.py,sha256=6OPibApplA5TF4TeixkQO_qewpaAidYX-fSDvvKYcTI,91
3
3
  yggdrasil/databricks/__init__.py,sha256=aGVve5mpoQtxSK2nfzrexjRPoutCIyaOnKZijkG4_QE,92
4
4
  yggdrasil/databricks/compute/__init__.py,sha256=TVDwPmW2SOmHmnhzZhsvrWbrxZ_lEcgqe3l9BeB-oxM,218
5
- yggdrasil/databricks/compute/cluster.py,sha256=4Pk_s98g1-YL48SGq45CAGj9tQd17qdy__oZEb9d-yw,28067
6
- yggdrasil/databricks/compute/execution_context.py,sha256=_5EaGsfwgq3Dzn3zZdngyNmDS1UXx9AZe47SFDXmukw,18573
5
+ yggdrasil/databricks/compute/cluster.py,sha256=91nBe_Ni0Yb4ECUU-YhSQeMVqy9VKhpWDAp771_1mxQ,28561
6
+ yggdrasil/databricks/compute/execution_context.py,sha256=plutmNa03VSHlXbGeKArtc-QH9ptZrrnss2yXS2pFYA,18584
7
7
  yggdrasil/databricks/compute/remote.py,sha256=sVWBb_1YR-e33on6F5QYMLKwT6end6rolCvN8HM-9Qw,1212
8
8
  yggdrasil/databricks/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  yggdrasil/databricks/jobs/config.py,sha256=8Slfw4Wl7vu0kIlaUUqVqjjOgPwuULoo0rroENCbC20,11494
@@ -30,7 +30,7 @@ yggdrasil/pyutils/callable_serde.py,sha256=6Pw9XO2JMUSmzGp1Vj3-AN1Nhave09fNWyn1x
30
30
  yggdrasil/pyutils/exceptions.py,sha256=Kt4xY4jPv-Ld8vBJaiQy51xbewD6Q-3bZkaa3THsFNM,2891
31
31
  yggdrasil/pyutils/modules.py,sha256=r3C4TRA8C_fUTltITV0VJY0pbIdxdV1op2z8BX0FgJo,10201
32
32
  yggdrasil/pyutils/parallel.py,sha256=L2r4_iumOzDk7omWVBPYmT5U8n7suOdv1AFpa_ghHSY,5902
33
- yggdrasil/pyutils/python_env.py,sha256=KqFnlKcpPfskLWzSiOkEjo71KQrLmg80aRfX_wo5_QA,47892
33
+ yggdrasil/pyutils/python_env.py,sha256=pjon-G9N-lXx64905_hLaWPa3ZAoy0AhQX3Kg-ucym8,48417
34
34
  yggdrasil/pyutils/retry.py,sha256=1zjascEsffvnkVRdHhdeoenk6tBPrzrj4VhBltbhBeU,10390
35
35
  yggdrasil/requests/__init__.py,sha256=THJz1IoZYQccwmXcQR3N8D-uWxCkfMtgeXDhONdERR8,41
36
36
  yggdrasil/requests/msal.py,sha256=ucnN45iZZpbXkByw212PX4shH4g0EeyrW8JEmfimWtY,5861
@@ -49,8 +49,8 @@ yggdrasil/types/cast/registry.py,sha256=-88mq-U1pDSGbEC9PRY0zJCzloyBodXgeSRBPb6h
49
49
  yggdrasil/types/cast/spark_cast.py,sha256=IHthM78dugabGXxNNW9sSHn-olDwzXcFdIFcPo9IiXU,23021
50
50
  yggdrasil/types/cast/spark_pandas_cast.py,sha256=8PgJItF_XbyBcNuBnXkMQU3PBy3sAPEXZT9SXL2WbU4,4200
51
51
  yggdrasil/types/cast/spark_polars_cast.py,sha256=ba1UOvY1ouGCro1Np9slXmJ4TEyWnUtwVEAwxGvPLlk,8336
52
- ygg-0.1.21.dist-info/METADATA,sha256=CiwzGJO6wNPbre6ZTW21BtM3_HxPXFJF6pkzyL_84fY,19204
53
- ygg-0.1.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
54
- ygg-0.1.21.dist-info/entry_points.txt,sha256=D0JDw2s-ZfU1GjXfniv6PvXwcyn5v9WBk4ya623Cti8,71
55
- ygg-0.1.21.dist-info/top_level.txt,sha256=iBe9Kk4VIVbLpgv_p8OZUIfxgj4dgJ5wBg6vO3rigso,10
56
- ygg-0.1.21.dist-info/RECORD,,
52
+ ygg-0.1.23.dist-info/METADATA,sha256=KMkpdOJCjfa2pTkNNpIVPDHfIVGIhKHFu5T73Ken2XY,19204
53
+ ygg-0.1.23.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
54
+ ygg-0.1.23.dist-info/entry_points.txt,sha256=D0JDw2s-ZfU1GjXfniv6PvXwcyn5v9WBk4ya623Cti8,71
55
+ ygg-0.1.23.dist-info/top_level.txt,sha256=iBe9Kk4VIVbLpgv_p8OZUIfxgj4dgJ5wBg6vO3rigso,10
56
+ ygg-0.1.23.dist-info/RECORD,,
@@ -390,6 +390,8 @@ class Cluster(WorkspaceService):
390
390
  python_version: Optional[Union[str, tuple[int, ...]]] = None,
391
391
  **kwargs
392
392
  ):
393
+ pip_settings = PipIndexSettings.default_settings()
394
+
393
395
  if kwargs:
394
396
  details = ClusterDetails(**{
395
397
  **details.as_shallow_dict(),
@@ -421,6 +423,13 @@ class Cluster(WorkspaceService):
421
423
  if details.is_single_node is not None and details.kind is None:
422
424
  details.kind = Kind.CLASSIC_PREVIEW
423
425
 
426
+ if pip_settings.extra_index_urls:
427
+ if details.spark_env_vars is None:
428
+ details.spark_env_vars = {}
429
+ str_urls = " ".join(pip_settings.extra_index_urls)
430
+ details.spark_env_vars["UV_EXTRA_INDEX_URL"] = details.spark_env_vars.get("UV_INDEX", str_urls)
431
+ details.spark_env_vars["PIP_EXTRA_INDEX_URL"] = details.spark_env_vars.get("PIP_EXTRA_INDEX_URL", str_urls)
432
+
424
433
  return details
425
434
 
426
435
  def create_or_update(
@@ -120,7 +120,7 @@ class ExecutionContext:
120
120
  cmd = r"""import glob
121
121
  import json
122
122
  import os
123
- from yggdrasil.pyutils import PythonEnv
123
+ from yggdrasil.pyutils.python_env import PythonEnv
124
124
 
125
125
  current_env = PythonEnv.get_current()
126
126
  meta = {}
@@ -40,6 +40,9 @@ _LOCKS_GUARD: threading.RLock = threading.RLock()
40
40
 
41
41
  # Installed into newly created envs (and on create() upsert when env already exists)
42
42
  DEFAULT_CREATE_PACKAGES: tuple[str, ...] = ("uv", "ygg")
43
+ EXCLUDED_PACKAGES = {
44
+ "python-apt"
45
+ }
43
46
 
44
47
 
45
48
  def _is_windows() -> bool:
@@ -464,7 +467,7 @@ class PythonEnv:
464
467
 
465
468
  env_obj = cls(root)
466
469
  if not env_obj.exists():
467
- raise PythonEnvError(f"Created env but python missing: {env_obj.python}")
470
+ raise PythonEnvError(f"Created env but python missing: {env_obj.python_executable}")
468
471
 
469
472
  env_obj.update(
470
473
  packages=install_pkgs,
@@ -560,17 +563,18 @@ class PythonEnv:
560
563
  )
561
564
 
562
565
  extra = list(pip_args or [])
563
- base = [uv, "pip", "install", "--python", str(env_obj.python)]
566
+ base_uv = [uv, "pip", "install", "--python", str(env_obj.python_executable)]
564
567
 
565
568
  if upgrade_pip:
566
569
  log.info("upgrading pip in env: %s", str(env_obj.root))
567
- _run_cmd(base + ["-U", "pip"] + extra, cwd=cwd, env=env, check=check)
570
+ _run_cmd(base_uv + ["-U", "pip"] + extra, cwd=cwd, env=env, check=check)
568
571
 
569
572
  if packages:
570
573
  pkgs = [packages] if isinstance(packages, str) else list(packages)
574
+
571
575
  if pkgs:
572
576
  log.info("installing packages into env %s: %s", str(env_obj.root), pkgs)
573
- _run_cmd(base + ["-U"] + pkgs + extra, cwd=cwd, env=env, check=check)
577
+ _run_cmd(base_uv + ["-U"] + pkgs + extra, cwd=cwd, env=env, check=check)
574
578
 
575
579
  if requirements:
576
580
  # requirements can be:
@@ -598,7 +602,7 @@ class PythonEnv:
598
602
  raise PythonEnvError("requirements must be a path-like string/Path or raw requirements text")
599
603
 
600
604
  log.info("installing requirements into env %s: %s", str(env_obj.root), str(req_path))
601
- _run_cmd(base + ["-U", "-r", str(req_path)] + extra, cwd=cwd, env=env, check=check)
605
+ _run_cmd(base_uv + ["-U", "-r", str(req_path)] + extra, cwd=cwd, env=env, check=check)
602
606
 
603
607
  finally:
604
608
  if tmp_ctx is not None:
@@ -634,12 +638,12 @@ class PythonEnv:
634
638
  return n if n else str(self.root)
635
639
 
636
640
  @property
637
- def python(self) -> Path:
641
+ def python_executable(self) -> Path:
638
642
  exe = "python.exe" if _is_windows() else "python"
639
643
  return self.bindir / exe
640
644
 
641
645
  def exists(self) -> bool:
642
- return self.python.exists()
646
+ return self.python_executable.exists()
643
647
 
644
648
  @property
645
649
  def version(self) -> str:
@@ -714,7 +718,7 @@ class PythonEnv:
714
718
 
715
719
  frozen_text: Optional[str] = None
716
720
  if keep_packages and self.exists():
717
- p = _run_cmd([uv, "pip", "freeze", "--python", str(self.python)], check=True)
721
+ p = _run_cmd([uv, "pip", "freeze", "--python", str(self.python_executable)], check=True)
718
722
  frozen_text = p.stdout or ""
719
723
 
720
724
  with _locked_env(root):
@@ -731,7 +735,7 @@ class PythonEnv:
731
735
 
732
736
  new_env = self.__class__(root)
733
737
  if not new_env.exists():
734
- raise PythonEnvError(f"Recreated env but python missing: {new_env.python}")
738
+ raise PythonEnvError(f"Recreated env but python missing: {new_env.python_executable}")
735
739
 
736
740
  if keep_packages and frozen_text and frozen_text.strip():
737
741
  import datetime as _dt
@@ -740,7 +744,7 @@ class PythonEnv:
740
744
  req_path = parent / f".{root.name}.freeze-{ts}.txt"
741
745
  req_path.write_text(frozen_text, encoding="utf-8")
742
746
  try:
743
- _run_cmd([uv, "pip", "install", "--python", str(new_env.python), "-r", str(req_path)], check=True)
747
+ _run_cmd([uv, "pip", "install", "--python", str(new_env.python_executable), "-r", str(req_path)], check=True)
744
748
  finally:
745
749
  try:
746
750
  req_path.unlink(missing_ok=True)
@@ -787,12 +791,12 @@ class PythonEnv:
787
791
  """
788
792
  if not python_versions:
789
793
  return self.export_requirements_matrix(
790
- python_versions=[self.python],
794
+ python_versions=[self.python_executable],
791
795
  out_dir=out_dir, base_name=base_name, include_frozen=include_frozen,
792
796
  include_input=include_input, check=check, buffers=buffers or {},
793
797
  uv_upgrade=uv_upgrade, uv_user=uv_user, uv_index_url=uv_index_url,
794
798
  uv_extra_pip_args=uv_extra_pip_args
795
- )[str(self.python)]
799
+ )[str(self.python_executable)]
796
800
 
797
801
  def _slug(s: str) -> str:
798
802
  s = (s or "").strip()
@@ -806,7 +810,7 @@ class PythonEnv:
806
810
  raise PythonEnvError("python_versions cannot be empty")
807
811
 
808
812
  if not self.exists():
809
- raise PythonEnvError(f"Python executable not found in env: {self.python}")
813
+ raise PythonEnvError(f"Python executable not found in env: {self.python_executable}")
810
814
 
811
815
  uv = self.__class__.ensure_uv(
812
816
  check=check,
@@ -878,7 +882,12 @@ print("RESULT:" + json.dumps(top_level))""".strip()
878
882
  if not isinstance(top_level, list) or not all(isinstance(x, str) for x in top_level):
879
883
  raise PythonEnvError(f"Unexpected top-level requirements payload: {top_level!r}")
880
884
 
881
- req_in_text = "\n".join(top_level) + "\n"
885
+ # exclude python-apt (yanked/unmaintained; not usable on Databricks)
886
+ _pyapt_re = re.compile(r"^\s*python-apt(\s*(?:==|~=|!=|<=|>=|<|>|\[)|\s*(?:;|$))", re.IGNORECASE)
887
+
888
+ filtered = [line for line in top_level if not _pyapt_re.match(line)]
889
+
890
+ req_in_text = "\n".join(filtered) + "\n"
882
891
  req_in_path.write_text(req_in_text, encoding="utf-8")
883
892
  if buffers is not None:
884
893
  buffers[f"{base_name}.in"] = req_in_text
@@ -887,7 +896,7 @@ print("RESULT:" + json.dumps(top_level))""".strip()
887
896
  frozen_path = out_root / f"{base_name}.frozen.txt"
888
897
  if include_frozen:
889
898
  log.info("exporting frozen requirements: %s", str(frozen_path))
890
- p = _run_cmd([uv, "pip", "freeze", "--python", str(self.python)], check=check)
899
+ p = _run_cmd([uv, "pip", "freeze", "--python", str(self.python_executable)], check=check)
891
900
  frozen_text = p.stdout or ""
892
901
  if write_files:
893
902
  frozen_path.write_text(frozen_text, encoding="utf-8")
@@ -944,8 +953,8 @@ print("RESULT:" + json.dumps(top_level))""".strip()
944
953
  # pick interpreter (default = env python)
945
954
  if python is None:
946
955
  if not self.exists():
947
- raise PythonEnvError(f"Python executable not found in env: {self.python}")
948
- py = self.python
956
+ raise PythonEnvError(f"Python executable not found in env: {self.python_executable}")
957
+ py = self.python_executable
949
958
  else:
950
959
  py = Path(python).expanduser().resolve() if isinstance(python, Path) else Path(str(python)).expanduser().resolve()
951
960
  if not py.exists():
@@ -1277,7 +1286,7 @@ print("RESULT:" + json.dumps(top_level))""".strip()
1277
1286
  require_python=require_python,
1278
1287
  dedupe=True,
1279
1288
  ):
1280
- py = str(e.python) if e.exists() else "-"
1289
+ py = str(e.python_executable) if e.exists() else "-"
1281
1290
  print(f"{e.name}\t{e.root}\t{py}")
1282
1291
  return 0
1283
1292
 
@@ -1323,7 +1332,7 @@ print("RESULT:" + json.dumps(top_level))""".strip()
1323
1332
  if args.cmd == "current":
1324
1333
  cur = cls.get_current()
1325
1334
  print(f"root={cur.root}")
1326
- print(f"python={cur.python}")
1335
+ print(f"python={cur.python_executable}")
1327
1336
  return 0
1328
1337
 
1329
1338
  raise PythonEnvError(f"Unknown command: {args.cmd}")
File without changes