Repository navigation
Fetch storage_id once per Repository - #10586
arielshaqed wants to merge 9 commits into
Conversation
1d7f07f to
a950323
Compare
a950323 to
0c78d35
Compare
|
Would it be simpler to keep the parameter name |
I seriously considered that, as you mentioned that change is much smaller. The thing is it would encourage users to pass a repo name rather than a repo object. But then they see no benefit - and continue making one get_repository API call per object used. APIs are forever (unfortunately). |
Do we want to discourage Right now We could type it with a
>>> from lakefs import StoredObject
>>> help(StoredObject.__init__)
__init__(
self,
repo: 'Repository | str',
reference_id: 'str',
path: 'str',
client: 'Optional[Client]' = None
)That seems fine if we're deprecating Supporting both options is going to end up being more complex. |
| from lakefs import repository as repository_mod # Rename: repository is also a function. | ||
|
|
||
|
|
||
| def repository_for(repo: repository_mod.Repository | str, client: Optional[Client]) -> repository_mod.Repository: |
There was a problem hiding this comment.
A good alternative would be to import Repository lazily inside each functions that require it.
There was a problem hiding this comment.
I would still need to define repository_for, though. So improving documentation to clear that up.
Use Repository | str for all repository parameters, and cache storage_id on that repository. Now it is fetched only once. Previously storage_id was cached on each object - and each object would re-fetch storage_id. To gain this advantage keep a Repository object and pass it through Refs, Branches, Tags, etc. into your objects. You may still pass a repository_id, of course, and forgo this advantage. Code still supports the keyword argument repository_id - otherwise it would break backwards compatibility. We may in future treat it as deprecated; for now it is merely renamed using a decorator.
This changed in Python 3.10. We use that, so follow [TFM](https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-10-and-newer)
0c78d35 to
c932caf
Compare
arielshaqed
left a comment
There was a problem hiding this comment.
Thanks!
PTAL...
| from lakefs import repository as repository_mod # Rename: repository is also a function. | ||
|
|
||
|
|
||
| def repository_for(repo: repository_mod.Repository | str, client: Optional[Client]) -> repository_mod.Repository: |
There was a problem hiding this comment.
I would still need to define repository_for, though. So improving documentation to clear that up.
| def renamed_parameters(**kwarg_map): | ||
| """ | ||
| Decorator for renaming args | ||
|
|
||
| To rename old1 -> new1 and old2 -> new2, decorate the function definition: | ||
|
|
||
| @renamed_parameters(old1="new1", old2="new2"}) | ||
| def func(new1, new2): | ||
| pass | ||
| """ | ||
| def decorator(func): | ||
| @wraps(func) | ||
| def wrapped(*args, **kwargs): | ||
| new_kwargs = {} | ||
| for k, v in kwargs.items(): | ||
| new_kwargs[kwarg_map.get(k, k)] = v | ||
| return func(*args, **new_kwargs) | ||
| return wrapped | ||
| return decorator |
There was a problem hiding this comment.
Could we add type-hints to preserve type signature for all the decorated functions/constructors? Note that this will start raising typecheck errors for repository_id= usage.
Also, would love to hear your response for #10586 (comment) comment. :)
It would look something like follows:
from collections.abc import Callable
from functools import wraps
from typing import ParamSpec, TypeVar, cast
P = ParamSpec("P")
R = TypeVar("R")
def renamed_parameters(**kwarg_map: str) -> Callable[[Callable[P, R]], Callable[P, R]]:
"""
Decorator for renaming args
To rename old1 -> new1 and old2 -> new2, decorate the function definition:
@renamed_parameters(old1="new1", old2="new2")
def func(new1, new2):
pass
"""
def decorator(func: Callable[P, R]) -> Callable[P, R]:
@wraps(func)
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
new_kwargs = {}
for k, v in kwargs.items():
new_kwargs[kwarg_map.get(k, k)] = v
return cast(Callable[..., R], func)(*args, **new_kwargs)
return wrapped
return decoratorThere was a problem hiding this comment.
I do not want to start raising errors for repository_id... yet.
There was a problem hiding this comment.
Also I am not sure what this actually changes. wrapped has the same type as func. Indeed, lsp-pyright gives the correct hint.
There was a problem hiding this comment.
Unfortunately, mypy does not do type-inference, and loses those information.
See https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators.
# script.py
from lakefs._decorators import renamed_parameters
@renamed_parameters(repository_id="repo")
def get_repo(repo: str) -> str:
return repo
get_repo(repo=123)
get_repo(repository_id="my-repo")With the above content:
$ mypy script.py
Success: no issues found in 1 source file$ pyright script.py
pyright script.py
/Users/saugat/projects/treeverse/lakeFS/clients/python-wrapper/script.py
/Users/saugat/projects/treeverse/lakeFS/clients/python-wrapper/script.py:7:15 - error: Argument of type "Literal[123]" cannot be assigned to parameter "repo" of type "str" in function "get_repo"
"Literal[123]" is not assignable to "str" (reportArgumentType)
/Users/saugat/projects/treeverse/lakeFS/clients/python-wrapper/script.py:8:1 - error: Argument missing for parameter "repo" (reportCallIssue)
/Users/saugat/projects/treeverse/lakeFS/clients/python-wrapper/script.py:8:10 - error: No parameter named "repository_id" (reportCallIssue)
3 errors, 0 warnings, 0 informations$ ty check script.py
All checks passed!So, pyright reports a type-checking error even for repository_id. However, mypy and ty treat these decorated functions as untyped and skip type-checking them, meaning users lose the benefits of static type checking.
Note that if you do add typehints to the decorator, then all 3 of them will start raising typechecker errors for users using repository_id= keyword arguments (positional arguments will work fine).
arielshaqed
left a comment
There was a problem hiding this comment.
Thanks! Not sure I should change the decorator.
| def renamed_parameters(**kwarg_map): | ||
| """ | ||
| Decorator for renaming args | ||
|
|
||
| To rename old1 -> new1 and old2 -> new2, decorate the function definition: | ||
|
|
||
| @renamed_parameters(old1="new1", old2="new2"}) | ||
| def func(new1, new2): | ||
| pass | ||
| """ | ||
| def decorator(func): | ||
| @wraps(func) | ||
| def wrapped(*args, **kwargs): | ||
| new_kwargs = {} | ||
| for k, v in kwargs.items(): | ||
| new_kwargs[kwarg_map.get(k, k)] = v | ||
| return func(*args, **new_kwargs) | ||
| return wrapped | ||
| return decorator |
There was a problem hiding this comment.
I do not want to start raising errors for repository_id... yet.
| def renamed_parameters(**kwarg_map): | ||
| """ | ||
| Decorator for renaming args | ||
|
|
||
| To rename old1 -> new1 and old2 -> new2, decorate the function definition: | ||
|
|
||
| @renamed_parameters(old1="new1", old2="new2"}) | ||
| def func(new1, new2): | ||
| pass | ||
| """ | ||
| def decorator(func): | ||
| @wraps(func) | ||
| def wrapped(*args, **kwargs): | ||
| new_kwargs = {} | ||
| for k, v in kwargs.items(): | ||
| new_kwargs[kwarg_map.get(k, k)] = v | ||
| return func(*args, **new_kwargs) | ||
| return wrapped | ||
| return decorator |
There was a problem hiding this comment.
Also I am not sure what this actually changes. wrapped has the same type as func. Indeed, lsp-pyright gives the correct hint.
skshetry
left a comment
There was a problem hiding this comment.
LGTM, except of type-hints conversation, but not a blocker. 🙂
It's not useful there, but it _is_ more consistent.
66fa0ed to
73fbd47
Compare
- Improves type-checking of methods with renamed parameters. - Flags a type error on uses of repository_id. But the call itself works, of course!
Use overloads to mark this usage as _deprecated_, but allow it.
arielshaqed
left a comment
There was a problem hiding this comment.
I decided:
- Add typing to the renaming.
- Allow
repository_idto pass type-checking. - Deprecate these uses.
It is slightly verbose because the Python type-checking language is garbage. But it means that I do not harm existing users.
PTAL!
Use Repository | str for all repository parameters, and cache storage_id on
that repository. Now it is fetched only once. Previously storage_id was
cached on each object - and each object would re-fetch storage_id.
To gain this advantage keep a Repository object and pass it through Refs,
Branches, Tags, etc. into your objects. You may still pass a repository_id,
of course, and forgo this advantage.
Code still supports the keyword argument repository_id - otherwise it would
break backwards compatibility. We may in future treat it as deprecated; for
now it is merely renamed using a decorator.