Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

URI repository paths #1956

Closed

Conversation

tokejepsen
Copy link
Member

@tokejepsen tokejepsen commented Aug 23, 2021

Goal

Support URI paths like file:// and dropbox://.

Closes #1908

Motivation

For some of our remote users we cant ensure access to local repository paths, so we have a need for storing our versions in the cloud.

Steps to test
Dropbox is the only supported cloud storage atm. For Dropbox you'll need to create an app: https://developers.dropbox.com/oauth-guide

I opted to use an app because you can have a separate root folder for the app, which makes it easier to organize.

Input the access token in the settings Dropbox section.

Implementation

I initially tried to do custom implementations of Path like objects, but quickly found that the library https://cloudpathlib.drivendata.org/ had already figured things out, so I opted to use that.
The library does not however support Dropbox (which we need), so there is a custom class for Dropbox. That same goes for the uri file:// which is just a wrapper for the built-in Path object.
The AnyPath wrapper determines which class to use depending on the uri and if its a local path it'll revert to built-in Path object. This means we can use the AnyPath as a replacement for Path everywhere.

I've initially put its all in tools but it might need a separate module.

There is a caching system in cloudpath that'll localize the data when reading it. In order for this to work smoothly with the user data directory, I'm using the user data directory as the cache directory. To optimize download I changed to keeping the zip file after extraction. To further optimize download I've changed the validation to happen after the versions have been found, else we would be downloading zip files just validate against the version.py inside.
To keep the user data directory clean, we delete any zip file that is not the current and any directory. Let me know if there is any issues with this, that you can think of.

The only thing I dont like about this implementation is the hack to get the system settings inside of the Dropbox class. This is because when we get the repository paths and validate their existence, we need the dropbox token.

[cuID:yjx9fd]

@tokejepsen tokejepsen added the type: feature Larger, user affecting changes and completely new things label Aug 23, 2021
@mkolar
Copy link
Member

mkolar commented Sep 1, 2021

resolves #1908

@tokejepsen
Copy link
Member Author

We could potentially unify the setup process with #1979, by using Team Folders, so people only have to make one Dropbox app, and one place for all the settings.

Copy link
Member

@antirotor antirotor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just found one unpleasant side-effect of this - if you enable dropbox module, set your token and then enter path to OpenPype repositories to dropbox://... and it will fail for some reason, you can't run settings anymore (anything from openpype in fact) as get_openpype_path_from_db() is called early in bootstrap process and it will pull in dropbox to check if path exists.

Some solution is with changing order how path is determined here:
https://github.com/pypeclub/OpenPype/blob/2a01fb8f3165b237161f2ad6877d9a6213c79087/start.py#L811

so check for OPENPYPE_PATH should be before this, but nevertheless we should probably handle dropbox errors more gracefully.

@tokejepsen
Copy link
Member Author

but nevertheless we should probably handle dropbox errors more gracefully.

Do you have any stack trace of the errors?

@antirotor
Copy link
Member

antirotor commented Sep 8, 2021

Do you have any stack trace of the errors?

yeah, sorry:

Traceback (most recent call last):
  File "C:\Users\annat\Documents\pype\3.0\start.py", line 940, in <module>
    boot()
  File "C:\Users\annat\Documents\pype\3.0\start.py", line 786, in boot
    openpype_path = get_openpype_path_from_db(openpype_mongo)
  File "C:\Users\annat\Documents\pype\3.0\igniter\tools.py", line 590, in get_openpype_path_from_db
    if AnyPath(path).exists():
  File "C:\Users\annat\Documents\pype\3.0\.venv\lib\site-packages\cloudpathlib\cloudpath.py", line 302, in exists
    return self.client._exists(self)
  File "C:\Users\annat\Documents\pype\3.0\igniter\tools.py", line 278, in _exists
    self.client.files_get_metadata(cloud_path.path)
  File "C:\Users\annat\Documents\pype\3.0\.venv\lib\site-packages\dropbox\base.py", line 1512, in files_get_metadata
    None,
  File "C:\Users\annat\Documents\pype\3.0\.venv\lib\site-packages\dropbox\dropbox_client.py", line 329, in request
    timeout=timeout)
  File "C:\Users\annat\Documents\pype\3.0\.venv\lib\site-packages\dropbox\dropbox_client.py", line 485, in request_json_string_with_retry
    timeout=timeout)
  File "C:\Users\annat\Documents\pype\3.0\.venv\lib\site-packages\dropbox\dropbox_client.py", line 611, in request_json_string
    raise AuthError(request_id, err)
