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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically convert image and html like assignments to uploads #1006

Merged
merged 6 commits into from
Nov 4, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 4 additions & 54 deletions src/neptune/new/metadata_containers/metadata_container.py
Expand Up @@ -14,14 +14,12 @@
# limitations under the License.
Copy link
Contributor

Choose a reason for hiding this comment

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

CHANGELOG?

#
import abc
import argparse
import atexit
import itertools
import threading
import time
import traceback
from contextlib import AbstractContextManager
from datetime import datetime
from functools import wraps
from typing import (
Any,
Expand All @@ -32,7 +30,6 @@
Union,
)

from neptune.common.deprecation import warn_once
from neptune.common.exceptions import UNIX_STYLES
from neptune.new.attributes import create_attribute_from_type
from neptune.new.attributes.attribute import Attribute
Expand Down Expand Up @@ -60,16 +57,7 @@
from neptune.new.internal.operation import DeleteAttribute
from neptune.new.internal.operation_processors.operation_processor import OperationProcessor
from neptune.new.internal.state import ContainerState
from neptune.new.internal.utils import (
is_bool,
is_dict_like,
is_float,
is_float_like,
is_int,
is_string,
is_string_like,
verify_type,
)
from neptune.new.internal.utils import verify_type
from neptune.new.internal.utils.logger import logger
from neptune.new.internal.utils.paths import parse_path
from neptune.new.internal.utils.runningmode import (
Expand All @@ -79,17 +67,8 @@
from neptune.new.internal.utils.uncaught_exception_handler import instance as uncaught_exception_handler
from neptune.new.internal.value_to_attribute_visitor import ValueToAttributeVisitor
from neptune.new.metadata_containers.metadata_containers_table import Table
from neptune.new.types import (
Boolean,
Integer,
)
from neptune.new.types.atoms.datetime import Datetime
from neptune.new.types.atoms.float import Float
from neptune.new.types.atoms.string import String
from neptune.new.types.mode import Mode
from neptune.new.types.namespace import Namespace
from neptune.new.types.value import Value
from neptune.new.types.value_copy import ValueCopy
from neptune.new.types.type_casting import cast_value


def ensure_not_stopped(fun):
Expand Down Expand Up @@ -254,39 +233,10 @@ def _print_structure_impl(self, struct: dict, indent: int) -> None:
def define(
self,
path: str,
value: Union[Value, int, float, str, datetime],
value: Any,
wait: bool = False,
) -> Attribute:
if isinstance(value, Value):
pass
elif isinstance(value, Handler):
value = ValueCopy(value)
elif isinstance(value, argparse.Namespace):
value = Namespace(vars(value))
elif is_bool(value):
value = Boolean(value)
elif is_int(value):
value = Integer(value)
elif is_float(value):
value = Float(value)
elif is_string(value):
value = String(value)
elif isinstance(value, datetime):
value = Datetime(value)
elif is_float_like(value):
value = Float(float(value))
elif is_dict_like(value):
value = Namespace(value)
elif is_string_like(value):
warn_once(
message="The object you're logging will be implicitly cast to a string."
" We'll end support of this behavior in `neptune-client==1.0.0`."
" To log the object as a string, use `str(object)` instead.",
stack_level=2,
)
value = String(str(value))
else:
raise TypeError("Value of unsupported type {}".format(type(value)))
value = cast_value(value)
parsed_path = parse_path(path)

with self._lock:
Expand Down
4 changes: 4 additions & 0 deletions src/neptune/new/types/atoms/datetime.py
Expand Up @@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
from dataclasses import dataclass
from datetime import datetime
from typing import (
TYPE_CHECKING,
Expand All @@ -27,7 +28,10 @@
Ret = TypeVar("Ret")


@dataclass
class Datetime(Atom):
value: datetime

def __init__(self, value: datetime):
self.value = value.replace(microsecond=1000 * int(value.microsecond / 1000))

Expand Down
14 changes: 12 additions & 2 deletions src/neptune/new/types/atoms/file.py
Expand Up @@ -295,9 +295,9 @@ def as_pickle(obj) -> "File":
def create_from(value) -> "File":
if isinstance(value, str):
return File(path=value)
elif is_pil_image(value) or is_matplotlib_figure(value):
elif File.is_convertable_to_image(value):
return File.as_image(value)
elif is_plotly_figure(value) or is_altair_chart(value) or is_bokeh_figure(value):
elif File.is_convertable_to_html(value):
return File.as_html(value)
elif is_numpy_array(value):
raise TypeError("Value of type {} is not supported. Please use File.as_image().".format(type(value)))
Expand All @@ -319,3 +319,13 @@ def is_convertable(value):
or is_pandas_dataframe(value)
or isinstance(value, File)
)

@staticmethod
def is_convertable_to_image(value):
convertable_to_img_predicates = (is_pil_image, is_matplotlib_figure)
return any(predicate(value) for predicate in convertable_to_img_predicates)

@staticmethod
def is_convertable_to_html(value):
convertable_to_html_predicates = (is_altair_chart, is_bokeh_figure, is_plotly_figure)
return any(predicate(value) for predicate in convertable_to_html_predicates)
78 changes: 78 additions & 0 deletions src/neptune/new/types/type_casting.py
@@ -0,0 +1,78 @@
#
# Copyright (c) 2022, Neptune Labs Sp. z o.o.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import argparse
from datetime import datetime
from typing import Any

from neptune.common.deprecation import warn_once
from neptune.new.handler import Handler
from neptune.new.internal.utils import (
is_bool,
is_dict_like,
is_float,
is_float_like,
is_int,
is_string,
is_string_like,
)
from neptune.new.types import (
Boolean,
File,
Integer,
)
from neptune.new.types.atoms.datetime import Datetime
from neptune.new.types.atoms.float import Float
from neptune.new.types.atoms.string import String
from neptune.new.types.namespace import Namespace
from neptune.new.types.value import Value
from neptune.new.types.value_copy import ValueCopy


def cast_value(value: Any) -> Value:
Copy link
Contributor

Choose a reason for hiding this comment

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

As we extracted this func (which is so cool!) what do you think about using a single-dispatch? https://docs.python.org/3.7/library/functools.html#functools.singledispatch You should be able to split this function into multiple one that will be triggered base on arguments type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's a good idea.
Let's try it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Or maybe not.
We do casting not only based on type, but also on predicate which looks like it's not supported by singledispatch.

if isinstance(value, Value):
return value
elif isinstance(value, Handler):
return ValueCopy(value)
elif isinstance(value, argparse.Namespace):
return Namespace(vars(value))
elif File.is_convertable_to_image(value):
return File.as_image(value)
elif File.is_convertable_to_html(value):
return File.as_html(value)
elif is_bool(value):
return Boolean(value)
elif is_int(value):
return Integer(value)
elif is_float(value):
return Float(value)
elif is_string(value):
return String(value)
elif isinstance(value, datetime):
return Datetime(value)
elif is_float_like(value):
return Float(float(value))
elif is_dict_like(value):
return Namespace(value)
elif is_string_like(value):
warn_once(
message="The object you're logging will be implicitly cast to a string."
" We'll end support of this behavior in `neptune-client==1.0.0`."
" To log the object as a string, use `str(object)` instead.",
stack_level=3,
)
return String(str(value))
else:
raise TypeError("Value of unsupported type {}".format(type(value)))