Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]

- Support `URI / URI_LIKE`, similar to `pathlib.Path`.

### Added

### Changed
Expand Down
21 changes: 0 additions & 21 deletions python/gigl/common/types/uri/gcs_uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,3 @@ def is_valid(
if raise_exception and not has_no_backslash:
raise TypeError(f"{cls.__name__} does not support backslashes; got {uri}")
return True if (has_valid_prefix and has_no_backslash) else False

@classmethod
def join(
cls, token: Union[str, Path, Uri], *tokens: Union[str, Path, Uri]
) -> GcsUri:
"""
Joins multiple URI tokens together and returns a new GcsUri object.

Args:
token (Union[str, Path, Uri]): The first URI token to join.
*tokens (Union[str, Path, Uri]): Additional URI tokens to join.

Returns:
GcsUri: A new GcsUri object representing the joined URI.
"""
joined_uri = super().join(token, *tokens)
uri = cls(uri=joined_uri.uri)
return uri

def __repr__(self) -> str:
return self.uri
19 changes: 1 addition & 18 deletions python/gigl/common/types/uri/http_uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,4 @@ def is_valid(
raise TypeError(f"{cls.__name__} must start with http:// or https://")
if raise_exception and not has_no_backslash:
raise TypeError(f"{cls.__name__} does not support backslashes")
return True if (has_valid_prefix and has_no_backslash) else False

@classmethod
def join(
cls, token: Union[str, Path, Uri], *tokens: Union[str, Path, Uri]
) -> HttpUri:
"""Join multiple URI tokens into a single URI.

Args:
token: The first URI token.
*tokens: Additional URI tokens to join.

Returns:
HttpUri: The joined URI.
"""
joined_uri = super().join(token, *tokens)
uri = cls(uri=joined_uri.uri)
return uri
return has_valid_prefix and has_no_backslash
24 changes: 0 additions & 24 deletions python/gigl/common/types/uri/local_uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,6 @@
class LocalUri(Uri, os.PathLike):
"""Represents a local URI (Uniform Resource Identifier) that extends the `Uri` class and implements the `os.PathLike` interface."""

@classmethod
def join(
cls, token: Union[str, Path, Uri], *tokens: Union[str, Path, Uri]
) -> LocalUri:
"""Joins multiple URI tokens together and returns a new `LocalUri` object.

Args:
token (Union[str, Path, Uri]): The first URI token to join.
*tokens (Union[str, Path, Uri]): Additional URI tokens to join.

Returns:
LocalUri: A new `LocalUri` object representing the joined URI.
"""
joined_uri = super().join(token, *tokens)
return cls(uri=joined_uri)

@classmethod
def is_valid(
cls, uri: Union[str, Path, Uri], raise_exception: Optional[bool] = False
Expand Down Expand Up @@ -55,14 +39,6 @@ def absolute(self) -> LocalUri:
"""
return LocalUri(uri=Path(self.uri).absolute())

def __repr__(self) -> str:
"""Returns a string representation of the `LocalUri` object.

Returns:
str: The string representation of the `LocalUri` object.
"""
return self.uri

def __fspath__(self):
"""Return the file system path representation of the object.
Needed for `os.PathLike` interface.
Expand Down
27 changes: 20 additions & 7 deletions python/gigl/common/types/uri/uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from pathlib import Path
from typing import Any, Union

from typing_extensions import Self
Comment thread
kmontemayor2-sc marked this conversation as resolved.


class Uri(object):
"""
Expand All @@ -14,11 +16,11 @@ class Uri(object):
def uri(self):
return self.__uri

def __init__(self, uri: Union[str, Path, Uri]):
def __init__(self, uri: _URI_LIKE):
self.__uri = self._token_to_string(uri)

@staticmethod
def _token_to_string(token: Union[str, Path, Uri]) -> str:
def _token_to_string(token: _URI_LIKE) -> str:
if isinstance(token, str):
return token
elif isinstance(token, Uri):
Expand All @@ -27,8 +29,11 @@ def _token_to_string(token: Union[str, Path, Uri]) -> str:
return str(token)
return ""

# TODO(kmonte): You should not be able to join a Uri with a Uri of a different type.
# *or* join HTTP on HTTP or GCS on GCS.
# This is not backwards compatible, so come around to this later.
@classmethod
def join(cls, token: Union[str, Path, Uri], *tokens: Union[str, Path, Uri]) -> Uri:
def join(cls, token: _URI_LIKE, *tokens: _URI_LIKE) -> Self:
"""
Join multiple tokens to create a new Uri instance.

Expand All @@ -43,13 +48,11 @@ def join(cls, token: Union[str, Path, Uri], *tokens: Union[str, Path, Uri]) -> U
token = cls._token_to_string(token)
token_strs: list[str] = [cls._token_to_string(token) for token in tokens]
joined_tmp_path = os.path.join(token, *token_strs)
joined_path = Uri(joined_tmp_path)
joined_path = cls(joined_tmp_path)
return joined_path

@classmethod
def is_valid(
cls, uri: Union[str, Path, Uri], raise_exception: bool = False
) -> bool:
def is_valid(cls, uri: _URI_LIKE, raise_exception: bool = False) -> bool:
"""
Check if the given URI is valid.

Expand Down Expand Up @@ -83,3 +86,13 @@ def __eq__(self, other: Any) -> bool:
if isinstance(other, Uri):
return self.uri == other.uri
return False

def __truediv__(self, other: _URI_LIKE) -> Self:
if isinstance(other, Uri) and not isinstance(other, type(self)):
Comment thread
svij-sc marked this conversation as resolved.
raise TypeError(
f"Cannot use '/' operator to join {type(self).__name__} with {type(other).__name__}"
)
return self.join(self, other)


_URI_LIKE = Union[str, Path, Uri]
Comment thread
kmontemayor2-sc marked this conversation as resolved.
66 changes: 66 additions & 0 deletions python/tests/unit/common/types/uri_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import unittest
from pathlib import Path

from gigl.common.types.uri.gcs_uri import GcsUri
from gigl.common.types.uri.http_uri import HttpUri
from gigl.common.types.uri.local_uri import LocalUri
from gigl.common.types.uri.uri import Uri
from gigl.common.types.uri.uri_factory import UriFactory


Expand All @@ -13,3 +18,64 @@ def test_can_get_basename(self):
self.assertEqual(file_name, gcs_uri_full.get_basename())
self.assertEqual(file_name, local_uri_full.get_basename())
self.assertEqual(file_name, http_uri_full.get_basename())

def test_join(self):
joined: Uri
with self.subTest("LocalUri"):
joined = LocalUri.join("/foo/bar", "file.txt")
self.assertEqual(joined, LocalUri("/foo/bar/file.txt"))
self.assertIsInstance(joined, LocalUri)
with self.subTest("HttpUri"):
joined = HttpUri.join("http://abc.com/xyz", "foo")
self.assertEqual(joined, HttpUri("http://abc.com/xyz/foo"))
self.assertIsInstance(joined, HttpUri)
with self.subTest("GcsUri"):
joined = GcsUri.join("gs://bucket/", "file.txt")
self.assertEqual(joined, GcsUri("gs://bucket/file.txt"))
self.assertIsInstance(joined, GcsUri)
with self.subTest("LocalUri with Path"):
joined = LocalUri.join("/foo/bar", Path("file.text"))
self.assertEqual(joined, LocalUri("/foo/bar/file.text"))

def test_div_join(self):
joined: Uri
with self.subTest("LocalUri"):
joined = LocalUri("/foo/bar") / "baz"
self.assertEqual(joined, LocalUri("/foo/bar/baz"))
self.assertIsInstance(joined, LocalUri)
with self.subTest("HttpUri"):
joined = HttpUri("http://abc.com/xyz") / "foo"
self.assertEqual(joined, HttpUri("http://abc.com/xyz/foo"))
self.assertIsInstance(joined, HttpUri)
with self.subTest("GcsUri"):
joined = GcsUri("gs://bucket/") / "file.txt"
self.assertEqual(joined, GcsUri("gs://bucket/file.txt"))
self.assertIsInstance(joined, GcsUri)
with self.subTest("LocalUri with Path"):
joined = LocalUri("/foo/bar") / Path("file.text")
self.assertEqual(joined, LocalUri("/foo/bar/file.text"))
self.assertIsInstance(joined, LocalUri)

def test_div_join_invalid_type(self):
with self.subTest("LocalUri / HttpUri"):
with self.assertRaises(TypeError):
LocalUri("/foo/bar") / HttpUri("http://abc.com/xyz")
with self.subTest("LocalUri / GcsUri"):
with self.assertRaises(TypeError):
LocalUri("/foo/bar") / GcsUri("gs://bucket/path/to")
with self.subTest("HttpUri / LocalUri"):
with self.assertRaises(TypeError):
HttpUri("http://abc.com/xyz") / LocalUri("/foo/bar")
with self.subTest("HttpUri / GcsUri"):
with self.assertRaises(TypeError):
HttpUri("http://abc.com/xyz") / GcsUri("gs://bucket/path/to")
with self.subTest("GcsUri / LocalUri"):
with self.assertRaises(TypeError):
GcsUri("gs://bucket/path/to") / LocalUri("/foo/bar")
with self.subTest("GcsUri / HttpUri"):
with self.assertRaises(TypeError):
GcsUri("gs://bucket/path/to") / HttpUri("http://abc.com/xyz")


if __name__ == "__main__":
unittest.main()