Streamlining Shaman protocol further, plus rough implementation for addon

This commit is contained in:
Sybren A. Stüvel 2022-03-24 15:25:28 +01:00
parent 668bfbc492
commit f9b7510c42
43 changed files with 4126 additions and 220 deletions

View File

@ -56,7 +56,7 @@ def _default_job_name() -> str:
@bpy.app.handlers.persistent
def _set_flamenco_job_name(a, b) -> None:
def _set_flamenco_job_name(a, b):
scene = bpy.context.scene
if scene.flamenco_job_name:
return
@ -65,7 +65,7 @@ def _set_flamenco_job_name(a, b) -> None:
@bpy.app.handlers.persistent
def _unset_flamenco_job_name(a, b) -> None:
def _unset_flamenco_job_name(a, b):
scene = bpy.context.scene
if scene.flamenco_job_name != _default_job_name():
return

View File

205
addon/flamenco/bat/cache.py Normal file
View File

@ -0,0 +1,205 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
#
# (c) 2019, Blender Foundation - Sybren A. Stüvel
import base64
import hashlib
import json
import logging
import sys
import time
import typing
from collections import deque
from pathlib import Path
from . import time_tracker
CACHE_ROOT = Path().home() / ".cache/shaman-client/shasums"
MAX_CACHE_FILES_AGE_SECS = 3600 * 24 * 60 # 60 days
log = logging.getLogger(__name__)
class TimeInfo:
computing_checksums = 0.0
checksum_cache_handling = 0.0
def find_files(root: Path) -> typing.Iterable[Path]:
"""Recursively finds files in the given root path.
Directories are recursed into, and file paths are yielded.
Symlinks are yielded if they refer to a regular file.
"""
queue = deque([root])
while queue:
path = queue.popleft()
# Ignore hidden files/dirs; these can be things like '.svn' or '.git',
# which shouldn't be sent to Shaman.
if path.name.startswith("."):
continue
if path.is_dir():
for child in path.iterdir():
queue.append(child)
continue
# Only yield symlinks if they link to (a link to) a normal file.
if path.is_symlink():
symlinked = path.resolve()
if symlinked.is_file():
yield path
continue
if path.is_file():
yield path
def compute_checksum(filepath: Path) -> str:
"""Compute the SHA256 checksum for the given file."""
blocksize = 32 * 1024
log.debug("Computing checksum of %s", filepath)
with time_tracker.track_time(TimeInfo, "computing_checksums"):
hasher = hashlib.sha256()
with filepath.open("rb") as infile:
while True:
block = infile.read(blocksize)
if not block:
break
hasher.update(block)
checksum = hasher.hexdigest()
return checksum
def _cache_path(filepath: Path) -> Path:
"""Compute the cache file for the given file path."""
fs_encoding = sys.getfilesystemencoding()
filepath = filepath.absolute()
# Reverse the directory, because most variation is in the last bytes.
rev_dir = str(filepath.parent)[::-1]
encoded_path = filepath.stem + rev_dir + filepath.suffix
cache_key = (
base64.urlsafe_b64encode(encoded_path.encode(fs_encoding)).decode().rstrip("=")
)
cache_path = CACHE_ROOT / cache_key[:10] / cache_key[10:]
return cache_path
def compute_cached_checksum(filepath: Path) -> str:
"""Computes the SHA256 checksum.
The checksum is cached to disk. If the cache is still valid, it is used to
skip the actual SHA256 computation.
"""
with time_tracker.track_time(TimeInfo, "checksum_cache_handling"):
current_stat = filepath.stat()
cache_path = _cache_path(filepath)
try:
with cache_path.open("r") as cache_file:
payload = json.load(cache_file)
except (OSError, ValueError):
# File may not exist, or have invalid contents.
pass
else:
checksum: str = payload.get("checksum", "")
cached_mtime = payload.get("file_mtime", 0.0)
cached_size = payload.get("file_size", -1)
if (
checksum
and current_stat.st_size == cached_size
and abs(cached_mtime - current_stat.st_mtime) < 0.01
):
cache_path.touch()
return checksum
checksum = compute_checksum(filepath)
with time_tracker.track_time(TimeInfo, "checksum_cache_handling"):
payload = {
"checksum": checksum,
"file_mtime": current_stat.st_mtime,
"file_size": current_stat.st_size,
}
try:
cache_path.parent.mkdir(parents=True, exist_ok=True)
with cache_path.open("w") as cache_file:
json.dump(payload, cache_file)
except IOError as ex:
log.warning("Unable to write checksum cache file %s: %s", cache_path, ex)
return checksum
def cleanup_cache() -> None:
"""Remove all cache files that are older than MAX_CACHE_FILES_AGE_SECS."""
if not CACHE_ROOT.exists():
return
with time_tracker.track_time(TimeInfo, "checksum_cache_handling"):
queue = deque([CACHE_ROOT])
rmdir_queue = []
now = time.time()
num_removed_files = 0
num_removed_dirs = 0
while queue:
path = queue.popleft()
if path.is_dir():
queue.extend(path.iterdir())
rmdir_queue.append(path)
continue
assert path.is_file()
path.relative_to(CACHE_ROOT)
age = now - path.stat().st_mtime
# Don't trust files from the future either.
if 0 <= age <= MAX_CACHE_FILES_AGE_SECS:
continue
path.unlink()
num_removed_files += 1
for dirpath in reversed(rmdir_queue):
assert dirpath.is_dir()
dirpath.relative_to(CACHE_ROOT)
try:
dirpath.rmdir()
num_removed_dirs += 1
except OSError:
pass
if num_removed_dirs or num_removed_files:
log.info(
"Cache Cleanup: removed %d dirs and %d files",
num_removed_dirs,
num_removed_files,
)

View File