dropbox.exceptions.AuthError: AuthError('f541561c6e5e4c7819a01325dfa470ce4', AuthError('missing_scope', TokenScopeError(required_scope='files.metadata.read')))

but that error is not really relevant. I've added files.metadata.read permissions and it still throws it so my issue is probably elsewhere, but point is that if there is any error at all from dropbox point of view, it will crash openpype at the early stages.

@tokejepsen
Copy link
Member Author

Good point! We'll need to gracefully authenticate Dropbox else use the local path I guess?

@antirotor
Copy link
Member

Good point! We'll need to gracefully authenticate Dropbox else use the local path I guess?

Maybe it would be enough to catch exception in:

if AnyPath(path).exists():
   ...

because you can say that if exists() fails, path doesn't exist :D

@tokejepsen
Copy link
Member Author

True, we could do a try catch all.

And fallback on the local user directory?

@antirotor
Copy link
Member

And fallback on the local user directory?

Yeah, issue warning and fallback to what can be found locally. That warning should be in UI if not in headless mode so artist would know exactly that he's not using "official" studio version but possibly outdated one and he need to contact someone to fix it.

@tokejepsen
Copy link
Member Author

Note to self. Something that keeps tripping me up when working with the Dropbox API is that the permissions are attached to the token. So if you update the permissions you'll need to update the token as well.

@tokejepsen
Copy link
Member Author

This is ready for another review.

Copy link
Member

@antirotor antirotor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are missing dependency in pyproject.toml:

cloudpathlib = "^0.6.2"

and we are missing documentation for this, but otherwise it seem to be working ok

@antirotor
Copy link
Member

also let's remove the clean versions function and put it to different pull request so we can track it cleanly in changelog. This cleanup should be configurable on local settings level - because you actually might need multiple versions of OpenPype on render nodes - version si passed to Deadline as environment variable.

@mkolar
Copy link
Member

mkolar commented Oct 25, 2021

@tokejepsen could you please alter this based on @antirotor last comment, this included two features that are not related and the one with version cleanup need a bit more love. Also needs to be separate for the changelog which is generated automatically

@tokejepsen
Copy link
Member Author

Yup, this is on my list to do at some point.

@@ -128,6 +428,39 @@ def validate_path_string(path: str) -> (bool, str):
return True, "valid path"


def get_openpype_system_settings(url: str) -> dict:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid it is not possible to use system settings before defying which OpenPype is used. That's why we separated few settings into global settings get_openpype_global_settings. Main reason is that we knew that we will want to store settings by version (implemented with #2570).


# Hack to share url to AnyPath paths.
MODULE = sys.modules[__name__]
URL = ""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use os.environ["OPENPYPE_MONGO"] instead of storing the value into global scope variable. Or pass the value into function where is needed.

@@ -14,6 +15,305 @@
ConfigurationError,
OperationFailure
)
from cloudpathlib import AnyPath
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code specific for dropbox should be in separated file and only code that is used here should be imported. There is a lot of code which is dropbox specific but does not make sense in context of igniter/tools for example class FilePath is dropbox specific but I this context it is confusing.

@ClementHector
Copy link
Contributor

ClementHector commented Apr 5, 2022

I will soon start implementing the same for Google Drive. Wouldn't the easiest way be to fork cloudpathlib and add the class for
dropbox and gdrive to it?

@tokejepsen
Copy link
Member Author

tokejepsen commented Apr 5, 2022

I will soon start implementing the same for Google Drive. Wouldn't the easiest way be to fork cloudpathlib and add the class for
dropbox and gdrive to it?

Yeah, it might be. I just didnt have time to figure out where it should be. Also a fork might be more trouble managing with OP than a custom class, but forking might lead to a PR to origin repository which would be better.

@mkolar
Copy link
Member

mkolar commented Apr 5, 2022

We had to actually put this on hold, until we finish refactor of the repository. To implement this cleanly it should be modules that provide the path cloud location, however those are not loaded until we start OP. there's a bit of an chicken and egg situation. We're counting on separating these responsibilities properly soon.

@mkolar
Copy link
Member

mkolar commented Apr 28, 2022

Unfortunatelly, we won't be able to merge this in this state. It is however one of the first things to tackle as soon as we're done with the refactor of the client application, which is being changed to allow for this to be re-implemented a lot more robustly, without having to hardcode any specific providers into the product.

@mkolar mkolar closed this Apr 28, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
type: feature Larger, user affecting changes and completely new things
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Version Repository URI Paths
5 participants