Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ Changelog follow https://keepachangelog.com/ format.

## [Unreleased]

## [1.15.0] - 2026-10-29

* `epath`:
* Support GCS Zonal Rapid (`gs://`) buckets via `fsspec`
(`gcsfs>=2026.4.0`).
* `edc`:
* [Fix] `edc.ContextVar`: `default_factory` is now lazily resolved
per-context, so each thread / coroutine gets its own independent
Expand Down Expand Up @@ -537,7 +542,8 @@ Changelog follow https://keepachangelog.com/ format.

<!-- mdlint off(LINK_UNUSED_ID) -->

[Unreleased]: https://github.com/google/etils/compare/v1.14.0...HEAD
[Unreleased]: https://github.com/google/etils/compare/v1.15.0...HEAD
[1.15.0]: https://github.com/google/etils/compare/v1.14.0...v1.15.0
[1.14.0]: https://github.com/google/etils/compare/v1.13.0...v1.14.0
[1.13.0]: https://github.com/google/etils/compare/v1.12.2...v1.13.0
[1.12.2]: https://github.com/google/etils/compare/v1.12.1...v1.12.2
Expand Down
2 changes: 1 addition & 1 deletion etils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

# A new PyPI release will be pushed everytime `__version__` is increased
# When changing this, also update the CHANGELOG.md
__version__ = '1.14.0'
__version__ = '1.15.0'