@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-3.0-or-later
"""BAT packing interface for Flamenco."""
from dataclasses import dataclass
@ -8,7 +9,7 @@ import queue
import threading
import typing
from . import wheels
from .. import wheels
pack = wheels.load_wheel("blender_asset_tracer.pack")
progress = wheels.load_wheel("blender_asset_tracer.pack.progress")
@ -122,39 +123,6 @@ class BatProgress(progress.Callback): # type: ignore
pass
# class ShamanPacker(shaman.ShamanPacker):
# """Packer with support for getting an auth token from Flamenco Server."""
# def __init__(
# self,
# bfile: Path,
# project: Path,
# target: str,
# endpoint: str,
# checkout_id: str,
# *,
# manager_id: str,
# **kwargs
# ) -> None:
# self.manager_id = manager_id
# super().__init__(bfile, project, target, endpoint, checkout_id, **kwargs)
# def _get_auth_token(self) -> str:
# """get a token from Flamenco Server"""
# from ..blender import PILLAR_SERVER_URL
# from ..pillar import blender_id_subclient, uncached_session, SUBCLIENT_ID
# url = urllib.parse.urljoin(
# PILLAR_SERVER_URL, "flamenco/jwt/generate-token/%s" % self.manager_id
# )
# auth_token = blender_id_subclient()["token"]
# resp = uncached_session.get(url, auth=(auth_token, SUBCLIENT_ID))
# resp.raise_for_status()
# return resp.text
class PackThread(threading.Thread):
queue: queue.SimpleQueue[Message]
@ -228,7 +196,7 @@ def copy( # type: ignore
exclusion_filter: str,
*,
relative_only: bool,
packer_class=pack.Packer, # type: ignore
packer_class=pack.Packer,
**packer_args: dict[Any, Any],
) -> PackThread:
"""Use BAT to copy the given file and dependencies to the target location.

View File

@ -0,0 +1,440 @@
# SPDX-License-Identifier: GPL-3.0-or-later
"""BAT interface for sending files to the Manager via the Shaman API."""
import logging
import random
from collections import deque
from pathlib import Path, PurePath, PurePosixPath
from typing import TYPE_CHECKING, Optional, Any
from .. import wheels
from . import cache
bat_pack = wheels.load_wheel("blender_asset_tracer.pack")
bat_transfer = wheels.load_wheel("blender_asset_tracer.pack.transfer")
bat_bpathlib = wheels.load_wheel("blender_asset_tracer.bpathlib")
if TYPE_CHECKING:
from ..manager import ApiClient as _ApiClient
from ..manager.models import (
ShamanRequirementsRequest as _ShamanRequirementsRequest,
ShamanRequirementsResponse as _ShamanRequirementsResponse,
ShamanFileSpec as _ShamanFileSpec,
)
else:
_ApiClient = object
_ShamanRequirementsRequest = object
_ShamanRequirementsResponse = object
_ShamanFileSpec = object
log = logging.getLogger(__name__)
MAX_DEFERRED_PATHS = 8
MAX_FAILED_PATHS = 8
# Mypy doesn't understand that bat_pack.Packer exists.
class Packer(bat_pack.Packer): # type: ignore
"""Creates BAT Packs on a Shaman server."""
def __init__(
self,
blendfile: Path,
project_root: Path,
target: str,
api_client: _ApiClient,
checkout_path: str,
**kwargs: dict[Any, Any],
) -> None:
"""Constructor
:param target: mock target root directory to construct project-relative paths.
"""
super().__init__(blendfile, project_root, target, **kwargs)
self.checkout_path = checkout_path
self.api_client = api_client
# Mypy doesn't understand that bat_transfer.FileTransferer exists.
def _create_file_transferer(self) -> bat_transfer.FileTransferer: # type: ignore
return Transferrer(self.api_client, self.project, self.checkout_path)
def _make_target_path(self, target: str) -> PurePath:
return PurePosixPath("/")
@property
def output_path(self) -> PurePath:
"""The path of the packed blend file in the target directory."""
assert self._output_path is not None
checkout_location = PurePosixPath(self.checkout_path)
rel_output = self._output_path.relative_to(self._target_path)
out_path: PurePath = checkout_location / rel_output
return out_path
def execute(self):
try:
super().execute()
except Exception as ex:
log.exception("Error communicating with Shaman")
self.abort(str(ex))
self._check_aborted()
class Transferrer(bat_transfer.FileTransferer): # type: ignore
"""Sends files to a Shaman server."""
class AbortUpload(Exception):
"""Raised from the upload callback to abort an upload."""
def __init__(
self,
api_client: _ApiClient,
local_project_root: Path,
checkout_path: str,
) -> None:
super().__init__()
from ..manager.apis import ShamanApi
self.shaman_api = ShamanApi(api_client)
self.project_root = local_project_root
self.checkout_path = checkout_path
self.log = log.getChild(self.__class__.__name__)
self.uploaded_files = 0
self.uploaded_bytes = 0
# Mapping from the relative path (as used in Shaman requests) to the
# absolute path where we can find the local file. This is typically just
# the same as the relative path (relative to the project root), but can
# also point to a temporary file when it had to be rewritten.
self._rel_to_local_path: dict[str, Path] = {}
# Temporary files that should be deleted before stopping.
self._delete_when_done: list[Path] = []
# noinspection PyBroadException
def run(self) -> None:
try:
self._run()
except Exception as ex:
# We have to catch exceptions in a broad way, as this is running in
# a separate thread, and exceptions won't otherwise be seen.
self.log.exception("Error transferring files to Shaman")
self.error_set("Unexpected exception transferring files to Shaman: %s" % ex)
finally:
# Delete the files that were supposed to be moved.
for src in self._delete_when_done:
self.delete_file(src)
self._delete_when_done.clear()
def _run(self) -> None:
self.uploaded_files = 0
self.uploaded_bytes = 0
# Construct the Shaman Checkout Definition file.
shaman_file_specs = self._create_checkout_definition()
if not shaman_file_specs:
# An error has already been logged.
return
failed_files = self._upload_missing_files(shaman_file_specs)
if failed_files:
self.log.error("Aborting upload due to too many failures")
self.error_set("Giving up after multiple attempts to upload the files")
return
self.log.info("All files uploaded succesfully")
self._request_checkout(shaman_file_specs)
def _upload_missing_files(
self, shaman_file_specs: _ShamanRequirementsRequest
) -> set[_ShamanFileSpec]:
self.log.info("Feeding %d files to the Shaman", len(shaman_file_specs.files))
if self.log.isEnabledFor(logging.INFO):
for spec in shaman_file_specs.files:
self.log.info(" - %s", spec.path)
# Try to upload all the files.
failed_files: set[_ShamanFileSpec] = set()
max_tries = 50
for try_index in range(max_tries):
# Send the file to the Shaman and see what we still need to send there.
to_upload = self._send_checkout_def_to_shaman(shaman_file_specs)
if to_upload is None:
# An error has already been logged.
return failed_files
if not to_upload:
break
# Send the files that still need to be sent.
self.log.info("Upload attempt %d", try_index + 1)
failed_files = self._upload_files(to_upload)
if not failed_files:
break
# Having failed paths at this point is expected when multiple
# clients are sending the same files. Instead of retrying on a
# file-by-file basis, we just re-send the checkout definition
# file to the Shaman and obtain a new list of files to upload.
return failed_files
def _create_checkout_definition(self) -> Optional[_ShamanRequirementsRequest]:
"""Create the checkout definition file for this BAT pack.
:returns: the checkout definition.
If there was an error and file transfer was aborted, the checkout
definition will be empty.
"""
from ..manager.models import (
ShamanRequirementsRequest,
ShamanFileSpec,
)
filespecs: list[ShamanFileSpec] = []
for src, dst, act in self.iter_queue():
try:
checksum = cache.compute_cached_checksum(src)
filesize = src.stat().st_size
relpath: str = bat_bpathlib.strip_root(dst)
filespec = ShamanFileSpec(
sha=checksum,
size=filesize,
path=relpath,
)
filespecs.append(filespec)
self._rel_to_local_path[relpath] = src
if act == bat_transfer.Action.MOVE:
self._delete_when_done.append(src)
except Exception:
# We have to catch exceptions in a broad way, as this is running in
# a separate thread, and exceptions won't otherwise be seen.
msg = "Error transferring %s to %s" % (src, dst)
self.log.exception(msg)
# Put the files to copy back into the queue, and abort. This allows
# the main thread to inspect the queue and see which files were not
# copied. The one we just failed (due to this exception) should also
# be reported there.
self.queue.put((src, dst, act))
self.error_set(msg)
return None
cache.cleanup_cache()
specs: ShamanRequirementsRequest = ShamanRequirementsRequest(files=filespecs)
return specs
def _send_checkout_def_to_shaman(
self,
requirements: _ShamanRequirementsRequest,
) -> Optional[deque[_ShamanFileSpec]]:
"""Send the checkout definition file to the Shaman.
:return: An iterable of file specs that still need to be uploaded, or
None if there was an error.
"""
from ..manager.exceptions import ApiException
try:
resp = self.shaman_api.shaman_checkout_requirements(requirements)
except ApiException as ex:
# TODO: the body should be JSON of a predefined type, parse it to get the actual message.
msg = "Error from Shaman, code %d: %s" % (ex.status, ex.body)
self.log.error(msg)
self.error_set(msg)
return None
assert isinstance(resp, _ShamanRequirementsResponse)
to_upload: deque[_ShamanFileSpec] = deque()
for file_spec in resp.files:
if file_spec.path not in self._rel_to_local_path:
msg = (
"Shaman requested path we did not intend to upload: %r" % file_spec
)
self.log.error(msg)
self.error_set(msg)
return None
self.log.debug(" %s: %s", file_spec.status, file_spec.path)
match file_spec.status.value:
case "unknown":
to_upload.appendleft(file_spec)
case "uploading":
to_upload.append(file_spec)
case _:
msg = "Unknown status in response from Shaman: %r" % file_spec
self.log.error(msg)
self.error_set(msg)
return None
return to_upload
def _upload_files(self, to_upload: deque[_ShamanFileSpec]) -> set[_ShamanFileSpec]:
"""Actually upload the files to Shaman.
Returns the set of files that we did not upload.
"""
if not to_upload:
self.log.info("All files are at the Shaman already")
self.report_transferred(0)
return set()
from ..manager.exceptions import ApiException
failed_specs: set[_ShamanFileSpec] = set()
deferred_specs: set[_ShamanFileSpec] = set()
def defer(filespec: _ShamanFileSpec) -> None:
nonlocal to_upload
self.log.info(
" %s deferred (already being uploaded by someone else)", filespec.path
)
deferred_specs.add(filespec)
# Instead of deferring this one file, randomize the files to upload.
# This prevents multiple deferrals when someone else is uploading
# files from the same project (because it probably happens alphabetically).
all_files = list(to_upload)
random.shuffle(all_files)
to_upload = deque(all_files)
self.log.info(
"Going to upload %d of %d files", len(to_upload), len(self._spec_to_paths)
)
while to_upload:
# After too many failures, just retry to get a fresh set of files to upload.
if len(failed_specs) > MAX_FAILED_PATHS:
self.log.info("Too many failures, going to abort this iteration")
failed_specs.update(to_upload)
return failed_specs
file_spec = to_upload.popleft()
self.log.info(" %s", file_spec.path)
# Pre-flight check. The generated API code will load the entire file into
# memory before sending it to the Shaman. It's faster to do a check at
# Shaman first, to see if we need uploading at all.
check_resp = self.shaman_api.shaman_file_store_check(
checksum=file_spec.sha,
filesize=file_spec.size,
)
if check_resp.status.value == "stored":
self.log.info(" %s: skipping, already on server", file_spec.path)
continue
# Let the Shaman know whether we can defer uploading this file or not.
can_defer = (
len(deferred_specs) < MAX_DEFERRED_PATHS
and file_spec not in deferred_specs
and len(to_upload)
)
local_filepath = self._rel_to_local_path[file_spec.path]
try:
with local_filepath.open("rb") as file_reader:
self.shaman_api.shaman_file_store(
checksum=file_spec.sha,
filesize=file_spec.size,
body=file_reader,
x_shaman_can_defer_upload=can_defer,
x_shaman_original_filename=file_spec.path,
)
except ApiException as ex:
match ex.status:
case 425: # Too Early, i.e. defer uploading this file.
self.log.info(
" %s: someone else is uploading this file, deferring",
file_spec.path,
)
defer(file_spec)
continue
case 417: # Expectation Failed; mismatch of checksum or file size.
msg = "Error from Shaman uploading %s, code %d: %s" % (
file_spec.path,
ex.status,
ex.body,
)
case _: # Unknown error
msg = "API exception\nHeaders: %s\nBody: %s\n" % (
ex.headers,
ex.body,
)
self.log.error(msg)
self.error_set(msg)
failed_specs.add(file_spec)
return failed_specs
failed_specs.discard(file_spec)
self.uploaded_files += 1
file_size = local_filepath.stat().st_size
self.uploaded_bytes += file_size
self.report_transferred(file_size)
if failed_specs:
self.log.info(
"Uploaded %d bytes in %d files so far",
self.uploaded_bytes,
self.uploaded_files,
)
return failed_specs
self.log.info(
"Done uploading %d bytes in %d files",
self.uploaded_bytes,
self.uploaded_files,
)
return set()
def report_transferred(self, bytes_transferred: int) -> None:
if self._abort.is_set():
self.log.warning("Interrupting ongoing upload")
raise self.AbortUpload("interrupting ongoing upload")
super().report_transferred(bytes_transferred)
def _request_checkout(self, shaman_file_specs: _ShamanRequirementsRequest) -> None:
"""Ask the Shaman to create a checkout of this BAT pack."""
if not self.checkout_path:
self.log.warning("NOT requesting checkout at Shaman")
return
from ..manager.models import ShamanCheckout
from ..manager.exceptions import ApiException
self.log.info(
"Requesting checkout at Shaman for checkout_path=%r", self.checkout_path
)
checkoutRequest = ShamanCheckout(
files=shaman_file_specs.files,
checkout_path=self.checkout_path,
)
try:
self.shaman_api.shaman_checkout(checkoutRequest)
except ApiException as ex:
match ex.status:
case 424: # Files were missing
msg = "We did not upload some files, checkout aborted"
case 409: # Checkout already exists
msg = (
"There is already an existing checkout at %s"
% self.checkout_path
)
case _: # Unknown error
msg = "API exception\nHeaders: %s\nBody: %s\n" % (
ex.headers,
ex.body,
)
self.log.error(msg)
self.error_set(msg)
return
self.log.info("Shaman created checkout at %s", self.checkout_path)

View File

@ -0,0 +1,32 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
#
# (c) 2019, Blender Foundation - Sybren A. Stüvel
import contextlib
import time
from typing import Any, Iterator
@contextlib.contextmanager
def track_time(tracker_object: Any, attribute: str) -> Iterator[None]:
"""Context manager, tracks how long the context took to run."""
start_time = time.monotonic()
yield
duration = time.monotonic() - start_time
tracked_so_far = getattr(tracker_object, attribute, 0.0)
setattr(tracker_object, attribute, tracked_so_far + duration)

View File

@ -10,7 +10,7 @@
"""
__version__ = "7bfde1df-dirty"
__version__ = "63793f42-dirty"
# import ApiClient
from flamenco.manager.api_client import ApiClient

View File

