Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add downloadUrl for derives #1290

Draft
wants to merge 11 commits into
base: develop
Choose a base branch
from
2 changes: 0 additions & 2 deletions src/viur/core/bones/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -985,14 +985,12 @@ def unserialize_compute(self, skel: "SkeletonInstance", name: str) -> bool:
case ComputeMethod.Lifetime:
now = utils.utcNow()
from viur.core.skeleton import RefSkel # noqa: E402 # import works only here because circular imports

if issubclass(skel.skeletonCls, RefSkel): # we have a ref skel we must load the complete Entity
db_obj = db.Get(skel["key"])
last_update = db_obj.get(f"_viur_compute_{name}_")
else:
last_update = skel.dbEntity.get(f"_viur_compute_{name}_")
skel.accessedValues[f"_viur_compute_{name}_"] = last_update or now

if not last_update or last_update + self.compute.interval.lifetime <= now:
# if so, recompute and refresh updated value
skel.accessedValues[name] = value = self._compute(skel, name)
Expand Down
2 changes: 2 additions & 0 deletions src/viur/core/bones/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ def __init__(
"height",
"derived",
"public",
"download_url_json",
"download_url_html"
),
public: bool = False,
**kwargs
Expand Down
18 changes: 18 additions & 0 deletions src/viur/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,24 @@ class Conf(ConfigType):
file_thumbnailer_url: t.Optional[str] = None
# TODO: """docstring"""

file_generate_download_url_for_derives: bool | t.Iterable[str] = False
"""
If True, for all derives, a download URL is created as well.

When an iterable of str is provided, for all entries that match a pattern,
a download URL will be created.

Example:
```py
class Test(Skeleton):
image_bar = FileBone(derive=conf["derives"])
image_foo = FileBone(derive=conf["derives"])
logo = FileBone(derive=conf["derives"])

conf.file_generate_download_url_for_derives = ["test.image*"]
```
"""

main_app: "Module" = None
"""Reference to our pre-build Application-Instance"""

Expand Down
48 changes: 31 additions & 17 deletions src/viur/core/modules/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
from google.cloud import storage
from google.oauth2.service_account import Credentials as ServiceAccountCredentials
from viur.core import conf, current, db, errors, utils
from viur.core.bones import BaseBone, BooleanBone, KeyBone, NumericBone, StringBone
from viur.core.bones import BaseBone, BooleanBone, KeyBone, NumericBone, StringBone, UriBone
from viur.core.bones import Compute, ComputeMethod, ComputeInterval
from viur.core.decorators import *
from viur.core.i18n import LanguageWrapper
from viur.core.prototypes.tree import SkelType, Tree, TreeSkel
Expand Down Expand Up @@ -281,20 +282,6 @@ def make_request():
return reslist


class DownloadUrlBone(BaseBone):
"""
This bone is used to inject a freshly signed download url into a FileSkel.
"""

def unserialize(self, skel, name):
if "dlkey" in skel.dbEntity and "name" in skel.dbEntity:
skel.accessedValues[name] = File.create_download_url(
skel["dlkey"], skel["name"], expires=conf.render_json_download_url_expiration
)
return True

return False


class FileLeafSkel(TreeSkel):
"""
Expand Down Expand Up @@ -350,10 +337,37 @@ class FileLeafSkel(TreeSkel):
searchable=True,
)

downloadUrl = DownloadUrlBone(
descr="Download-URL",
download_url_json = UriBone(
descr="Download-URL for HTML",
readOnly=True,
visible=False,
compute=Compute(
fn=lambda skel: File.create_download_url(
dlkey=skel["dlkey"],
filename=skel["name"],
expires=conf.render_json_download_url_expiration
),
interval=ComputeInterval(
method=ComputeMethod.Lifetime,
lifetime=conf.render_json_download_url_expiration or datetime.timedelta(hours=1)
)
)
)
download_url_html = UriBone(
descr="Download-URL for HTML",
readOnly=True,
visible=False,
compute=Compute(
fn=lambda skel: File.create_download_url(
dlkey=skel["dlkey"],
filename=skel["name"],
expires=conf.render_html_download_url_expiration
),
interval=ComputeInterval(
method=ComputeMethod.Lifetime,
lifetime=conf.render_html_download_url_expiration or datetime.timedelta(hours=1)
)
)
)

derived = BaseBone(
Expand Down
41 changes: 36 additions & 5 deletions src/viur/core/render/json/default.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import fnmatch
import json
import typing as t
from enum import Enum

from viur.core.modules.file import File
from viur.core import bones, db, current
from viur.core.render.abstract import AbstractRenderer
from viur.core.skeleton import SkeletonInstance
Expand Down Expand Up @@ -88,7 +89,11 @@ def renderSingleBoneValue(self, value: t.Any,
if isinstance(bone, bones.RelationalBone):
if isinstance(value, dict):
return {
"dest": self.renderSkelValues(value["dest"], injectDownloadURL=isinstance(bone, bones.FileBone)),
"dest": self.renderSkelValues(
value["dest"],
bone_path=f"{skel.kindName}.{bone.name}",
injectDownloadURL=isinstance(bone, bones.FileBone)
),
"rel": (self.renderSkelValues(value["rel"], injectDownloadURL=isinstance(bone, bones.FileBone))
if value["rel"] else None),
}
Expand Down Expand Up @@ -122,7 +127,10 @@ def renderBoneValue(self, bone: bones.BaseBone, skel: SkeletonInstance, key: str
res = self.renderSingleBoneValue(boneVal, bone, skel, key)
return res

def renderSkelValues(self, skel: SkeletonInstance, injectDownloadURL: bool = False) -> t.Optional[dict]:
def renderSkelValues(self,
skel: SkeletonInstance,
bone_path: t.Optional[str] = None,
injectDownloadURL: bool = False) -> t.Optional[dict]:
"""
Prepares values of one :class:`viur.core.skeleton.Skeleton` or a list of skeletons for output.

Expand All @@ -140,15 +148,38 @@ def renderSkelValues(self, skel: SkeletonInstance, injectDownloadURL: bool = Fal

if (
injectDownloadURL
and (file := getattr(conf.main_app, "file", None))
and "dlkey" in skel
and "name" in skel
):
res["downloadUrl"] = file.create_download_url(
Copy link
Member

Choose a reason for hiding this comment

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

This is the opposite of #1252, I would keep it as it is.

res["downloadUrl"] = File.create_download_url(
skel["dlkey"],
skel["name"],
expires=conf.render_json_download_url_expiration
)

# generate the downloadUrl for derives
search_paths = []
if search_path := current.request.get().request.headers.get("X-VIUR-DERIVED-DOWNLOAD-URL"):
search_paths.extend(search_path.split(","))
if conf.file_generate_download_url_for_derives:

if isinstance(conf.file_generate_download_url_for_derives, t.Iterable):
search_paths.extend(conf.file_generate_download_url_for_derives)
else:
search_paths.append("*")

for search_path in search_paths:
if fnmatch.fnmatch(bone_path, search_path):
break
else:
return res

for derive_name in res.get("derived", {}).get("files", {}):
res["derived"]["files"][derive_name]["downloadUrl"] = File.create_download_url(
skel["dlkey"],
derive_name,
derived=True
)
return res

def renderEntry(self, skel: SkeletonInstance, actionName, params=None):
Expand Down
Loading