# Do NOT add anything to this file. This is left empty on purpose.
# Users should import subprojects directly.
12 changes: 6 additions & 6 deletions etils/ecolab/auto_display_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def _reraise_error(fn: _T) -> _T:
@functools.wraps(fn) # pyrefly: ignore[bad-argument-type]
def decorated(self, node: ast.AST):
try:
return fn(self, node) # pytype: disable=wrong-arg-types
return fn(self, node) # pyrefly: ignore[not-callable]
except Exception as e: # pylint: disable=broad-exception-caught
code = '\n'.join(self.lines_recorder.last_lines)
print(f'Error for code:\n-----\n{code}\n-----')
Expand Down Expand Up @@ -260,7 +260,7 @@ def _maybe_display(
if line_info.print_line:
fn_kwargs.append(ast.keyword('line_code', _unparse_line(node)))

node.value = ast.Call( # pytype: disable=wrong-arg-types
node.value = ast.Call(
func=_parse_expr('ecolab.auto_display_utils._display_and_return'), # pyrefly: ignore[bad-argument-type]
args=[node.value],
keywords=fn_kwargs,
Expand Down Expand Up @@ -289,7 +289,7 @@ def _is_alias_stmt(self, node: ast.AST) -> bool:
def visit_Assert(self, node: ast.Assert) -> None: # pylint: disable=invalid-name
# Wrap assert so the `node.value` match the expected API
node = _WrapAssertNode(node)
node = self._maybe_display(node) # pytype: disable=wrong-arg-types
node = self._maybe_display(node) # pyrefly: ignore[bad-argument-type, bad-assignment]
assert isinstance(node, _WrapAssertNode)
node = node._node # Unwrap # pylint: disable=protected-access
return node # pyrefly: ignore[bad-return]
Expand Down Expand Up @@ -351,12 +351,12 @@ def _has_trailing_semicolon(
)

# Extract the lines of the statement
line_num = node.end_lineno - 1 # pytype: disable=attribute-error
line_num = node.end_lineno - 1 # pyrefly: ignore[missing-attribute]
last_line = code_lines[line_num] # lineno starts at `1`

# `node.end_col_offset` is in bytes, so UTF-8 characters count 3.
last_part_of_line = last_line.encode('utf-8')
last_part_of_line = last_part_of_line[node.end_col_offset :] # pytype: disable=attribute-error
last_part_of_line = last_part_of_line[node.end_col_offset :]
last_part_of_line = last_part_of_line.decode('utf-8')

# Check if the last character is a `;` token
Expand Down Expand Up @@ -386,7 +386,7 @@ def _detect_trailing_regex() -> re.Pattern[str]:
# * `; a; b`
# * `; a=1`

available_letters = ''.join(sorted(_Options.all_letters)) # pytype: disable=wrong-arg-types
available_letters = ''.join(sorted(_Options.all_letters))
return re.compile(
' *; *' # Trailing `;` (surrounded by spaces)
f'(?P<options>[{available_letters}]*)?' # Optionally a `option` letter
Expand Down
2 changes: 1 addition & 1 deletion etils/ecolab/cell_autoreload.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def _pre_run_cell_maybe_reload(
'reload_recursive': False,
'collapse_prefix': f'Autoreload ({len(modules_to_reload)} modules): ',
}
with adhoc_imports.adhoc(**adhoc_kwargs): # pyrefly: ignore[bad-argument-type]
with adhoc_imports.adhoc(**adhoc_kwargs):
for module in modules_to_reload:
importlib.import_module(module)

Expand Down
2 changes: 1 addition & 1 deletion etils/ecolab/inplace_reload.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def update(self, old, new):
case types.FunctionType(), types.FunctionType():
return self._update_function(old, new)
case types.MethodType(), types.MethodType():
return self._update_function(old.__func__, new.__func__) # pytype: disable=wrong-arg-types
return self._update_function(old.__func__, new.__func__)
case property(), property():
return self._update_property(old, new)

Expand Down
96 changes: 47 additions & 49 deletions etils/ecolab/lazy_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def print_current_imports() -> None:
import threading
import time
import timeit
import tomllib # pytype: disable=import-error
import tomllib
import traceback
import typing # Note we do not import `Any`, `TypeVar`,...
import types
Expand All @@ -132,72 +132,71 @@ def print_current_imports() -> None:

with _builder.replace_imports(is_std=False):
# pylint: disable=g-import-not-at-top,unused-import,reimported
# pytype: disable=import-error
# ====== Etils ======
from etils import array_types
from etils import ecolab
from etils import edc
from etils import edc # pyrefly: ignore[missing-module-attribute]
from etils import enp
from etils import epath
from etils import epy
from etils import etqdm
from etils import etree
from etils import exm
from etils import exm # pyrefly: ignore[missing-module-attribute]
from etils import g3_utils
from etils.ecolab import lazy_imports
# ====== Common third party ======
from absl import app
from absl import flags
import apache_beam as beam
import bagz
import chex
import dataclass_array as dca
import apache_beam as beam # pyrefly: ignore[missing-import]
import bagz # pyrefly: ignore[missing-import]
import chex # pyrefly: ignore[missing-import]
import dataclass_array as dca # pyrefly: ignore[missing-import]
import einops
import fiddle as fdl
import flask
import flax
from flax import linen as nn
from flax import nnx
import functorch
import gin
import grain.python as grain
import graphviz
import imageio
import fiddle as fdl # pyrefly: ignore[missing-import]
import flask # pyrefly: ignore[missing-import]
import flax # pyrefly: ignore[missing-import]
from flax import linen as nn # pyrefly: ignore[missing-import]
from flax import nnx # pyrefly: ignore[missing-import]
import functorch # pyrefly: ignore[missing-import]
import gin # pyrefly: ignore[missing-import]
import grain.python as grain # pyrefly: ignore[missing-import]
import graphviz # pyrefly: ignore[missing-import]
import imageio # pyrefly: ignore[missing-import]
import immutabledict
# Even though `import ipywidgets as widgets` is the common alias, widgets
# is likely too ambiguous.
import ipywidgets
import jax
from jax import numpy as jnp
import jaxtyping
import lark
import jax # pyrefly: ignore[missing-import]
from jax import numpy as jnp # pyrefly: ignore[missing-import]
import jaxtyping # pyrefly: ignore[missing-import]
import lark # pyrefly: ignore[missing-import]
import matplotlib
import matplotlib as mpl # Standard alias
from matplotlib import pyplot as plt
import mcp
import mcp # pyrefly: ignore[missing-import]
import mediapy as media
import ml_collections
import networkx as nx
import ml_collections # pyrefly: ignore[missing-import]
import networkx as nx # pyrefly: ignore[missing-source-for-stubs]
import numpy as np
import optax
import orbax
from orbax import checkpoint as ocp
from orbax.checkpoint.experimental import v1 as ocp_v1
from orbax import export as oex
import pandas as pd
import optax # pyrefly: ignore[missing-import]
import orbax # pyrefly: ignore[missing-import]
from orbax import checkpoint as ocp # pyrefly: ignore[missing-import]
from orbax.checkpoint.experimental import v1 as ocp_v1 # pyrefly: ignore[missing-import]
from orbax import export as oex # pyrefly: ignore[missing-import]
import pandas as pd # pyrefly: ignore[missing-import]
import PIL
from PIL import Image # Common alias
import pycolmap
import scipy
import seaborn as sns
import sklearn
import tensorflow as tf
import tensorflow.experimental.numpy as tnp
import tensorflow_datasets as tfds
import torch
import pycolmap # pyrefly: ignore[missing-import]
import scipy # pyrefly: ignore[missing-import]
import seaborn as sns # pyrefly: ignore[missing-source-for-stubs]
import sklearn # pyrefly: ignore[missing-import]
import tensorflow as tf # pyrefly: ignore[missing-source-for-stubs]
import tensorflow.experimental.numpy as tnp # pyrefly: ignore[missing-import]
import tensorflow_datasets as tfds # pyrefly: ignore[missing-import]
import torch # pyrefly: ignore[missing-import]
# from torch import nn # Collision with flax.linen
import torchtext
import torchvision
import torchtext # pyrefly: ignore[missing-import]
import torchvision # pyrefly: ignore[missing-import]
import tqdm
# tqdm import also trigger additional imports.
# TODO(epot): Currently pylance might not infer `tqdm.auto` match
Expand All @@ -206,18 +205,17 @@ def print_current_imports() -> None:
tqdm.auto # pylint: disable=pointless-statement
tqdm.notebook # pylint: disable=pointless-statement
import tree
import typeguard
import typeguard # pyrefly: ignore[missing-import]
import typing_extensions
import plotly
from plotly import express as px
from plotly import graph_objects as go
import plotly # pyrefly: ignore[missing-import]
from plotly import express as px # pyrefly: ignore[missing-import]
from plotly import graph_objects as go # pyrefly: ignore[missing-import]
from pydantic import v1 as pydantic
import requests
import sunds
import visu3d as v3d
from xmanager.contrib import flow as xmflow
import sunds # pyrefly: ignore[missing-import]
import visu3d as v3d # pyrefly: ignore[missing-import]
from xmanager.contrib import flow as xmflow # pyrefly: ignore[missing-import]
from xmanager import xm
# pytype: enable=import-error
# pylint: enable=g-import-not-at-top,unused-import,reimported


Expand Down
10 changes: 5 additions & 5 deletions etils/ecolab/lazy_imports_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ def test_lazy_imports():
# lazy_imports can be imported but do not appear in `print_current_imports`
from etils.ecolab.lazy_imports import lazy_imports # pylint: disable=g-import-not-at-top

assert not lazy_imports._etils_state.module_loaded # pytype: disable=module-attr
assert not lazy_imports._etils_state.module_loaded # pyrefly: ignore[missing-attribute]
_ = lazy_imports.print_current_imports # Trigger import
assert lazy_imports._etils_state.module_loaded # pytype: disable=module-attr
assert lazy_imports._etils_state.module_loaded # pyrefly: ignore[missing-attribute]
del lazy_imports

assert ecolab.lazy_utils.current_import_statements(
Expand All @@ -75,16 +75,16 @@ def test_lazy_imports():
def test_lazy_imports_mutate():
from etils.lazy_imports import enp # pylint: disable=g-import-not-at-top

assert not enp._etils_state.module_loaded # pytype: disable=module-attr
assert not enp._etils_state.module_loaded # pyrefly: ignore[missing-attribute]
_ = enp.NpModule
assert enp._etils_state.module_loaded # pytype: disable=module-attr
assert enp._etils_state.module_loaded # pyrefly: ignore[missing-attribute]

enp.some_value = 123 # pyrefly: ignore[missing-attribute]

# Mutating lazy import is propagated to the shared instance.
from etils import enp as enp2 # pylint: disable=g-import-not-at-top

assert enp2.some_value == 123 # pytype: disable=module-attr
assert enp2.some_value == 123 # pyrefly: ignore[missing-attribute]


def test_lazy_imports_built_in():
Expand Down
14 changes: 5 additions & 9 deletions etils/ecolab/patch_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@
def set_verbose() -> None:
"""Log stderr & `absl.logging` in Colab (filtered by default)."""
# pylint: disable=g-import-not-at-top
# pytype: disable=import-error
from absl import logging
from colabtools import googlelog
# pytype: enable=import-error
from colabtools import googlelog # pyrefly: ignore[missing-import]
# pylint: enable=g-import-not-at-top

logging.set_verbosity(logging.INFO)
Expand All @@ -51,13 +49,11 @@ def patch_graphviz() -> None:
Calling this function fix the behavior.
"""
# pylint: disable=g-import-not-at-top
# pytype: disable=import-error
from colabtools import proto
from colabtools import publish
from colabtools import stubby
from colabtools import proto # pyrefly: ignore[missing-import]
from colabtools import publish # pyrefly: ignore[missing-import]
from colabtools import stubby # pyrefly: ignore[missing-import]

import graphviz
# pytype: enable=import-error
import graphviz # pyrefly: ignore[missing-import]
# pylint: enable=g-import-not-at-top

request_proto_cls = proto.GetProtoClass('graphviz_server.RenderRequest')
Expand Down
2 changes: 1 addition & 1 deletion etils/edc/cast_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class AutoCast(metaclass=_AutoCastMeta):
pass

else:
AutoCast = Annotated[_T, _IS_NORMALIZED] # pytype: disable=invalid-typevar
AutoCast = Annotated[_T, _IS_NORMALIZED]


def make_auto_cast_descriptor(
Expand Down
4 changes: 2 additions & 2 deletions etils/edc/cast_utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ class C(B):
class D(C):
d: edc.AutoCast[
int
] = '888' # Default arg # pytype: disable=annotation-type-mismatch
] = '888' # Default arg

d = D(c='123', c_non_autocast='456') # pytype: disable=wrong-arg-types
d = D(c='123', c_non_autocast='456') # pyrefly: ignore[bad-argument-type]

assert d.d == 888
assert d.c == 123
Expand Down
2 changes: 1 addition & 1 deletion etils/edc/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class ContextVar(metaclass=_ContextVarMeta):
pass

else:
ContextVar = Annotated[_T, _IS_CONTEXTVAR] # pytype: disable=invalid-typevar
ContextVar = Annotated[_T, _IS_CONTEXTVAR]


def make_contextvar_descriptor(
Expand Down
2 changes: 1 addition & 1 deletion etils/edc/dataclass_utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def replace(self) -> int: # Custom replace function
def test_replace():
obj = object()
x = A(y=obj)
y = x.replace(x=123) # pytype: disable=attribute-error
y = x.replace(x=123) # pyrefly: ignore[missing-attribute]
assert x == A(y=obj)
assert y == A(x=123, y=obj)
assert x.y is y.y
Expand Down
8 changes: 4 additions & 4 deletions etils/edc/field_utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
def test_field_no_op(frozen: bool):
@dataclasses.dataclass(frozen=frozen)
class A:
x: Any = edc.field() # pytype: disable=annotation-type-mismatch
x: Any = edc.field()

# No argument
with pytest.raises(TypeError, match=r'__init__\(\) missing 1 required'):
Expand All @@ -51,7 +51,7 @@ def test_field_multi_instance(frozen: bool):

@dataclasses.dataclass(frozen=frozen)
class A:
x: Any = edc.field(validate=str) # pytype: disable=annotation-type-mismatch
x: Any = edc.field(validate=str)

a0 = A(123)
assert a0.x == '123'
Expand Down Expand Up @@ -95,10 +95,10 @@ def test_field_validate(
eq=eq,
order=order,
unsafe_hash=unsafe_hash,
frozen=frozen, # pytype: disable=not-supported-yet
frozen=frozen,
)
class A:
x: Any = edc.field(validate=str, **default_kwargs) # pytype: disable=annotation-type-mismatch
x: Any = edc.field(validate=str, **default_kwargs)

# No argument
if default_kwargs:
Expand Down
4 changes: 2 additions & 2 deletions etils/edc/frozen_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def __repr__(self) -> str:

def tree_flatten(self) -> tuple[list[Any], Any]:
"""`jax.tree_utils` support."""
import jax # pylint: disable=g-import-not-at-top # pytype: disable=import-error
import jax # pylint: disable=g-import-not-at-top # pyrefly: ignore[missing-import]

obj = self._edc_impl.resolve()
# Not if the wrapped object do not support tree_map, then it will be
Expand All @@ -113,7 +113,7 @@ def tree_unflatten(
flattened: list[Any],
) -> Any:
"""`jax.tree_utils` support."""
import jax # pylint: disable=g-import-not-at-top # pytype: disable=import-error
import jax # pylint: disable=g-import-not-at-top # pyrefly: ignore[missing-import]

return jax.tree_util.tree_unflatten(metadata, flattened)

Expand Down
Loading
Loading