@ -0,0 +1,591 @@
"""
Flamenco manager
Render Farm manager API # noqa: E501
The version of the OpenAPI document: 1.0.0
Generated by: https://openapi-generator.tech
"""
import re # noqa: F401
import sys # noqa: F401
from flamenco.manager.api_client import ApiClient, Endpoint as _Endpoint
from flamenco.manager.model_utils import ( # noqa: F401
check_allowed_values,
check_validations,
date,
datetime,
file_type,
none_type,
validate_and_convert_types
)
from flamenco.manager.model.error import Error
from flamenco.manager.model.shaman_checkout import ShamanCheckout
from flamenco.manager.model.shaman_requirements_request import ShamanRequirementsRequest
from flamenco.manager.model.shaman_requirements_response import ShamanRequirementsResponse
from flamenco.manager.model.shaman_single_file_status import ShamanSingleFileStatus
class ShamanApi(object):
"""NOTE: This class is auto generated by OpenAPI Generator
Ref: https://openapi-generator.tech
Do not edit the class manually.
"""
def __init__(self, api_client=None):
if api_client is None:
api_client = ApiClient()
self.api_client = api_client
self.shaman_checkout_endpoint = _Endpoint(
settings={
'response_type': None,
'auth': [],
'endpoint_path': '/shaman/checkout/create',
'operation_id': 'shaman_checkout',
'http_method': 'POST',
'servers': None,
},
params_map={
'all': [
'shaman_checkout',
],
'required': [
'shaman_checkout',
],
'nullable': [
],
'enum': [
],
'validation': [
]
},
root_map={
'validations': {
},
'allowed_values': {
},
'openapi_types': {
'shaman_checkout':
(ShamanCheckout,),
},
'attribute_map': {
},
'location_map': {
'shaman_checkout': 'body',
},
'collection_format_map': {
}
},
headers_map={
'accept': [
'application/json'
],
'content_type': [
'application/json'
]
},
api_client=api_client
)
self.shaman_checkout_requirements_endpoint = _Endpoint(
settings={
'response_type': (ShamanRequirementsResponse,),
'auth': [],
'endpoint_path': '/shaman/checkout/requirements',
'operation_id': 'shaman_checkout_requirements',
'http_method': 'POST',
'servers': None,
},
params_map={
'all': [
'shaman_requirements_request',
],
'required': [
'shaman_requirements_request',
],
'nullable': [
],
'enum': [
],
'validation': [
]
},
root_map={
'validations': {
},
'allowed_values': {
},
'openapi_types': {
'shaman_requirements_request':
(ShamanRequirementsRequest,),
},
'attribute_map': {
},
'location_map': {
'shaman_requirements_request': 'body',
},
'collection_format_map': {
}
},
headers_map={
'accept': [
'application/json'
],
'content_type': [
'application/json'
]
},
api_client=api_client
)
self.shaman_file_store_endpoint = _Endpoint(
settings={
'response_type': None,
'auth': [],
'endpoint_path': '/shaman/files/{checksum}/{filesize}',
'operation_id': 'shaman_file_store',
'http_method': 'POST',
'servers': None,
},
params_map={
'all': [
'checksum',
'filesize',
'body',
'x_shaman_can_defer_upload',
'x_shaman_original_filename',
],
'required': [
'checksum',
'filesize',
'body',
],
'nullable': [
],
'enum': [
],
'validation': [
]
},
root_map={
'validations': {
},
'allowed_values': {
},
'openapi_types': {
'checksum':
(str,),
'filesize':
(int,),
'body':
(file_type,),
'x_shaman_can_defer_upload':
(bool,),
'x_shaman_original_filename':
(str,),
},
'attribute_map': {
'checksum': 'checksum',
'filesize': 'filesize',
'x_shaman_can_defer_upload': 'X-Shaman-Can-Defer-Upload',
'x_shaman_original_filename': 'X-Shaman-Original-Filename',
},
'location_map': {
'checksum': 'path',
'filesize': 'path',
'body': 'body',
'x_shaman_can_defer_upload': 'header',
'x_shaman_original_filename': 'header',
},
'collection_format_map': {
}
},
headers_map={
'accept': [
'application/json'
],
'content_type': [
'application/octet-stream'
]
},
api_client=api_client
)
self.shaman_file_store_check_endpoint = _Endpoint(
settings={
'response_type': (ShamanSingleFileStatus,),
'auth': [],
'endpoint_path': '/shaman/files/{checksum}/{filesize}',
'operation_id': 'shaman_file_store_check',
'http_method': 'OPTIONS',
'servers': None,
},
params_map={
'all': [
'checksum',
'filesize',
],
'required': [
'checksum',
'filesize',
],
'nullable': [
],
'enum': [
],
'validation': [
]
},
root_map={
'validations': {
},
'allowed_values': {
},
'openapi_types': {
'checksum':
(str,),
'filesize':
(int,),
},
'attribute_map': {
'checksum': 'checksum',
'filesize': 'filesize',
},
'location_map': {
'checksum': 'path',
'filesize': 'path',
},
'collection_format_map': {
}
},
headers_map={
'accept': [
'application/json'
],
'content_type': [],
},
api_client=api_client
)
def shaman_checkout(
self,
shaman_checkout,
**kwargs
):
"""Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint. # noqa: E501
This method makes a synchronous HTTP request by default. To make an
asynchronous HTTP request, please pass async_req=True
>>> thread = api.shaman_checkout(shaman_checkout, async_req=True)
>>> result = thread.get()
Args:
shaman_checkout (ShamanCheckout): Set of files to check out.
Keyword Args:
_return_http_data_only (bool): response data without head status
code and headers. Default is True.
_preload_content (bool): if False, the urllib3.HTTPResponse object
will be returned without reading/decoding response data.
Default is True.
_request_timeout (int/float/tuple): timeout setting for this request. If
one number provided, it will be total request timeout. It can also
be a pair (tuple) of (connection, read) timeouts.
Default is None.
_check_input_type (bool): specifies if type checking
should be done one the data sent to the server.
Default is True.
_check_return_type (bool): specifies if type checking
should be done one the data received from the server.
Default is True.
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_content_type (str/None): force body content-type.
Default is None and content-type will be predicted by allowed
content-types and body.
_host_index (int/None): specifies the index of the server
that we want to use.
Default is read from the configuration.
async_req (bool): execute request asynchronously
Returns:
None
If the method is called asynchronously, returns the request
thread.
"""
kwargs['async_req'] = kwargs.get(
'async_req', False
)
kwargs['_return_http_data_only'] = kwargs.get(
'_return_http_data_only', True
)
kwargs['_preload_content'] = kwargs.get(
'_preload_content', True
)
kwargs['_request_timeout'] = kwargs.get(
'_request_timeout', None
)
kwargs['_check_input_type'] = kwargs.get(
'_check_input_type', True
)
kwargs['_check_return_type'] = kwargs.get(
'_check_return_type', True
)
kwargs['_spec_property_naming'] = kwargs.get(
'_spec_property_naming', False
)
kwargs['_content_type'] = kwargs.get(
'_content_type')
kwargs['_host_index'] = kwargs.get('_host_index')
kwargs['shaman_checkout'] = \
shaman_checkout
return self.shaman_checkout_endpoint.call_with_http_info(**kwargs)
def shaman_checkout_requirements(
self,
shaman_requirements_request,
**kwargs
):
"""Checks a Shaman Requirements file, and reports which files are unknown. # noqa: E501
This method makes a synchronous HTTP request by default. To make an
asynchronous HTTP request, please pass async_req=True
>>> thread = api.shaman_checkout_requirements(shaman_requirements_request, async_req=True)
>>> result = thread.get()
Args:
shaman_requirements_request (ShamanRequirementsRequest): Set of files to check
Keyword Args:
_return_http_data_only (bool): response data without head status
code and headers. Default is True.
_preload_content (bool): if False, the urllib3.HTTPResponse object
will be returned without reading/decoding response data.
Default is True.
_request_timeout (int/float/tuple): timeout setting for this request. If
one number provided, it will be total request timeout. It can also
be a pair (tuple) of (connection, read) timeouts.
Default is None.
_check_input_type (bool): specifies if type checking
should be done one the data sent to the server.
Default is True.
_check_return_type (bool): specifies if type checking
should be done one the data received from the server.
Default is True.
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_content_type (str/None): force body content-type.
Default is None and content-type will be predicted by allowed
content-types and body.
_host_index (int/None): specifies the index of the server
that we want to use.
Default is read from the configuration.
async_req (bool): execute request asynchronously
Returns:
ShamanRequirementsResponse
If the method is called asynchronously, returns the request
thread.
"""
kwargs['async_req'] = kwargs.get(
'async_req', False
)
kwargs['_return_http_data_only'] = kwargs.get(
'_return_http_data_only', True
)
kwargs['_preload_content'] = kwargs.get(
'_preload_content', True
)
kwargs['_request_timeout'] = kwargs.get(
'_request_timeout', None
)
kwargs['_check_input_type'] = kwargs.get(
'_check_input_type', True
)
kwargs['_check_return_type'] = kwargs.get(
'_check_return_type', True
)
kwargs['_spec_property_naming'] = kwargs.get(
'_spec_property_naming', False
)
kwargs['_content_type'] = kwargs.get(
'_content_type')
kwargs['_host_index'] = kwargs.get('_host_index')
kwargs['shaman_requirements_request'] = \
shaman_requirements_request
return self.shaman_checkout_requirements_endpoint.call_with_http_info(**kwargs)
def shaman_file_store(
self,
checksum,
filesize,
body,
**kwargs
):
"""Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body. # noqa: E501
This method makes a synchronous HTTP request by default. To make an
asynchronous HTTP request, please pass async_req=True
>>> thread = api.shaman_file_store(checksum, filesize, body, async_req=True)
>>> result = thread.get()
Args:
checksum (str): SHA256 checksum of the file.
filesize (int): Size of the file in bytes.
body (file_type): Contents of the file
Keyword Args:
x_shaman_can_defer_upload (bool): The client indicates that it can defer uploading this file. The \"208\" response will not only be returned when the file is already fully known to the Shaman server, but also when someone else is currently uploading this file. . [optional]
x_shaman_original_filename (str): The original filename. If sent along with the request, it will be included in the server logs, which can aid in debugging. . [optional]
_return_http_data_only (bool): response data without head status
code and headers. Default is True.
_preload_content (bool): if False, the urllib3.HTTPResponse object
will be returned without reading/decoding response data.
Default is True.
_request_timeout (int/float/tuple): timeout setting for this request. If
one number provided, it will be total request timeout. It can also
be a pair (tuple) of (connection, read) timeouts.
Default is None.
_check_input_type (bool): specifies if type checking
should be done one the data sent to the server.
Default is True.
_check_return_type (bool): specifies if type checking
should be done one the data received from the server.
Default is True.
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_content_type (str/None): force body content-type.
Default is None and content-type will be predicted by allowed
content-types and body.
_host_index (int/None): specifies the index of the server
that we want to use.
Default is read from the configuration.
async_req (bool): execute request asynchronously
Returns:
None
If the method is called asynchronously, returns the request
thread.
"""
kwargs['async_req'] = kwargs.get(
'async_req', False
)
kwargs['_return_http_data_only'] = kwargs.get(
'_return_http_data_only', True
)
kwargs['_preload_content'] = kwargs.get(
'_preload_content', True
)
kwargs['_request_timeout'] = kwargs.get(
'_request_timeout', None
)
kwargs['_check_input_type'] = kwargs.get(
'_check_input_type', True
)
kwargs['_check_return_type'] = kwargs.get(
'_check_return_type', True
)
kwargs['_spec_property_naming'] = kwargs.get(
'_spec_property_naming', False
)
kwargs['_content_type'] = kwargs.get(
'_content_type')
kwargs['_host_index'] = kwargs.get('_host_index')
kwargs['checksum'] = \
checksum
kwargs['filesize'] = \
filesize
kwargs['body'] = \
body
return self.shaman_file_store_endpoint.call_with_http_info(**kwargs)
def shaman_file_store_check(
self,
checksum,
filesize,
**kwargs
):
"""Check the status of a file on the Shaman server. # noqa: E501
This method makes a synchronous HTTP request by default. To make an
asynchronous HTTP request, please pass async_req=True
>>> thread = api.shaman_file_store_check(checksum, filesize, async_req=True)
>>> result = thread.get()
Args:
checksum (str): SHA256 checksum of the file.
filesize (int): Size of the file in bytes.
Keyword Args:
_return_http_data_only (bool): response data without head status
code and headers. Default is True.
_preload_content (bool): if False, the urllib3.HTTPResponse object
will be returned without reading/decoding response data.
Default is True.
_request_timeout (int/float/tuple): timeout setting for this request. If
one number provided, it will be total request timeout. It can also
be a pair (tuple) of (connection, read) timeouts.
Default is None.
_check_input_type (bool): specifies if type checking
should be done one the data sent to the server.
Default is True.
_check_return_type (bool): specifies if type checking
should be done one the data received from the server.
Default is True.
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_content_type (str/None): force body content-type.
Default is None and content-type will be predicted by allowed
content-types and body.
_host_index (int/None): specifies the index of the server
that we want to use.
Default is read from the configuration.
async_req (bool): execute request asynchronously
Returns:
ShamanSingleFileStatus
If the method is called asynchronously, returns the request
thread.
"""
kwargs['async_req'] = kwargs.get(
'async_req', False
)
kwargs['_return_http_data_only'] = kwargs.get(
'_return_http_data_only', True
)
kwargs['_preload_content'] = kwargs.get(
'_preload_content', True
)
kwargs['_request_timeout'] = kwargs.get(
'_request_timeout', None
)
kwargs['_check_input_type'] = kwargs.get(
'_check_input_type', True
)
kwargs['_check_return_type'] = kwargs.get(
'_check_return_type', True
)
kwargs['_spec_property_naming'] = kwargs.get(
'_spec_property_naming', False
)
kwargs['_content_type'] = kwargs.get(
'_content_type')
kwargs['_host_index'] = kwargs.get('_host_index')
kwargs['checksum'] = \
checksum
kwargs['filesize'] = \
filesize
return self.shaman_file_store_check_endpoint.call_with_http_info(**kwargs)

View File

@ -76,7 +76,7 @@ class ApiClient(object):
self.default_headers[header_name] = header_value
self.cookie = cookie
# Set default User-Agent.
self.user_agent = 'Flamenco/7bfde1df-dirty (Blender add-on)'
self.user_agent = 'Flamenco/63793f42-dirty (Blender add-on)'
def __enter__(self):
return self

View File

