-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Construct proper S3 URIs on register_model
#39
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
from __future__ import annotations | ||
|
||
import functools | ||
import inspect | ||
from collections.abc import Sequence | ||
from typing import Any, Callable, TypeVar | ||
|
||
CallableT = TypeVar("CallableT", bound=Callable[..., Any]) | ||
|
||
|
||
# copied from https://github.com/Rapptz/RoboDanny | ||
def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str: | ||
size = len(seq) | ||
if size == 0: | ||
return "" | ||
|
||
if size == 1: | ||
return seq[0] | ||
|
||
if size == 2: | ||
return f"{seq[0]} {final} {seq[1]}" | ||
|
||
return delim.join(seq[:-1]) + f" {final} {seq[-1]}" | ||
|
||
|
||
def quote(string: str) -> str: | ||
"""Add single quotation marks around the given string. Does *not* do any escaping.""" | ||
return f"'{string}'" | ||
|
||
|
||
# copied from https://github.com/openai/openai-python | ||
def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]: # noqa: C901 | ||
"""Decorator to enforce a given set of arguments or variants of arguments are passed to the decorated function. | ||
|
||
Useful for enforcing runtime validation of overloaded functions. | ||
|
||
Example usage: | ||
```py | ||
@overload | ||
def foo(*, a: str) -> str: | ||
... | ||
|
||
|
||
@overload | ||
def foo(*, b: bool) -> str: | ||
... | ||
|
||
|
||
# This enforces the same constraints that a static type checker would | ||
# i.e. that either a or b must be passed to the function | ||
@required_args(["a"], ["b"]) | ||
def foo(*, a: str | None = None, b: bool | None = None) -> str: | ||
... | ||
``` | ||
""" | ||
|
||
def inner(func: CallableT) -> CallableT: # noqa: C901 | ||
params = inspect.signature(func).parameters | ||
positional = [ | ||
name | ||
for name, param in params.items() | ||
if param.kind | ||
in { | ||
param.POSITIONAL_ONLY, | ||
param.POSITIONAL_OR_KEYWORD, | ||
} | ||
] | ||
|
||
@functools.wraps(func) | ||
def wrapper(*args: object, **kwargs: object) -> object: | ||
given_params: set[str] = set() | ||
for i, _ in enumerate(args): | ||
try: | ||
given_params.add(positional[i]) | ||
except IndexError: | ||
msg = f"{func.__name__}() takes {len(positional)} argument(s) but {len(args)} were given" | ||
raise TypeError(msg) from None | ||
|
||
for key in kwargs: | ||
given_params.add(key) | ||
|
||
for variant in variants: | ||
matches = all(param in given_params for param in variant) | ||
if matches: | ||
break | ||
else: # no break | ||
if len(variants) > 1: | ||
variations = human_join( | ||
[ | ||
"(" | ||
+ human_join([quote(arg) for arg in variant], final="and") | ||
+ ")" | ||
for variant in variants | ||
] | ||
) | ||
msg = f"Missing required arguments; Expected either {variations} arguments to be given" | ||
else: | ||
# TODO: this error message is not deterministic | ||
missing = list(set(variants[0]) - given_params) | ||
if len(missing) > 1: | ||
msg = f"Missing required arguments: {human_join([quote(arg) for arg in missing])}" | ||
else: | ||
msg = f"Missing required argument: {quote(missing[0])}" | ||
raise TypeError(msg) | ||
return func(*args, **kwargs) | ||
|
||
return wrapper # type: ignore | ||
|
||
return inner |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
"""Utilities for the model registry.""" | ||
|
||
from __future__ import annotations | ||
|
||
import os | ||
|
||
from typing_extensions import overload | ||
|
||
from ._utils import required_args | ||
from .exceptions import MissingMetadata | ||
|
||
|
||
@overload | ||
def s3_uri_from( | ||
path: str, | ||
) -> str: ... | ||
|
||
|
||
@overload | ||
def s3_uri_from( | ||
path: str, | ||
bucket: str, | ||
) -> str: ... | ||
|
||
|
||
@overload | ||
def s3_uri_from( | ||
path: str, | ||
bucket: str, | ||
*, | ||
endpoint: str, | ||
region: str, | ||
) -> str: ... | ||
|
||
|
||
@required_args( | ||
(), | ||
( # pre-configured env | ||
"bucket", | ||
), | ||
( # custom env or non-default bucket | ||
"bucket", | ||
"endpoint", | ||
"region", | ||
), | ||
) | ||
def s3_uri_from( | ||
path: str, | ||
bucket: str | None = None, | ||
*, | ||
endpoint: str | None = None, | ||
region: str | None = None, | ||
) -> str: | ||
"""Build an S3 URI. | ||
|
||
This helper function builds an S3 URI from a path and a bucket name, assuming you have a configured environment | ||
with a default bucket, endpoint, and region set. | ||
If you don't, you must provide all three optional arguments. | ||
That is also the case for custom environments, where the default bucket is not set, or if you want to use a | ||
different bucket. | ||
|
||
Args: | ||
path: Storage path. | ||
bucket: Name of the S3 bucket. Defaults to AWS_S3_BUCKET. | ||
endpoint: Endpoint of the S3 bucket. Defaults to AWS_S3_ENDPOINT. | ||
region: Region of the S3 bucket. Defaults to AWS_DEFAULT_REGION. | ||
|
||
Returns: | ||
S3 URI. | ||
""" | ||
default_bucket = os.environ.get("AWS_S3_BUCKET") | ||
if not bucket: | ||
if not default_bucket: | ||
msg = "Custom environment requires all arguments" | ||
raise MissingMetadata(msg) | ||
bucket = default_bucket | ||
elif (not default_bucket or default_bucket != bucket) and not endpoint: | ||
msg = ( | ||
"bucket_endpoint and bucket_region must be provided for non-default bucket" | ||
) | ||
raise MissingMetadata(msg) | ||
|
||
endpoint = endpoint or os.getenv("AWS_S3_ENDPOINT") | ||
region = region or os.getenv("AWS_DEFAULT_REGION") | ||
|
||
if not (endpoint and region): | ||
msg = "Missing environment variables: bucket_endpoint and bucket_region are required" | ||
raise MissingMetadata(msg) | ||
|
||
# https://alexwlchan.net/2020/s3-keys-are-not-file-paths/ nor do they resolve to valid URls | ||
# FIXME: is this safe? | ||
return f"s3://{bucket}/{path}?endpoint={endpoint}&defaultRegion={region}" | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bumping this note here now that the PR is "ready", can you share your thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks better for uri building. Did we also want to add the URI components as individual properties to the model metadata? Maybe we can pass a metadata object to the uri builders so that it can be reused in the register call.
@tarilabs didn't UI say they would like the uri components individually, or are they happy to have the constructed uri?