Skip to content
Draft
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
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,14 @@ format_cpp:

format: format_py format_cpp format_scala format_md

# ty resolves the stdlib against a single Python version per invocation, so each end of
# the supported range needs its own pass.
type_check:
# Floor: 3.11, from [tool.ty.environment] in pyproject.toml. Catches stdlib APIs that do
# not exist that far back.
uv run ty check ${PYTHON_DIRS}
# Ceiling: catches stdlib modules and signatures 3.13 removed or changed.
uv run ty check --python-version 3.13 ${PYTHON_DIRS}

build_cpp_extensions:
$(MAKE) -C gigl-core build_cpp_extensions
Expand Down
4 changes: 2 additions & 2 deletions examples/link_prediction/graph_store/storage_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,14 @@
import argparse
import ast
import os
from distutils.util import strtobool
from typing import Literal, Optional, Union

import torch

from gigl.common import Uri, UriFactory
from gigl.common.logger import Logger
from gigl.common.utils.os_utils import import_obj
from gigl.common.utils.parse import str_to_bool
from gigl.distributed.graph_store import (
GraphStoreInfo,
build_storage_dataset,
Expand Down Expand Up @@ -254,7 +254,7 @@ def storage_node_process(
splitter=splitter,
ssl_positive_label_percentage=ssl_positive_label_percentage,
should_load_tf_records_in_parallel=bool(
strtobool(args.should_load_tf_records_in_parallel)
str_to_bool(args.should_load_tf_records_in_parallel)
),
num_rpc_threads=args.num_rpc_threads,
rpc_timeout=args.rpc_timeout,
Expand Down
4 changes: 2 additions & 2 deletions examples/tutorial/KDD_2025/heterogeneous_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import argparse
import datetime
from collections.abc import Mapping
from distutils.util import strtobool
from pathlib import Path

import fastavro
Expand All @@ -38,6 +37,7 @@
from gigl.common import Uri, UriFactory
from gigl.common.data.export import EmbeddingExporter
from gigl.common.logger import Logger
from gigl.common.utils.parse import str_to_bool
from gigl.distributed import (
DistDataset,
DistNeighborLoader,
Expand Down Expand Up @@ -154,7 +154,7 @@ def inference(
task_config_uri,
_tfrecord_uri_pattern=".*tfrecord",
)
if strtobool(args.use_local_saved_model):
if str_to_bool(args.use_local_saved_model):
model_uri = LOCAL_SAVED_MODEL_URI
else:
model_uri = gbml_config_pb_wrapper.shared_config.trained_model_metadata.trained_model_uri
Expand Down
4 changes: 2 additions & 2 deletions examples/tutorial/KDD_2025/heterogeneous_training.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@

import argparse
from collections.abc import Iterable, Mapping
from distutils.util import strtobool
from typing import Literal

import torch
Expand All @@ -48,6 +47,7 @@
from examples.tutorial.KDD_2025.utils import LOCAL_SAVED_MODEL_URI, init_model
from gigl.common import UriFactory
from gigl.common.logger import Logger
from gigl.common.utils.parse import str_to_bool
from gigl.distributed import (
DistABLPLoader,
DistDataset,
Expand Down Expand Up @@ -269,7 +269,7 @@ def train(
logger.info(f"Test node type {node_type} has {node_ids.size(0)} nodes.") # ty: ignore[unresolved-attribute] TODO(ty-torch-keyed-access): fix ty false positives for torch-backed keyed container access.
training_process_port = get_free_port()
logger.info(f"Will train for {max_training_batches} batches.")
if strtobool(args.use_local_saved_model):
if str_to_bool(args.use_local_saved_model):
model_uri = LOCAL_SAVED_MODEL_URI
else:
model_uri = gbml_config_pb_wrapper.shared_config.trained_model_metadata.trained_model_uri
Expand Down
41 changes: 41 additions & 0 deletions gigl/common/utils/parse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from typing import Final

_TRUE_VALUES: Final[frozenset[str]] = frozenset({"y", "yes", "t", "true", "on", "1"})
_FALSE_VALUES: Final[frozenset[str]] = frozenset({"n", "no", "f", "false", "off", "0"})


def str_to_bool(value: str) -> bool:
"""
Converts a string representation of truth to a bool.

Accepts and rejects the same spellings as `distutils.util.strtobool`, which is no
longer part of the standard library, though the `ValueError` message keeps the
caller's casing instead of lowercasing it:

- True: "y", "yes", "t", "true", "on", "1"
- False: "n", "no", "f", "false", "off", "0"

Matching is case-insensitive. Whitespace is not stripped, so " true" raises rather
than returning True.

Example:
>>> str_to_bool("TRUE")
True
>>> str_to_bool("off")
False

Args:
value (str): The string to interpret as a boolean.

Returns:
bool: The truth value `value` spells.

Raises:
ValueError: If `value` is not one of the accepted spellings.
"""
normalized_value = value.lower()
if normalized_value in _TRUE_VALUES:
return True
if normalized_value in _FALSE_VALUES:
return False
raise ValueError(f"invalid truth value {value!r}")
6 changes: 3 additions & 3 deletions gigl/distributed/dataset_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import time
from collections.abc import Mapping
from distutils.util import strtobool
from typing import Literal, MutableMapping, Optional, Tuple, Type, Union

import torch
Expand All @@ -29,6 +28,7 @@
)
from gigl.common.logger import Logger
from gigl.common.utils.decorator import tf_on_cpu
from gigl.common.utils.parse import str_to_bool
from gigl.distributed.constants import DEFAULT_MASTER_DATA_BUILDING_PORT
from gigl.distributed.dist_context import DistributedContext
from gigl.distributed.dist_dataset import DistDataset
Expand Down Expand Up @@ -629,11 +629,11 @@ def build_dataset_from_task_config_uri(
)

should_use_range_partitioning = bool(
strtobool(args.get("should_use_range_partitioning", "True"))
str_to_bool(args.get("should_use_range_partitioning", "True"))
)

should_load_tensors_in_parallel = bool(
strtobool(args.get("should_load_tensors_in_parallel", "True"))
str_to_bool(args.get("should_load_tensors_in_parallel", "True"))
)

logger.info(
Expand Down
28 changes: 22 additions & 6 deletions gigl/scripts/post_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,17 @@ def run_command_and_stream_stdout(cmd: str) -> Optional[int]:
return return_code


def main():
"""Main entry point for the post-install script."""
def main() -> int:
"""Main entry point for the post-install script.

Returns:
int: 0, when install_glt.sh succeeds.

Raises:
SystemExit: With a non-zero code when install_glt.sh is missing, fails, or
reports no exit status. Callers must propagate it: swallowing it lets a
build succeed while shipping an environment with no working GLT.
"""
print("Running GIGL post-install script...")

# Get the directory where this script is located
Expand All @@ -52,9 +61,16 @@ def main():

try:
print(f"Executing {cmd}...")
result = run_command_and_stream_stdout(cmd)
print("Post-install script finished running, with return code: ", result)
return result
return_code = run_command_and_stream_stdout(cmd)
print("Post-install script finished running, with return code: ", return_code)
# `Popen.poll()` returns None while the child has no recorded status, so an
# unknown outcome is not evidence of success.
if return_code is None:
print("Error: could not determine the exit status of install_glt.sh")
sys.exit(1)
if return_code != 0:
sys.exit(return_code)
return return_code

except subprocess.CalledProcessError as e:
print(f"Error running install_glt.sh: {e}")
Expand All @@ -65,4 +81,4 @@ def main():


if __name__ == "__main__":
main()
raise SystemExit(main())
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from collections import OrderedDict
from contextlib import ExitStack
from distutils.util import strtobool
from time import time
from typing import Any, Optional, Type

Expand All @@ -12,6 +11,7 @@

from gigl.common.logger import Logger
from gigl.common.utils import os_utils
from gigl.common.utils.parse import str_to_bool
from gigl.common.utils.torch_training import (
get_rank,
get_world_size,
Expand Down Expand Up @@ -201,7 +201,7 @@ def __init__(self, **kwargs) -> None:
# Retrieval-specific Task Parameters
softmax_temp = float(kwargs.get("softmax_temp", 0.07))
should_remove_accidental_hits = bool(
strtobool(kwargs.get("should_remove_accidental_hits", "True"))
str_to_bool(kwargs.get("should_remove_accidental_hits", "True"))
)
task = base_task(
temperature=softmax_temp,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import tempfile
from distutils.util import strtobool

from torch.profiler import (
ProfilerActivity,
Expand All @@ -10,6 +9,7 @@

from gigl.common import LocalUri
from gigl.common.logger import Logger
from gigl.common.utils.parse import str_to_bool

logger = Logger()

Expand All @@ -33,9 +33,9 @@ def __init__(self, **kwargs) -> None:
active=self.active,
repeat=self.repeat,
)
self.profile_memory = bool(strtobool(kwargs.get("profile_memory", "True")))
self.record_shapes = bool(strtobool(kwargs.get("record_shapes", "False")))
self.with_stack = bool(strtobool(kwargs.get("with_stack", "False")))
self.profile_memory = bool(str_to_bool(kwargs.get("profile_memory", "True")))
self.record_shapes = bool(str_to_bool(kwargs.get("record_shapes", "False")))
self.with_stack = bool(str_to_bool(kwargs.get("with_stack", "False")))
logger.info(f"Profiler will be instantiated with {self.__dict__}")

def profiler_context(self) -> profile:
Expand Down
8 changes: 4 additions & 4 deletions gigl/src/common/types/pb_wrappers/gbml_config.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from __future__ import annotations

from dataclasses import dataclass, field
from distutils.util import strtobool
from typing import Optional

from gigl.common import Uri, UriFactory
from gigl.common.logger import Logger
from gigl.common.utils.parse import str_to_bool
from gigl.common.utils.proto_utils import ProtoUtils
from gigl.src.common.types.graph_data import EdgeType, NodeType
from gigl.src.common.types.pb_wrappers.dataset_metadata import DatasetMetadataPbWrapper
Expand Down Expand Up @@ -529,7 +529,7 @@ def should_use_glt_backend(self) -> bool:
"""

return bool(
strtobool(
str_to_bool(
dict(self.gbml_config_pb.feature_flags).get(
"should_run_glt_backend", "False"
)
Expand All @@ -550,7 +550,7 @@ def should_populate_predictions_path(self) -> bool:
bool: Whether to populate predictions path in the InferenceOutput for each entity type
"""
return bool(
strtobool(
str_to_bool(
dict(self.gbml_config_pb.feature_flags).get(
"should_populate_predictions_path", "False"
)
Expand All @@ -570,7 +570,7 @@ def should_populate_embeddings_path(self) -> bool:
bool: Whether to populate embeddings path in the InferenceOutput for each entity type
"""
return bool(
strtobool(
str_to_bool(
dict(self.gbml_config_pb.feature_flags).get(
"should_populate_embeddings_path", "True"
)
Expand Down
4 changes: 2 additions & 2 deletions gigl/src/subgraph_sampler/subgraph_sampler.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import argparse
import datetime
import os
from distutils.util import strtobool
from typing import Optional, Sequence

import gigl.env.dep_constants as dep_constants
Expand All @@ -16,6 +15,7 @@
from gigl.common.metrics.decorators import flushes_metrics, profileit
from gigl.common.utils import os_utils
from gigl.common.utils.gcs import GcsUtils
from gigl.common.utils.parse import str_to_bool
from gigl.env.pipelines_config import get_resource_config
from gigl.src.common.constants.components import GiGLComponents
from gigl.src.common.constants.metrics import TIMER_SUBGRAPH_SAMPLER_S
Expand Down Expand Up @@ -101,7 +101,7 @@ def run(
# Dataproc image 2.0 starting 2026-08-25. Setting the `use_spark35_runner`
# experimental flag to "False" remains a temporary escape hatch until then.
use_spark35: bool = bool(
strtobool(
str_to_bool(
gbml_config_pb_wrapper.dataset_config.subgraph_sampler_config.experimental_flags.get(
"use_spark35_runner", "True"
)
Expand Down
6 changes: 4 additions & 2 deletions gigl/src/training/v1/lib/training_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import sys
import tempfile
import traceback
from distutils.util import strtobool
from typing import Any, Optional

import tensorflow as tf
Expand All @@ -19,6 +18,7 @@
from gigl.common.metrics.decorators import flushes_metrics, profileit
from gigl.common.utils import os_utils, torch_training
from gigl.common.utils.local_fs import does_path_exist
from gigl.common.utils.parse import str_to_bool
from gigl.common.utils.torch_training import (
get_distributed_backend,
get_rank,
Expand Down Expand Up @@ -300,7 +300,9 @@ def __run(
# If all parameters are always expected to receive backprop in training, it is not recommended to enable this flag, as it can adversely affect
# performance as a result of the extra traversal of the autograd graph every iteration.
should_enable_find_unused_parameters = bool(
strtobool(trainer_args.get("should_enable_find_unused_parameters", "False"))
str_to_bool(
trainer_args.get("should_enable_find_unused_parameters", "False")
)
)

trainer_instance.model = setup_model_device(
Expand Down
10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,13 @@ exclude = ["*_pb2.py", "*_pb2.pyi"]
# Enforces a consistent import order (stdlib → third-party → first-party).
# Replaces: isort
# Docs: https://docs.astral.sh/ruff/rules/#isort-i
select = ["F401", "I"]
#
# UP005 - Deprecated unittest aliases (pyupgrade)
# Flags 15 of the 16 aliases removed in Python 3.12, such as `assertEquals`; it does
# not cover `assertDictContainsSubset`. Autofixable.
# Replaces: nothing.
# Docs: https://docs.astral.sh/ruff/rules/deprecated-unittest-alias/
select = ["F401", "I", "UP005"]

[tool.ruff.lint.per-file-ignores]
# __init__.py files re-export symbols for the public API, so unused-import
Expand All @@ -300,6 +306,8 @@ select = ["F401", "I"]
known-first-party = ["gigl", "tests", "snapchat", "scripts"]

[tool.ty.environment]
# The floor of the supported range. The `type_check` Make target covers the ceiling with
# a second `ty check --python-version 3.13` pass.
python-version = "3.11"

[tool.ty.src]
Expand Down
4 changes: 2 additions & 2 deletions scripts/bootstrap_resource_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
import subprocess
import tempfile
from dataclasses import dataclass
from distutils.util import strtobool
from typing import Optional

import yaml

from gigl.common import GcsUri, HttpUri, LocalUri, UriFactory
from gigl.common.utils.parse import str_to_bool
from gigl.src.common.utils.file_loader import FileLoader

GIGL_ROOT_DIR = pathlib.Path(__file__).resolve().parent.parent
Expand Down Expand Up @@ -335,7 +335,7 @@ def assert_gcs_bucket_exists(bucket_name: str):
print(f"Updated YAML file saved at '{destination_file_path}'")

# Update the user's shell configuration
if args.force_shell_config_update and strtobool(args.force_shell_config_update):
if args.force_shell_config_update and str_to_bool(args.force_shell_config_update):
should_update_shell_config = "y"
print("Forcing shell updated due to --force_shell_config_update flag.")
else:
Expand Down
Loading
Loading