Skip to content
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

Support for the /files endpoints #377 #378

Closed
wants to merge 13 commits into from
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The openeo Python client library can now also be installed with conda (conda-forge channel)
([#176](https://github.com/Open-EO/openeo-python-client/issues/176))
- Allow using a custom `requests.Session` in `openeo.rest.auth.oidc` logic
- Full support for user-uploaded files (`/files` endpoints) [#377](https://github.com/Open-EO/openeo-python-client/issues/377)

### Changed

- Less verbose log printing on failed batch job [#332](https://github.com/Open-EO/openeo-python-client/issues/332)
- Improve (UTC) timezone handling in `openeo.util.Rfc3339` and add `rfc3339.today()`/`rfc3339.utcnow()`.
- `list_files()` returns a list of `UserFile` objects instead of a list of dictionaries - use `to_dict` on the `UserFile` object to get the dictionary.

### Removed

Expand Down
2 changes: 2 additions & 0 deletions openeo/internal/jupyter.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ def render_component(component: str, data = None, parameters: dict = None):
# Set the data as the corresponding parameter in the Vue components
key = COMPONENT_MAP.get(component, component)
if data is not None:
if isinstance(data, list):
data = [(x.to_dict() if "to_dict" in dir(x) else x) for x in data]
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
parameters[key] = data

# Construct HTML, load Vue Components source files only if the openEO HTML tag is not yet defined
Expand Down
23 changes: 17 additions & 6 deletions openeo/rest/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import json
import logging
import shlex
import os
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
import sys
import warnings
from collections import OrderedDict
Expand Down Expand Up @@ -33,6 +34,7 @@
from openeo.rest.datacube import DataCube
from openeo.rest.imagecollectionclient import ImageCollectionClient
from openeo.rest.mlmodel import MlModel
from openeo.rest.userfile import UserFile
from openeo.rest.job import BatchJob, RESTJob
from openeo.rest.rest_capabilities import RESTCapabilities
from openeo.rest.service import Service
Expand Down Expand Up @@ -1092,20 +1094,29 @@ def list_files(self):
"""
Lists all files that the logged in user uploaded.

:return: file_list: List of the user uploaded files.
:return: file_list: List of the user-uploaded files.
"""

files = self.get('/files', expected_status=200).json()['files']
files = [UserFile(file['path'], connection=self, metadata=file) for file in files]
return VisualList("data-table", data=files, parameters={'columns': 'files'})

def create_file(self, path):
def get_file(self, path: str) -> UserFile:
"""
Creates virtual file
Gets a (virtual) file for the user workspace
m-mohr marked this conversation as resolved.
Show resolved Hide resolved

:return: file object.
:return: UserFile object.
"""
# No endpoint just returns a file object.
raise NotImplementedError()
return UserFile(path, connection=self)

def upload_file(self, source: Union[Path, str]) -> UserFile:
"""
Uploads a file to the user workspace and stores it with the filename of the source given.
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
If a file with the name exists on the user workspace it will be replaced.
m-mohr marked this conversation as resolved.
Show resolved Hide resolved

:return: UserFile object.
"""
return self.get_file(os.path.basename(source)).upload(source)
m-mohr marked this conversation as resolved.
Show resolved Hide resolved

def _build_request_with_process_graph(self, process_graph: Union[dict, Any], **kwargs) -> dict:
"""
Expand Down
63 changes: 63 additions & 0 deletions openeo/rest/userfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import typing
from typing import Any, Dict, Union
import os
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
from pathlib import Path
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
from openeo.util import ensure_dir

if typing.TYPE_CHECKING:
# Imports for type checking only (circular import issue at runtime).
from openeo.rest.connection import Connection
m-mohr marked this conversation as resolved.
Show resolved Hide resolved


class UserFile:
"""Represents a file in the user-workspace of openeo."""

def __init__(self, path: str, connection: 'Connection', metadata: Dict[str, Any] = None):
self.path = path
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
self.metadata = metadata or {"path": path}
self.connection = connection

def __repr__(self):
return '<{c} file={i!r}>'.format(c=self.__class__.__name__, i=self.path)

def _get_endpoint(self) -> str:
return "/files/{}".format(self.path)
m-mohr marked this conversation as resolved.
Show resolved Hide resolved

def download(self, target: Union[Path, str] = None) -> Path:
"""
Downloads a user-uploaded file to the given location.

:param target: download target path. Can be an existing folder
(in which case the file name advertised by backend will be used)
or full file name. By default, the working directory will be used.
"""
# GET /files/{path}
response = self.connection.get(self._get_endpoint(), expected_status=200, stream=True)

target = Path(target or Path.cwd())
if target.is_dir():
target = target / os.path.basename(self.path)
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
ensure_dir(target.parent)

with target.open(mode="wb") as f:
for chunk in response.iter_content(chunk_size=None):
f.write(chunk)

return target


def upload(self, source: Union[Path, str]):
# PUT /files/{path}
""" Uploaded (or replaces) a user-uploaded file."""
path = Path(source)
with path.open(mode="rb") as f:
self.connection.put(self._get_endpoint(), expected_status=200, data=f)
soxofaan marked this conversation as resolved.
Show resolved Hide resolved

def delete(self):
""" Delete a user-uploaded file."""
# DELETE /files/{path}
self.connection.delete(self._get_endpoint(), expected_status=204)

def to_dict(self) -> Dict[str, Any]:
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
""" Returns the provided metadata as dict."""
return self.metadata