@ -16,4 +16,5 @@
# Import APIs into API package:
from flamenco.manager.api.jobs_api import JobsApi
from flamenco.manager.api.meta_api import MetaApi
from flamenco.manager.api.shaman_api import ShamanApi
from flamenco.manager.api.worker_api import WorkerApi

View File

@ -404,7 +404,7 @@ conf = flamenco.manager.Configuration(
"OS: {env}\n"\
"Python Version: {pyversion}\n"\
"Version of the API: 1.0.0\n"\
"SDK Package Version: 7bfde1df-dirty".\
"SDK Package Version: 63793f42-dirty".\
format(env=sys.platform, pyversion=sys.version)
def get_host_settings(self):

View File

@ -0,0 +1,318 @@
# flamenco.manager.ShamanApi
All URIs are relative to *http://localhost*
Method | HTTP request | Description
------------- | ------------- | -------------
[**shaman_checkout**](ShamanApi.md#shaman_checkout) | **POST** /shaman/checkout/create | Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.
[**shaman_checkout_requirements**](ShamanApi.md#shaman_checkout_requirements) | **POST** /shaman/checkout/requirements | Checks a Shaman Requirements file, and reports which files are unknown.
[**shaman_file_store**](ShamanApi.md#shaman_file_store) | **POST** /shaman/files/{checksum}/{filesize} | Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file&#39;s contents should be sent in the request body.
[**shaman_file_store_check**](ShamanApi.md#shaman_file_store_check) | **OPTIONS** /shaman/files/{checksum}/{filesize} | Check the status of a file on the Shaman server.
# **shaman_checkout**
> shaman_checkout(shaman_checkout)
Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import shaman_api
from flamenco.manager.model.error import Error
from flamenco.manager.model.shaman_checkout import ShamanCheckout
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = shaman_api.ShamanApi(api_client)
shaman_checkout = ShamanCheckout(
files=[
ShamanFileSpec(
sha="sha_example",
size=1,
path="path_example",
),
],
checkout_path="checkout_path_example",
) # ShamanCheckout | Set of files to check out.
# example passing only required values which don't have defaults set
try:
# Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.
api_instance.shaman_checkout(shaman_checkout)
except flamenco.manager.ApiException as e:
print("Exception when calling ShamanApi->shaman_checkout: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**shaman_checkout** | [**ShamanCheckout**](ShamanCheckout.md)| Set of files to check out. |
### Return type
void (empty response body)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**204** | Checkout was created succesfully. | - |
**424** | There were files missing. Use &#x60;shamanCheckoutRequirements&#x60; to figure out which ones. | - |
**409** | Checkout already exists. | - |
**0** | unexpected error | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **shaman_checkout_requirements**
> ShamanRequirementsResponse shaman_checkout_requirements(shaman_requirements_request)
Checks a Shaman Requirements file, and reports which files are unknown.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import shaman_api
from flamenco.manager.model.shaman_requirements_request import ShamanRequirementsRequest
from flamenco.manager.model.error import Error
from flamenco.manager.model.shaman_requirements_response import ShamanRequirementsResponse
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = shaman_api.ShamanApi(api_client)
shaman_requirements_request = ShamanRequirementsRequest(
files=[
ShamanFileSpec(
sha="sha_example",
size=1,
path="path_example",
),
],
) # ShamanRequirementsRequest | Set of files to check
# example passing only required values which don't have defaults set
try:
# Checks a Shaman Requirements file, and reports which files are unknown.
api_response = api_instance.shaman_checkout_requirements(shaman_requirements_request)
pprint(api_response)
except flamenco.manager.ApiException as e:
print("Exception when calling ShamanApi->shaman_checkout_requirements: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**shaman_requirements_request** | [**ShamanRequirementsRequest**](ShamanRequirementsRequest.md)| Set of files to check |
### Return type
[**ShamanRequirementsResponse**](ShamanRequirementsResponse.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | Subset of the posted requirements, indicating the unknown files. | - |
**0** | unexpected error | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **shaman_file_store**
> shaman_file_store(checksum, filesize, body)
Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import shaman_api
from flamenco.manager.model.error import Error
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = shaman_api.ShamanApi(api_client)
checksum = "checksum_example" # str | SHA256 checksum of the file.
filesize = 1 # int | Size of the file in bytes.
body = open('/path/to/file', 'rb') # file_type | Contents of the file
x_shaman_can_defer_upload = True # bool | The client indicates that it can defer uploading this file. The \"208\" response will not only be returned when the file is already fully known to the Shaman server, but also when someone else is currently uploading this file. (optional)
x_shaman_original_filename = "X-Shaman-Original-Filename_example" # str | The original filename. If sent along with the request, it will be included in the server logs, which can aid in debugging. (optional)
# example passing only required values which don't have defaults set
try:
# Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body.
api_instance.shaman_file_store(checksum, filesize, body)
except flamenco.manager.ApiException as e:
print("Exception when calling ShamanApi->shaman_file_store: %s\n" % e)
# example passing only required values which don't have defaults set
# and optional values
try:
# Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body.
api_instance.shaman_file_store(checksum, filesize, body, x_shaman_can_defer_upload=x_shaman_can_defer_upload, x_shaman_original_filename=x_shaman_original_filename)
except flamenco.manager.ApiException as e:
print("Exception when calling ShamanApi->shaman_file_store: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**checksum** | **str**| SHA256 checksum of the file. |
**filesize** | **int**| Size of the file in bytes. |
**body** | **file_type**| Contents of the file |
**x_shaman_can_defer_upload** | **bool**| The client indicates that it can defer uploading this file. The \&quot;208\&quot; response will not only be returned when the file is already fully known to the Shaman server, but also when someone else is currently uploading this file. | [optional]
**x_shaman_original_filename** | **str**| The original filename. If sent along with the request, it will be included in the server logs, which can aid in debugging. | [optional]
### Return type
void (empty response body)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: application/octet-stream
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**204** | The file was accepted. | - |
**208** | The file was already known to the server. | - |
**417** | There was a mismatch between the request parameters and the actual file size or checksum of the uploaded file. | - |
**425** | Client should defer uploading this file. The file is currently in the process of being uploaded by someone else, and &#x60;X-Shaman-Can-Defer-Upload: true&#x60; was sent in the request. | - |
**0** | unexpected error | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **shaman_file_store_check**
> ShamanSingleFileStatus shaman_file_store_check(checksum, filesize)
Check the status of a file on the Shaman server.
### Example
```python
import time
import flamenco.manager
from flamenco.manager.api import shaman_api
from flamenco.manager.model.error import Error
from flamenco.manager.model.shaman_single_file_status import ShamanSingleFileStatus
from pprint import pprint
# Defining the host is optional and defaults to http://localhost
# See configuration.py for a list of all supported configuration parameters.
configuration = flamenco.manager.Configuration(
host = "http://localhost"
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient() as api_client:
# Create an instance of the API class
api_instance = shaman_api.ShamanApi(api_client)
checksum = "checksum_example" # str | SHA256 checksum of the file.
filesize = 1 # int | Size of the file in bytes.
# example passing only required values which don't have defaults set
try:
# Check the status of a file on the Shaman server.
api_response = api_instance.shaman_file_store_check(checksum, filesize)
pprint(api_response)
except flamenco.manager.ApiException as e:
print("Exception when calling ShamanApi->shaman_file_store_check: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**checksum** | **str**| SHA256 checksum of the file. |
**filesize** | **int**| Size of the file in bytes. |
### Return type
[**ShamanSingleFileStatus**](ShamanSingleFileStatus.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | Normal response. | - |
**0** | unexpected error | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

View File

@ -0,0 +1,14 @@
# ShamanCheckout
Set of files with their SHA256 checksum, size in bytes, and desired location in the checkout directory.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**files** | [**[ShamanFileSpec]**](ShamanFileSpec.md) | |
**checkout_path** | **str** | Path where the Manager should create this checkout, It is relative to the Shaman checkout path as configured on the Manager. In older versions of the Shaman this was just the \&quot;checkout ID\&quot;, but in this version it can be a path like &#x60;project-slug/scene-name/unique-ID&#x60;. |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -0,0 +1,15 @@
# ShamanFileSpec
Specification of a file in the Shaman storage.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**sha** | **str** | SHA256 checksum of the file |
**size** | **int** | File size in bytes |
**path** | **str** | Location of the file in the checkout |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -0,0 +1,15 @@
# ShamanFileSpecWithStatus
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**sha** | **str** | SHA256 checksum of the file |
**size** | **int** | File size in bytes |
**path** | **str** | Location of the file in the checkout |
**status** | [**ShamanFileStatus**](ShamanFileStatus.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -0,0 +1,12 @@
# ShamanFileSpecWithStatusAllOf
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**status** | [**ShamanFileStatus**](ShamanFileStatus.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -0,0 +1,11 @@
# ShamanFileStatus
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | **str** | | must be one of ["unknown", "uploading", "stored", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -0,0 +1,13 @@
# ShamanRequirementsRequest
Set of files with their SHA256 checksum and size in bytes.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**files** | [**[ShamanFileSpec]**](ShamanFileSpec.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -0,0 +1,13 @@
# ShamanRequirementsResponse
The files from a requirements request, with their status on the Shaman server. Files that are known to Shaman are excluded from the response.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**files** | [**[ShamanFileSpecWithStatus]**](ShamanFileSpecWithStatus.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -0,0 +1,13 @@
# ShamanSingleFileStatus
Status of a file in the Shaman storage.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**status** | [**ShamanFileStatus**](ShamanFileStatus.md) | |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -0,0 +1,273 @@
"""
Flamenco manager
Render Farm manager API # noqa: E501
The version of the OpenAPI document: 1.0.0
Generated by: https://openapi-generator.tech
"""
import re # noqa: F401
import sys # noqa: F401
from flamenco.manager.model_utils import ( # noqa: F401
ApiTypeError,
ModelComposed,
ModelNormal,
ModelSimple,
cached_property,
change_keys_js_to_python,
convert_js_args_to_python_args,
date,
datetime,
file_type,
none_type,
validate_get_composed_info,
OpenApiModel
)
from flamenco.manager.exceptions import ApiAttributeError
def lazy_import():
from flamenco.manager.model.shaman_file_spec import ShamanFileSpec
globals()['ShamanFileSpec'] = ShamanFileSpec
class ShamanCheckout(ModelNormal):
"""NOTE: This class is auto generated by OpenAPI Generator.
Ref: https://openapi-generator.tech
Do not edit the class manually.
Attributes:
allowed_values (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
with a capitalized key describing the allowed value and an allowed
value. These dicts store the allowed enum values.
attribute_map (dict): The key is attribute name
and the value is json key in definition.
discriminator_value_class_map (dict): A dict to go from the discriminator
variable value to the discriminator class name.
validations (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
that stores validations for max_length, min_length, max_items,
min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum,
inclusive_minimum, and regex.
additional_properties_type (tuple): A tuple of classes accepted
as additional properties values.
"""
allowed_values = {
}
validations = {
}
@cached_property
def additional_properties_type():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
"""
lazy_import()
return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501
_nullable = False
@cached_property
def openapi_types():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
Returns
openapi_types (dict): The key is attribute name
and the value is attribute type.
"""
lazy_import()
return {
'files': ([ShamanFileSpec],), # noqa: E501
'checkout_path': (str,), # noqa: E501
}
@cached_property
def discriminator():
return None
attribute_map = {
'files': 'files', # noqa: E501
'checkout_path': 'checkoutPath', # noqa: E501
}
read_only_vars = {
}
_composed_schemas = {}
@classmethod
@convert_js_args_to_python_args
def _from_openapi_data(cls, files, checkout_path, *args, **kwargs): # noqa: E501
"""ShamanCheckout - a model defined in OpenAPI
Args:
files ([ShamanFileSpec]):
checkout_path (str): Path where the Manager should create this checkout, It is relative to the Shaman checkout path as configured on the Manager. In older versions of the Shaman this was just the \"checkout ID\", but in this version it can be a path like `project-slug/scene-name/unique-ID`.
Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
self = super(OpenApiModel, cls).__new__(cls)
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.files = files
self.checkout_path = checkout_path
for var_name, var_value in kwargs.items():
if var_name not in self.attribute_map and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self.additional_properties_type is None:
# discard variable.
continue
setattr(self, var_name, var_value)
return self
required_properties = set([
'_data_store',
'_check_type',
'_spec_property_naming',
'_path_to_item',
'_configuration',
'_visited_composed_classes',
])
@convert_js_args_to_python_args
def __init__(self, files, checkout_path, *args, **kwargs): # noqa: E501
"""ShamanCheckout - a model defined in OpenAPI
Args:
files ([ShamanFileSpec]):
checkout_path (str): Path where the Manager should create this checkout, It is relative to the Shaman checkout path as configured on the Manager. In older versions of the Shaman this was just the \"checkout ID\", but in this version it can be a path like `project-slug/scene-name/unique-ID`.
Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.files = files
self.checkout_path = checkout_path
for var_name, var_value in kwargs.items():
if var_name not in self.attribute_map and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self.additional_properties_type is None:
# discard variable.
continue
setattr(self, var_name, var_value)
if var_name in self.read_only_vars:
raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
f"class with read only attributes.")

View File

@ -0,0 +1,273 @@
"""
Flamenco manager
Render Farm manager API # noqa: E501
The version of the OpenAPI document: 1.0.0
Generated by: https://openapi-generator.tech
"""
import re # noqa: F401
import sys # noqa: F401
from flamenco.manager.model_utils import ( # noqa: F401
ApiTypeError,
ModelComposed,
ModelNormal,
ModelSimple,
cached_property,
change_keys_js_to_python,
convert_js_args_to_python_args,
date,
datetime,
file_type,
none_type,
validate_get_composed_info,
OpenApiModel
)
from flamenco.manager.exceptions import ApiAttributeError
class ShamanFileSpec(ModelNormal):
"""NOTE: This class is auto generated by OpenAPI Generator.
Ref: https://openapi-generator.tech
Do not edit the class manually.
Attributes:
allowed_values (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
with a capitalized key describing the allowed value and an allowed
value. These dicts store the allowed enum values.
attribute_map (dict): The key is attribute name
and the value is json key in definition.
discriminator_value_class_map (dict): A dict to go from the discriminator
variable value to the discriminator class name.
validations (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
that stores validations for max_length, min_length, max_items,
min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum,
inclusive_minimum, and regex.
additional_properties_type (tuple): A tuple of classes accepted
as additional properties values.
"""
allowed_values = {
}
validations = {
}
@cached_property
def additional_properties_type():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
"""
return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501
_nullable = False
@cached_property
def openapi_types():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
Returns
openapi_types (dict): The key is attribute name
and the value is attribute type.
"""
return {
'sha': (str,), # noqa: E501
'size': (int,), # noqa: E501
'path': (str,), # noqa: E501
}
@cached_property
def discriminator():
return None
attribute_map = {
'sha': 'sha', # noqa: E501
'size': 'size', # noqa: E501
'path': 'path', # noqa: E501
}
read_only_vars = {
}
_composed_schemas = {}
@classmethod
@convert_js_args_to_python_args
def _from_openapi_data(cls, sha, size, path, *args, **kwargs): # noqa: E501
"""ShamanFileSpec - a model defined in OpenAPI
Args:
sha (str): SHA256 checksum of the file
size (int): File size in bytes
path (str): Location of the file in the checkout
Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
self = super(OpenApiModel, cls).__new__(cls)
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.sha = sha
self.size = size
self.path = path
for var_name, var_value in kwargs.items():
if var_name not in self.attribute_map and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self.additional_properties_type is None:
# discard variable.
continue
setattr(self, var_name, var_value)
return self
required_properties = set([
'_data_store',
'_check_type',
'_spec_property_naming',
'_path_to_item',
'_configuration',
'_visited_composed_classes',
])
@convert_js_args_to_python_args
def __init__(self, sha, size, path, *args, **kwargs): # noqa: E501
"""ShamanFileSpec - a model defined in OpenAPI
Args:
sha (str): SHA256 checksum of the file
size (int): File size in bytes
path (str): Location of the file in the checkout
Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.sha = sha
self.size = size
self.path = path
for var_name, var_value in kwargs.items():
if var_name not in self.attribute_map and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self.additional_properties_type is None:
# discard variable.
continue
setattr(self, var_name, var_value)
if var_name in self.read_only_vars:
raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
f"class with read only attributes.")

View File

@ -0,0 +1,328 @@
"""
Flamenco manager
Render Farm manager API # noqa: E501
The version of the OpenAPI document: 1.0.0
Generated by: https://openapi-generator.tech
"""
import re # noqa: F401
import sys # noqa: F401
from flamenco.manager.model_utils import ( # noqa: F401
ApiTypeError,
ModelComposed,
ModelNormal,
ModelSimple,
cached_property,
change_keys_js_to_python,
convert_js_args_to_python_args,
date,
datetime,
file_type,
none_type,
validate_get_composed_info,
OpenApiModel
)
from flamenco.manager.exceptions import ApiAttributeError
def lazy_import():
from flamenco.manager.model.shaman_file_spec import ShamanFileSpec
from flamenco.manager.model.shaman_file_spec_with_status_all_of import ShamanFileSpecWithStatusAllOf
from flamenco.manager.model.shaman_file_status import ShamanFileStatus
globals()['ShamanFileSpec'] = ShamanFileSpec
globals()['ShamanFileSpecWithStatusAllOf'] = ShamanFileSpecWithStatusAllOf
globals()['ShamanFileStatus'] = ShamanFileStatus
class ShamanFileSpecWithStatus(ModelComposed):
"""NOTE: This class is auto generated by OpenAPI Generator.
Ref: https://openapi-generator.tech
Do not edit the class manually.
Attributes:
allowed_values (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
with a capitalized key describing the allowed value and an allowed
value. These dicts store the allowed enum values.
attribute_map (dict): The key is attribute name
and the value is json key in definition.
discriminator_value_class_map (dict): A dict to go from the discriminator
variable value to the discriminator class name.
validations (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
that stores validations for max_length, min_length, max_items,
min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum,
inclusive_minimum, and regex.
additional_properties_type (tuple): A tuple of classes accepted
as additional properties values.
"""
allowed_values = {
}
validations = {
}
@cached_property
def additional_properties_type():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
"""
lazy_import()
return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501
_nullable = False
@cached_property
def openapi_types():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
Returns
openapi_types (dict): The key is attribute name
and the value is attribute type.
"""
lazy_import()
return {
'sha': (str,), # noqa: E501
'size': (int,), # noqa: E501
'path': (str,), # noqa: E501
'status': (ShamanFileStatus,), # noqa: E501
}
@cached_property
def discriminator():
return None
attribute_map = {
'sha': 'sha', # noqa: E501
'size': 'size', # noqa: E501
'path': 'path', # noqa: E501
'status': 'status', # noqa: E501
}
read_only_vars = {
}
@classmethod
@convert_js_args_to_python_args
def _from_openapi_data(cls, *args, **kwargs): # noqa: E501
"""ShamanFileSpecWithStatus - a model defined in OpenAPI
Keyword Args:
sha (str): SHA256 checksum of the file
size (int): File size in bytes
path (str): Location of the file in the checkout
status (ShamanFileStatus):
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
self = super(OpenApiModel, cls).__new__(cls)
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
constant_args = {
'_check_type': _check_type,
'_path_to_item': _path_to_item,
'_spec_property_naming': _spec_property_naming,
'_configuration': _configuration,
'_visited_composed_classes': self._visited_composed_classes,
}
composed_info = validate_get_composed_info(
constant_args, kwargs, self)
self._composed_instances = composed_info[0]
self._var_name_to_model_instances = composed_info[1]
self._additional_properties_model_instances = composed_info[2]
discarded_args = composed_info[3]
for var_name, var_value in kwargs.items():
if var_name in discarded_args and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self._additional_properties_model_instances:
# discard variable.
continue
setattr(self, var_name, var_value)
return self
required_properties = set([
'_data_store',
'_check_type',
'_spec_property_naming',
'_path_to_item',
'_configuration',
'_visited_composed_classes',
'_composed_instances',
'_var_name_to_model_instances',
'_additional_properties_model_instances',
])
@convert_js_args_to_python_args
def __init__(self, *args, **kwargs): # noqa: E501
"""ShamanFileSpecWithStatus - a model defined in OpenAPI
Keyword Args:
sha (str): SHA256 checksum of the file
size (int): File size in bytes
path (str): Location of the file in the checkout
status (ShamanFileStatus):
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
constant_args = {
'_check_type': _check_type,
'_path_to_item': _path_to_item,
'_spec_property_naming': _spec_property_naming,
'_configuration': _configuration,
'_visited_composed_classes': self._visited_composed_classes,
}
composed_info = validate_get_composed_info(
constant_args, kwargs, self)
self._composed_instances = composed_info[0]
self._var_name_to_model_instances = composed_info[1]
self._additional_properties_model_instances = composed_info[2]
discarded_args = composed_info[3]
for var_name, var_value in kwargs.items():
if var_name in discarded_args and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self._additional_properties_model_instances:
# discard variable.
continue
setattr(self, var_name, var_value)
if var_name in self.read_only_vars:
raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
f"class with read only attributes.")
@cached_property
def _composed_schemas():
# we need this here to make our import statements work
# we must store _composed_schemas in here so the code is only run
# when we invoke this method. If we kept this at the class
# level we would get an error because the class level
# code would be run when this module is imported, and these composed
# classes don't exist yet because their module has not finished
# loading
lazy_import()
return {
'anyOf': [
],
'allOf': [
ShamanFileSpec,
ShamanFileSpecWithStatusAllOf,
],
'oneOf': [
],
}

View File

@ -0,0 +1,267 @@
"""
Flamenco manager
Render Farm manager API # noqa: E501
The version of the OpenAPI document: 1.0.0
Generated by: https://openapi-generator.tech
"""
import re # noqa: F401
import sys # noqa: F401
from flamenco.manager.model_utils import ( # noqa: F401
ApiTypeError,
ModelComposed,
ModelNormal,
ModelSimple,
cached_property,
change_keys_js_to_python,
convert_js_args_to_python_args,
date,
datetime,
file_type,
none_type,
validate_get_composed_info,
OpenApiModel
)
from flamenco.manager.exceptions import ApiAttributeError
def lazy_import():
from flamenco.manager.model.shaman_file_status import ShamanFileStatus
globals()['ShamanFileStatus'] = ShamanFileStatus
class ShamanFileSpecWithStatusAllOf(ModelNormal):
"""NOTE: This class is auto generated by OpenAPI Generator.
Ref: https://openapi-generator.tech
Do not edit the class manually.
Attributes:
allowed_values (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
with a capitalized key describing the allowed value and an allowed
value. These dicts store the allowed enum values.
attribute_map (dict): The key is attribute name
and the value is json key in definition.
discriminator_value_class_map (dict): A dict to go from the discriminator
variable value to the discriminator class name.
validations (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
that stores validations for max_length, min_length, max_items,
min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum,
inclusive_minimum, and regex.
additional_properties_type (tuple): A tuple of classes accepted
as additional properties values.
"""
allowed_values = {
}
validations = {
}
@cached_property
def additional_properties_type():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
"""
lazy_import()
return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501
_nullable = False
@cached_property
def openapi_types():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
Returns
openapi_types (dict): The key is attribute name
and the value is attribute type.
"""
lazy_import()
return {
'status': (ShamanFileStatus,), # noqa: E501
}
@cached_property
def discriminator():
return None
attribute_map = {
'status': 'status', # noqa: E501
}
read_only_vars = {
}
_composed_schemas = {}
@classmethod
@convert_js_args_to_python_args
def _from_openapi_data(cls, status, *args, **kwargs): # noqa: E501
"""ShamanFileSpecWithStatusAllOf - a model defined in OpenAPI
Args:
status (ShamanFileStatus):
Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
self = super(OpenApiModel, cls).__new__(cls)
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.status = status
for var_name, var_value in kwargs.items():
if var_name not in self.attribute_map and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self.additional_properties_type is None:
# discard variable.
continue
setattr(self, var_name, var_value)
return self
required_properties = set([
'_data_store',
'_check_type',
'_spec_property_naming',
'_path_to_item',
'_configuration',
'_visited_composed_classes',
])
@convert_js_args_to_python_args
def __init__(self, status, *args, **kwargs): # noqa: E501
"""ShamanFileSpecWithStatusAllOf - a model defined in OpenAPI
Args:
status (ShamanFileStatus):
Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.status = status
for var_name, var_value in kwargs.items():
if var_name not in self.attribute_map and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self.additional_properties_type is None:
# discard variable.
continue
setattr(self, var_name, var_value)
if var_name in self.read_only_vars:
raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
f"class with read only attributes.")

View File

@ -0,0 +1,283 @@
"""
Flamenco manager
Render Farm manager API # noqa: E501
The version of the OpenAPI document: 1.0.0
Generated by: https://openapi-generator.tech
"""
import re # noqa: F401
import sys # noqa: F401
from flamenco.manager.model_utils import ( # noqa: F401
ApiTypeError,
ModelComposed,
ModelNormal,
ModelSimple,
cached_property,
change_keys_js_to_python,
convert_js_args_to_python_args,
date,
datetime,
file_type,
none_type,
validate_get_composed_info,
OpenApiModel
)
from flamenco.manager.exceptions import ApiAttributeError
class ShamanFileStatus(ModelSimple):
"""NOTE: This class is auto generated by OpenAPI Generator.
Ref: https://openapi-generator.tech
Do not edit the class manually.
Attributes:
allowed_values (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
with a capitalized key describing the allowed value and an allowed
value. These dicts store the allowed enum values.
validations (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
that stores validations for max_length, min_length, max_items,
min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum,
inclusive_minimum, and regex.
additional_properties_type (tuple): A tuple of classes accepted
as additional properties values.
"""
allowed_values = {
('value',): {
'UNKNOWN': "unknown",
'UPLOADING': "uploading",
'STORED': "stored",
},
}
validations = {
}
additional_properties_type = None
_nullable = False
@cached_property
def openapi_types():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
Returns
openapi_types (dict): The key is attribute name
and the value is attribute type.
"""
return {
'value': (str,),
}
@cached_property
def discriminator():
return None
attribute_map = {}
read_only_vars = set()
_composed_schemas = None
required_properties = set([
'_data_store',
'_check_type',
'_spec_property_naming',
'_path_to_item',
'_configuration',
'_visited_composed_classes',
])
@convert_js_args_to_python_args
def __init__(self, *args, **kwargs):
"""ShamanFileStatus - a model defined in OpenAPI
Note that value can be passed either in args or in kwargs, but not in both.
Args:
args[0] (str):, must be one of ["unknown", "uploading", "stored", ] # noqa: E501
Keyword Args:
value (str):, must be one of ["unknown", "uploading", "stored", ] # noqa: E501
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
# required up here when default value is not given
_path_to_item = kwargs.pop('_path_to_item', ())
if 'value' in kwargs:
value = kwargs.pop('value')
elif args:
args = list(args)
value = args.pop(0)
else:
raise ApiTypeError(
"value is required, but not passed in args or kwargs and doesn't have default",
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value
if kwargs:
raise ApiTypeError(
"Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
kwargs,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
@classmethod
@convert_js_args_to_python_args
def _from_openapi_data(cls, *args, **kwargs):
"""ShamanFileStatus - a model defined in OpenAPI
Note that value can be passed either in args or in kwargs, but not in both.
Args:
args[0] (str):, must be one of ["unknown", "uploading", "stored", ] # noqa: E501
Keyword Args:
value (str):, must be one of ["unknown", "uploading", "stored", ] # noqa: E501
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
# required up here when default value is not given
_path_to_item = kwargs.pop('_path_to_item', ())
self = super(OpenApiModel, cls).__new__(cls)
if 'value' in kwargs:
value = kwargs.pop('value')
elif args:
args = list(args)
value = args.pop(0)
else:
raise ApiTypeError(
"value is required, but not passed in args or kwargs and doesn't have default",
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value
if kwargs:
raise ApiTypeError(
"Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
kwargs,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
return self

View File

@ -0,0 +1,267 @@
"""
Flamenco manager
Render Farm manager API # noqa: E501
The version of the OpenAPI document: 1.0.0
Generated by: https://openapi-generator.tech
"""
import re # noqa: F401
import sys # noqa: F401
from flamenco.manager.model_utils import ( # noqa: F401
ApiTypeError,
ModelComposed,
ModelNormal,
ModelSimple,
cached_property,
change_keys_js_to_python,
convert_js_args_to_python_args,
date,
datetime,
file_type,
none_type,
validate_get_composed_info,
OpenApiModel
)
from flamenco.manager.exceptions import ApiAttributeError
def lazy_import():
from flamenco.manager.model.shaman_file_spec import ShamanFileSpec
globals()['ShamanFileSpec'] = ShamanFileSpec
class ShamanRequirementsRequest(ModelNormal):
"""NOTE: This class is auto generated by OpenAPI Generator.
Ref: https://openapi-generator.tech
Do not edit the class manually.
Attributes:
allowed_values (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
with a capitalized key describing the allowed value and an allowed
value. These dicts store the allowed enum values.
attribute_map (dict): The key is attribute name
and the value is json key in definition.
discriminator_value_class_map (dict): A dict to go from the discriminator
variable value to the discriminator class name.
validations (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
that stores validations for max_length, min_length, max_items,
min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum,
inclusive_minimum, and regex.
additional_properties_type (tuple): A tuple of classes accepted
as additional properties values.
"""
allowed_values = {
}
validations = {
}
@cached_property
def additional_properties_type():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
"""
lazy_import()
return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501
_nullable = False
@cached_property
def openapi_types():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
Returns
openapi_types (dict): The key is attribute name
and the value is attribute type.
"""
lazy_import()
return {
'files': ([ShamanFileSpec],), # noqa: E501
}
@cached_property
def discriminator():
return None
attribute_map = {
'files': 'files', # noqa: E501
}
read_only_vars = {
}
_composed_schemas = {}
@classmethod
@convert_js_args_to_python_args
def _from_openapi_data(cls, files, *args, **kwargs): # noqa: E501
"""ShamanRequirementsRequest - a model defined in OpenAPI
Args:
files ([ShamanFileSpec]):
Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
self = super(OpenApiModel, cls).__new__(cls)
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.files = files
for var_name, var_value in kwargs.items():
if var_name not in self.attribute_map and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self.additional_properties_type is None:
# discard variable.
continue
setattr(self, var_name, var_value)
return self
required_properties = set([
'_data_store',
'_check_type',
'_spec_property_naming',
'_path_to_item',
'_configuration',
'_visited_composed_classes',
])
@convert_js_args_to_python_args
def __init__(self, files, *args, **kwargs): # noqa: E501
"""ShamanRequirementsRequest - a model defined in OpenAPI
Args:
files ([ShamanFileSpec]):
Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.files = files
for var_name, var_value in kwargs.items():
if var_name not in self.attribute_map and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self.additional_properties_type is None:
# discard variable.
continue
setattr(self, var_name, var_value)
if var_name in self.read_only_vars:
raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
f"class with read only attributes.")

View File

@ -0,0 +1,267 @@
"""
Flamenco manager
Render Farm manager API # noqa: E501
The version of the OpenAPI document: 1.0.0
Generated by: https://openapi-generator.tech
"""
import re # noqa: F401
import sys # noqa: F401
from flamenco.manager.model_utils import ( # noqa: F401
ApiTypeError,
ModelComposed,
ModelNormal,
ModelSimple,
cached_property,
change_keys_js_to_python,
convert_js_args_to_python_args,
date,
datetime,
file_type,
none_type,
validate_get_composed_info,
OpenApiModel
)
from flamenco.manager.exceptions import ApiAttributeError
def lazy_import():
from flamenco.manager.model.shaman_file_spec_with_status import ShamanFileSpecWithStatus
globals()['ShamanFileSpecWithStatus'] = ShamanFileSpecWithStatus
class ShamanRequirementsResponse(ModelNormal):
"""NOTE: This class is auto generated by OpenAPI Generator.
Ref: https://openapi-generator.tech
Do not edit the class manually.
Attributes:
allowed_values (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
with a capitalized key describing the allowed value and an allowed
value. These dicts store the allowed enum values.
attribute_map (dict): The key is attribute name
and the value is json key in definition.
discriminator_value_class_map (dict): A dict to go from the discriminator
variable value to the discriminator class name.
validations (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
that stores validations for max_length, min_length, max_items,
min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum,
inclusive_minimum, and regex.
additional_properties_type (tuple): A tuple of classes accepted
as additional properties values.
"""
allowed_values = {
}
validations = {
}
@cached_property
def additional_properties_type():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
"""
lazy_import()
return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501
_nullable = False
@cached_property
def openapi_types():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
Returns
openapi_types (dict): The key is attribute name
and the value is attribute type.
"""
lazy_import()
return {
'files': ([ShamanFileSpecWithStatus],), # noqa: E501
}
@cached_property
def discriminator():
return None
attribute_map = {
'files': 'files', # noqa: E501
}
read_only_vars = {
}
_composed_schemas = {}
@classmethod
@convert_js_args_to_python_args
def _from_openapi_data(cls, files, *args, **kwargs): # noqa: E501
"""ShamanRequirementsResponse - a model defined in OpenAPI
Args:
files ([ShamanFileSpecWithStatus]):
Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
self = super(OpenApiModel, cls).__new__(cls)
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.files = files
for var_name, var_value in kwargs.items():
if var_name not in self.attribute_map and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self.additional_properties_type is None:
# discard variable.
continue
setattr(self, var_name, var_value)
return self
required_properties = set([
'_data_store',
'_check_type',
'_spec_property_naming',
'_path_to_item',
'_configuration',
'_visited_composed_classes',
])
@convert_js_args_to_python_args
def __init__(self, files, *args, **kwargs): # noqa: E501
"""ShamanRequirementsResponse - a model defined in OpenAPI
Args:
files ([ShamanFileSpecWithStatus]):
Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.files = files
for var_name, var_value in kwargs.items():
if var_name not in self.attribute_map and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self.additional_properties_type is None:
# discard variable.
continue
setattr(self, var_name, var_value)
if var_name in self.read_only_vars:
raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
f"class with read only attributes.")

View File

@ -0,0 +1,267 @@
"""
Flamenco manager
Render Farm manager API # noqa: E501
The version of the OpenAPI document: 1.0.0
Generated by: https://openapi-generator.tech
"""
import re # noqa: F401
import sys # noqa: F401
from flamenco.manager.model_utils import ( # noqa: F401
ApiTypeError,
ModelComposed,
ModelNormal,
ModelSimple,
cached_property,
change_keys_js_to_python,
convert_js_args_to_python_args,
date,
datetime,
file_type,
none_type,
validate_get_composed_info,
OpenApiModel
)
from flamenco.manager.exceptions import ApiAttributeError
def lazy_import():
from flamenco.manager.model.shaman_file_status import ShamanFileStatus
globals()['ShamanFileStatus'] = ShamanFileStatus
class ShamanSingleFileStatus(ModelNormal):
"""NOTE: This class is auto generated by OpenAPI Generator.
Ref: https://openapi-generator.tech
Do not edit the class manually.
Attributes:
allowed_values (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
with a capitalized key describing the allowed value and an allowed
value. These dicts store the allowed enum values.
attribute_map (dict): The key is attribute name
and the value is json key in definition.
discriminator_value_class_map (dict): A dict to go from the discriminator
variable value to the discriminator class name.
validations (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
that stores validations for max_length, min_length, max_items,
min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum,
inclusive_minimum, and regex.
additional_properties_type (tuple): A tuple of classes accepted
as additional properties values.
"""
allowed_values = {
}
validations = {
}
@cached_property
def additional_properties_type():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
"""
lazy_import()
return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501
_nullable = False
@cached_property
def openapi_types():
"""
This must be a method because a model may have properties that are
of type self, this must run after the class is loaded
Returns
openapi_types (dict): The key is attribute name
and the value is attribute type.
"""
lazy_import()
return {
'status': (ShamanFileStatus,), # noqa: E501
}
@cached_property
def discriminator():
return None
attribute_map = {
'status': 'status', # noqa: E501
}
read_only_vars = {
}
_composed_schemas = {}
@classmethod
@convert_js_args_to_python_args
def _from_openapi_data(cls, status, *args, **kwargs): # noqa: E501
"""ShamanSingleFileStatus - a model defined in OpenAPI
Args:
status (ShamanFileStatus):
Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
self = super(OpenApiModel, cls).__new__(cls)
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.status = status
for var_name, var_value in kwargs.items():
if var_name not in self.attribute_map and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self.additional_properties_type is None:
# discard variable.
continue
setattr(self, var_name, var_value)
return self
required_properties = set([
'_data_store',
'_check_type',
'_spec_property_naming',
'_path_to_item',
'_configuration',
'_visited_composed_classes',
])
@convert_js_args_to_python_args
def __init__(self, status, *args, **kwargs): # noqa: E501
"""ShamanSingleFileStatus - a model defined in OpenAPI
Args:
status (ShamanFileStatus):
Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
"""
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.status = status
for var_name, var_value in kwargs.items():
if var_name not in self.attribute_map and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self.additional_properties_type is None:
# discard variable.
continue
setattr(self, var_name, var_value)
if var_name in self.read_only_vars:
raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
f"class with read only attributes.")

View File

@ -25,6 +25,14 @@ from flamenco.manager.model.job_settings import JobSettings
from flamenco.manager.model.job_status import JobStatus
from flamenco.manager.model.registered_worker import RegisteredWorker
from flamenco.manager.model.security_error import SecurityError
from flamenco.manager.model.shaman_checkout import ShamanCheckout
from flamenco.manager.model.shaman_file_spec import ShamanFileSpec
from flamenco.manager.model.shaman_file_spec_with_status import ShamanFileSpecWithStatus
from flamenco.manager.model.shaman_file_spec_with_status_all_of import ShamanFileSpecWithStatusAllOf
from flamenco.manager.model.shaman_file_status import ShamanFileStatus
from flamenco.manager.model.shaman_requirements_request import ShamanRequirementsRequest
from flamenco.manager.model.shaman_requirements_response import ShamanRequirementsResponse
from flamenco.manager.model.shaman_single_file_status import ShamanSingleFileStatus
from flamenco.manager.model.submitted_job import SubmittedJob
from flamenco.manager.model.task_status import TaskStatus
from flamenco.manager.model.task_update import TaskUpdate

View File

@ -4,7 +4,7 @@ Render Farm manager API
The `flamenco.manager` package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.0.0
- Package version: 7bfde1df-dirty
- Package version: 63793f42-dirty
- Build package: org.openapitools.codegen.languages.PythonClientCodegen
For more information, please visit [https://flamenco.io/](https://flamenco.io/)
@ -68,6 +68,10 @@ Class | Method | HTTP request | Description
*JobsApi* | [**get_job_types**](flamenco/manager/docs/JobsApi.md#get_job_types) | **GET** /api/jobs/types | Get list of job types and their parameters.
*JobsApi* | [**submit_job**](flamenco/manager/docs/JobsApi.md#submit_job) | **POST** /api/jobs | Submit a new job for Flamenco Manager to execute.
*MetaApi* | [**get_version**](flamenco/manager/docs/MetaApi.md#get_version) | **GET** /api/version | Get the Flamenco version of this Manager
*ShamanApi* | [**shaman_checkout**](flamenco/manager/docs/ShamanApi.md#shaman_checkout) | **POST** /shaman/checkout/create | Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.
*ShamanApi* | [**shaman_checkout_requirements**](flamenco/manager/docs/ShamanApi.md#shaman_checkout_requirements) | **POST** /shaman/checkout/requirements | Checks a Shaman Requirements file, and reports which files are unknown.
*ShamanApi* | [**shaman_file_store**](flamenco/manager/docs/ShamanApi.md#shaman_file_store) | **POST** /shaman/files/{checksum}/{filesize} | Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file&#39;s contents should be sent in the request body.
*ShamanApi* | [**shaman_file_store_check**](flamenco/manager/docs/ShamanApi.md#shaman_file_store_check) | **OPTIONS** /shaman/files/{checksum}/{filesize} | Check the status of a file on the Shaman server.
*WorkerApi* | [**register_worker**](flamenco/manager/docs/WorkerApi.md#register_worker) | **POST** /api/worker/register-worker | Register a new worker
*WorkerApi* | [**schedule_task**](flamenco/manager/docs/WorkerApi.md#schedule_task) | **POST** /api/worker/task | Obtain a new task to execute
*WorkerApi* | [**sign_off**](flamenco/manager/docs/WorkerApi.md#sign_off) | **POST** /api/worker/sign-off | Mark the worker as offline
@ -95,6 +99,14 @@ Class | Method | HTTP request | Description
- [JobStatus](flamenco/manager/docs/JobStatus.md)
- [RegisteredWorker](flamenco/manager/docs/RegisteredWorker.md)
- [SecurityError](flamenco/manager/docs/SecurityError.md)
- [ShamanCheckout](flamenco/manager/docs/ShamanCheckout.md)
- [ShamanFileSpec](flamenco/manager/docs/ShamanFileSpec.md)
- [ShamanFileSpecWithStatus](flamenco/manager/docs/ShamanFileSpecWithStatus.md)
- [ShamanFileSpecWithStatusAllOf](flamenco/manager/docs/ShamanFileSpecWithStatusAllOf.md)
- [ShamanFileStatus](flamenco/manager/docs/ShamanFileStatus.md)
- [ShamanRequirementsRequest](flamenco/manager/docs/ShamanRequirementsRequest.md)
- [ShamanRequirementsResponse](flamenco/manager/docs/ShamanRequirementsResponse.md)
- [ShamanSingleFileStatus](flamenco/manager/docs/ShamanSingleFileStatus.md)
- [SubmittedJob](flamenco/manager/docs/SubmittedJob.md)
- [TaskStatus](flamenco/manager/docs/TaskStatus.md)
- [TaskUpdate](flamenco/manager/docs/TaskUpdate.md)

View File

@ -315,7 +315,7 @@ class FLAMENCO3_OT_explore_file_path(bpy.types.Operator):
bl_label = "Open in file explorer"
bl_description = __doc__.rstrip(".")
path: bpy.props.StringProperty(
path: bpy.props.StringProperty( # type: ignore
name="Path", description="Path to explore", subtype="DIR_PATH"
)
@ -334,22 +334,22 @@ class FLAMENCO3_OT_explore_file_path(bpy.types.Operator):
{"ERROR"}, "Unable to open %s or any of its parents." % self.path
)
return {"CANCELLED"}
to_open = str(to_open)
if platform.system() == "Windows":
import os
os.startfile(to_open)
# Ignore the mypy error here, as os.startfile() only exists on Windows.
os.startfile(str(to_open)) # type: ignore
elif platform.system() == "Darwin":
import subprocess
subprocess.Popen(["open", to_open])
subprocess.Popen(["open", str(to_open)])
else:
import subprocess
subprocess.Popen(["xdg-open", to_open])
subprocess.Popen(["xdg-open", str(to_open)])
return {"FINISHED"}

View File

@ -80,17 +80,9 @@ func (f *Flamenco) ShamanFileStoreCheck(e echo.Context, checksum string, filesiz
logger.Debug().Msg("shaman: checking file")
status := f.shaman.FileStoreCheck(e.Request().Context(), checksum, int64(filesize))
// TODO: actually switch over the actual statuses, see the TODO in the Shaman interface.
switch status {
case api.ShamanFileStatusStored:
return e.String(http.StatusOK, "")
case api.ShamanFileStatusUploading:
return e.String(420 /* Enhance Your Calm */, "")
case api.ShamanFileStatusUnknown:
return e.String(http.StatusNotFound, "")
}
return sendAPIError(e, http.StatusInternalServerError, "unexpected file status")
return e.JSON(http.StatusOK, api.ShamanSingleFileStatus{
Status: status,
})
}
// Store a new file on the Shaman server. Note that the Shaman server can
@ -127,23 +119,22 @@ func (f *Flamenco) ShamanFileStore(e echo.Context, checksum string, filesize int
canDefer, origFilename,
)
if err != nil {
if err == fileserver.ErrFileAlreadyExists {
switch err {
case fileserver.ErrFileAlreadyExists:
return e.String(http.StatusAlreadyReported, "")
case fileserver.ErrFileShouldDefer:
return e.String(http.StatusTooEarly, "")
}
logger.Warn().Err(err).Msg("shaman: checking stored file")
if sizeErr, ok := err.(fileserver.ErrFileSizeMismatch); ok {
return sendAPIError(e, http.StatusExpectationFailed,
"size mismatch, expected %d bytes, received %d bytes",
sizeErr.DeclaredSize, sizeErr.ActualSize)
}
if checksumErr, ok := err.(fileserver.ErrFileChecksumMismatch); ok {
return sendAPIError(e, http.StatusExpectationFailed,
"checksum mismatch, expected %d bytes, received %d bytes",
checksumErr.DeclaredChecksum, checksumErr.ActualChecksum)
}
switch v := err.(type) {
case fileserver.ErrFileSizeMismatch, fileserver.ErrFileChecksumMismatch:
return sendAPIError(e, http.StatusExpectationFailed, v.Error())
default:
return sendAPIError(e, http.StatusInternalServerError, "unexpected error: %v", err)
}
}
return nil
}

View File

@ -344,13 +344,7 @@ paths:
description: Normal response.
content:
application/json:
schema:
type: object
description: Status of a file in the Shaman storage.
properties:
"status": {$ref: "#/components/schemas/ShamanFileStatus"}
required: [status]
schema: {$ref: "#/components/schemas/ShamanSingleFileStatus"}
default:
description: unexpected error
content:
@ -364,6 +358,8 @@ paths:
Store a new file on the Shaman server. Note that the Shaman server can
forcibly close the HTTP connection when another client finishes uploading
the exact same file, to prevent double uploads.
The file's contents should be sent in the request body.
tags: [shaman]
parameters:
- name: checksum
@ -393,23 +389,28 @@ paths:
The original filename. If sent along with the request, it will be
included in the server logs, which can aid in debugging.
requestBody:
description: The file's contents.
description: Contents of the file
required: true
content:
application/octet-stream:
example: Just the contents of any file.
schema:
type: string
format: binary
responses:
"204":
description: Checkout was created succesfully.
description: The file was accepted.
"208":
description: >
The file has already been uploaded. Note that this can also be sent
when this file is currently in the process of being uploaded, and
`X-Shaman-Can-Defer-Upload: true` was sent in the request.
"409":
description: Checkout already exists.
description: The file was already known to the server.
"417":
description: There was a mismatch between the request parameters and the actual file size or checksum of the uploaded file.
description: >
There was a mismatch between the request parameters and the actual
file size or checksum of the uploaded file.
"425":
description: >
Client should defer uploading this file. The file is currently in
the process of being uploaded by someone else, and
`X-Shaman-Can-Defer-Upload: true` was sent in the request.
default:
description: unexpected error
content:
@ -721,7 +722,8 @@ components:
properties:
"sha": {type: string, description: "SHA256 checksum of the file"}
"size": {type: integer, description: "File size in bytes"}
required: [sha, size]
"path": {type: string, description: "Location of the file in the checkout"}
required: [sha, size, path]
ShamanFileSpecWithStatus:
allOf:
@ -730,20 +732,13 @@ components:
"status": {$ref: "#/components/schemas/ShamanFileStatus"}
required: [status]
ShamanFileSpecWithPath:
allOf:
- $ref: '#/components/schemas/ShamanFileSpec'
- properties:
"path": {type: string, description: Location of the file in the checkout}
required: [path]
ShamanCheckout:
type: object
description: Set of files with their SHA256 checksum, size in bytes, and desired location in the checkout directory.
properties:
"files":
type: array
items: {$ref: "#/components/schemas/ShamanFileSpecWithPath"}
items: {$ref: "#/components/schemas/ShamanFileSpec"}
"checkoutPath":
type: string
description: >
@ -765,6 +760,13 @@ components:
type: string
enum: [unknown, uploading, stored]
ShamanSingleFileStatus:
type: object
description: Status of a file in the Shaman storage.
properties:
"status": {$ref: "#/components/schemas/ShamanFileStatus"}
required: [status]
securitySchemes:
worker_auth:
description: Username is the worker ID, password is the secret given at worker registration.

View File

@ -1377,9 +1377,7 @@ func (r ShamanCheckoutRequirementsResponse) StatusCode() int {
type ShamanFileStoreCheckResponse struct {
Body []byte
HTTPResponse *http.Response
JSON200 *struct {
Status ShamanFileStatus `json:"status"`
}
JSON200 *ShamanSingleFileStatus
JSONDefault *Error
}
@ -2028,9 +2026,7 @@ func ParseShamanFileStoreCheckResponse(rsp *http.Response) (*ShamanFileStoreChec
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest struct {
Status ShamanFileStatus `json:"status"`
}
var dest ShamanSingleFileStatus
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}

View File

@ -56,6 +56,7 @@ type ServerInterface interface {
// (OPTIONS /shaman/files/{checksum}/{filesize})
ShamanFileStoreCheck(ctx echo.Context, checksum string, filesize int) error
// Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads.
// The file's contents should be sent in the request body.
// (POST /shaman/files/{checksum}/{filesize})
ShamanFileStore(ctx echo.Context, checksum string, filesize int, params ShamanFileStoreParams) error
}

View File

@ -18,92 +18,92 @@ import (
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
"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",
"AP//RfyB4BlUAAA=",
"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",
"3by5+Z8AAAD//+uKmtshVAAA",
}
// GetSwagger returns the content of the embedded swagger specification file

View File

@ -246,11 +246,14 @@ type SecurityError struct {
type ShamanCheckout struct {
// Path where the Manager should create this checkout, It is relative to the Shaman checkout path as configured on the Manager. In older versions of the Shaman this was just the "checkout ID", but in this version it can be a path like `project-slug/scene-name/unique-ID`.
CheckoutPath string `json:"checkoutPath"`
Files []ShamanFileSpecWithPath `json:"files"`
Files []ShamanFileSpec `json:"files"`
}
// Specification of a file in the Shaman storage.
type ShamanFileSpec struct {
// Location of the file in the checkout
Path string `json:"path"`
// SHA256 checksum of the file
Sha string `json:"sha"`
@ -258,15 +261,6 @@ type ShamanFileSpec struct {
Size int `json:"size"`
}
// ShamanFileSpecWithPath defines model for ShamanFileSpecWithPath.
type ShamanFileSpecWithPath struct {
// Embedded struct due to allOf(#/components/schemas/ShamanFileSpec)
ShamanFileSpec `yaml:",inline"`
// Embedded fields due to inline allOf schema
// Location of the file in the checkout
Path string `json:"path"`
}
// ShamanFileSpecWithStatus defines model for ShamanFileSpecWithStatus.
type ShamanFileSpecWithStatus struct {
// Embedded struct due to allOf(#/components/schemas/ShamanFileSpec)
@ -288,6 +282,11 @@ type ShamanRequirementsResponse struct {
Files []ShamanFileSpecWithStatus `json:"files"`
}
// Status of a file in the Shaman storage.
type ShamanSingleFileStatus struct {
Status ShamanFileStatus `json:"status"`
}
// Job definition submitted to Flamenco.
type SubmittedJob struct {
// Arbitrary metadata strings. More complex structures can be modeled by using `a.b.c` notation for the key.

View File

@ -16,7 +16,10 @@ func (m *Manager) ReportRequirements(ctx context.Context, requirements api.Shama
logger := zerolog.Ctx(ctx)
logger.Debug().Msg("user requested checkout requirements")
missing := api.ShamanRequirementsResponse{}
missing := api.ShamanRequirementsResponse{
Files: []api.ShamanFileSpecWithStatus{},
}
alreadyRequested := map[string]bool{}
for _, fileSpec := range requirements.Files {
fileKey := fmt.Sprintf("%s/%d", fileSpec.Sha, fileSpec.Size)
@ -25,7 +28,7 @@ func (m *Manager) ReportRequirements(ctx context.Context, requirements api.Shama
continue
}
path, status := m.fileStore.ResolveFile(fileSpec.Sha, int64(fileSpec.Size), filestore.ResolveEverything)
storePath, status := m.fileStore.ResolveFile(fileSpec.Sha, int64(fileSpec.Size), filestore.ResolveEverything)
var apiStatus api.ShamanFileStatus
switch status {
@ -38,13 +41,13 @@ func (m *Manager) ReportRequirements(ctx context.Context, requirements api.Shama
case filestore.StatusStored:
// We expect this file to be sent soon, though, so we need to
// 'touch' it to make sure it won't be GC'd in the mean time.
go touchFile(path)
go touchFile(storePath)
// Only send a response when the caller needs to do something.
continue
default:
logger.Error().
Str("path", path).
Str("path", fileSpec.Path).
Str("status", status.String()).
Str("checksum", fileSpec.Sha).
Int("filesize", fileSpec.Size).

View File

@ -34,13 +34,13 @@ func TestReportRequirements(t *testing.T) {
manager, cleanup := createTestManager()
defer cleanup()
spec1 := api.ShamanFileSpec{Sha: "63b72c63b9424fd13b9370fb60069080c3a15717cf3ad442635b187c6a895079", Size: 127, Path: "file1.txt"}
spec2 := api.ShamanFileSpec{Sha: "9f1470441beb98dbb66e3339e7da697d9c2312999a6a5610c461cbf55040e210", Size: 795, Path: "file2.txt"}
spec3 := api.ShamanFileSpec{Sha: "59c6bd72af62aa860343adcafd46e3998934a9db2997ce08514b4361f099fa58", Size: 1134, Path: "file3.txt"}
spec4 := api.ShamanFileSpec{Sha: "59c6bd72af62aa860343adcafd46e3998934a9db2997ce08514b4361f099fa58", Size: 1134, Path: "file4.txt"} // duplicate of the above
required := api.ShamanRequirementsRequest{
Files: []api.ShamanFileSpec{
{"63b72c63b9424fd13b9370fb60069080c3a15717cf3ad442635b187c6a895079", 127},
{"9f1470441beb98dbb66e3339e7da697d9c2312999a6a5610c461cbf55040e210", 795},
{"59c6bd72af62aa860343adcafd46e3998934a9db2997ce08514b4361f099fa58", 1134},
{"59c6bd72af62aa860343adcafd46e3998934a9db2997ce08514b4361f099fa58", 1134}, // duplicate of the above
},
Files: []api.ShamanFileSpec{spec1, spec2, spec3, spec4},
}
response, err := manager.ReportRequirements(context.Background(), required)
@ -49,9 +49,9 @@ func TestReportRequirements(t *testing.T) {
// We should not be required to upload the same file twice, so the duplicate
// should not be in the response.
assert.Equal(t, []api.ShamanFileSpecWithStatus{
{api.ShamanFileSpec{"63b72c63b9424fd13b9370fb60069080c3a15717cf3ad442635b187c6a895079", 127}, api.ShamanFileStatusUnknown},
{api.ShamanFileSpec{"9f1470441beb98dbb66e3339e7da697d9c2312999a6a5610c461cbf55040e210", 795}, api.ShamanFileStatusUnknown},
{api.ShamanFileSpec{"59c6bd72af62aa860343adcafd46e3998934a9db2997ce08514b4361f099fa58", 1134}, api.ShamanFileStatusUnknown},
{ShamanFileSpec: spec1, Status: api.ShamanFileStatusUnknown},
{ShamanFileSpec: spec2, Status: api.ShamanFileStatusUnknown},
{ShamanFileSpec: spec3, Status: api.ShamanFileStatusUnknown},
}, response.Files)
}

View File

@ -38,6 +38,11 @@ import (
// uploaded the same file at the same time.
var ErrFileAlreadyExists = errors.New("uploaded file already exists")
// ErrFileShouldDefer indicates that the file is currently being uploaded
// already. This will only be returned when the caller indicates the client is
// capable of deferring the upload.
var ErrFileShouldDefer = errors.New("file is being uploaded by someone else")
type ErrFileSizeMismatch struct {
DeclaredSize int64
ActualSize int64
@ -59,14 +64,15 @@ func (e ErrFileChecksumMismatch) Error() string {
// ReceiveFile streams a file from a HTTP request to disk.
func (fs *FileServer) ReceiveFile(
ctx context.Context, bodyReader io.ReadCloser,
checksum string, filesize int64, canDefer bool,
checksum string, filesize int64,
canDefer bool, originalFilename string,
) error {
logger := *zerolog.Ctx(ctx)
defer bodyReader.Close()
localPath, status := fs.fileStore.ResolveFile(checksum, filesize, filestore.ResolveEverything)
storePath, status := fs.fileStore.ResolveFile(checksum, filesize, filestore.ResolveEverything)
logger = logger.With().
Str("path", localPath).
Str("path", originalFilename).
Str("checksum", checksum).
Int64("filesize", filesize).
Str("status", status.String()).
@ -74,12 +80,12 @@ func (fs *FileServer) ReceiveFile(
switch status {
case filestore.StatusStored:
logger.Info().Msg("shaman: uploaded file already exists")
logger.Info().Str("storePath", storePath).Msg("shaman: uploaded file already exists")
return ErrFileAlreadyExists
case filestore.StatusUploading:
if canDefer {
logger.Info().Msg("shaman: someone is uploading this file and client can defer")
return ErrFileAlreadyExists
logger.Info().Str("storePath", storePath).Msg("shaman: someone is uploading this file and client can defer")
return ErrFileShouldDefer
}
}

View File

@ -49,7 +49,7 @@ func TestStoreFile(t *testing.T) {
testWithChecksum := func(checksum string, reportSize int64) error {
buffer := io.NopCloser(bytes.NewBuffer(payload))
return server.ReceiveFile(context.Background(), buffer, checksum, reportSize, false)
return server.ReceiveFile(context.Background(), buffer, checksum, reportSize, false, "testfile.txt")
}
var err error

View File

@ -154,8 +154,10 @@ func (s *Store) MoveToStored(checksum string, filesize int64, uploadedFilePath s
func (s *Store) removeFile(filePath string) error {
err := os.Remove(filePath)
if err != nil {
if !os.IsNotExist(err) {
log.Debug().Err(err).Msg("shaman: unable to delete file; ignoring")
}
}
// Clean up directory structure, but ignore any errors (dirs may not be empty)
directory := path.Dir(filePath)

View File

@ -148,7 +148,7 @@ func (s *Server) FileStoreCheck(ctx context.Context, checksum string, filesize i
// early when another client finishes uploading the exact same file, to prevent
// double uploads.
func (s *Server) FileStore(ctx context.Context, file io.ReadCloser, checksum string, filesize int64, canDefer bool, originalFilename string) error {
err := s.fileServer.ReceiveFile(ctx, file, checksum, filesize, canDefer)
err := s.fileServer.ReceiveFile(ctx, file, checksum, filesize, canDefer, originalFilename)
// TODO: Maybe translate this error into something that can be understood by
// the caller without relying on types declared in the `fileserver` package?
return err