Skip to content

Convert naive datetime objects to UTC #68

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

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ You can log configurations or other single values. Pass the metadata as a dictio

For example, `{"parameters/learning_rate": 0.001}`. In the field path, each forward slash `/` nests the field under a namespace. Use namespaces to structure the metadata into meaningful categories.

Any `datetime` values that don't have the `tzinfo` attribute set are assumed to be in the local timezone.

__Parameters__

| Name | Type | Default | Description |
Expand Down Expand Up @@ -265,7 +267,7 @@ __Parameters__
|-------------|------------------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `data` | `Dict[str, Union[float, int]]` | `None` | Dictionary of metrics to log. Each metric value is associated with a step. To log multiple metrics at once, pass multiple key-value pairs. |
| `step` | `Union[float, int]` | `None` | Index of the log entry. Must be increasing. <br> **Tip:** Using float rather than int values can be useful, for example, when logging substeps in a batch. |
| `timestamp` | `datetime`, optional | `None` | Time of logging the metadata. |
| `timestamp` | `datetime`, optional | `None` | Time of logging the metadata. If not provided, the current time is used. If provided, and `timestamp.tzinfo` is not set, the time is assumed to be in the local timezone. |

__Examples__

Expand Down
19 changes: 14 additions & 5 deletions src/neptune_scale/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
import threading
import time
from contextlib import AbstractContextManager
from datetime import datetime
from datetime import (
datetime,
timezone,
)
from multiprocessing.sharedctypes import Synchronized
from multiprocessing.synchronize import Condition as ConditionT
from typing import (
Expand Down Expand Up @@ -52,7 +55,10 @@
datetime_to_proto,
make_step,
)
from neptune_scale.core.util import safe_signal_name
from neptune_scale.core.util import (
ensure_utc,
safe_signal_name,
)
from neptune_scale.core.validation import (
verify_collection_type,
verify_max_length,
Expand Down Expand Up @@ -429,7 +435,8 @@ def log_metrics(
To log multiple metrics at once, pass multiple key-value pairs.
step: Index of the log entry. Must be increasing.
Tip: Using float rather than int values can be useful, for example, when logging substeps in a batch.
timestamp (optional): Time of logging the metadata.
timestamp (optional): Time of logging the metadata. If not provided, the current time is used. If provided,
and `timestamp.tzinfo` is not set, the time is assumed to be in the local timezone.


Examples:
Expand Down Expand Up @@ -462,6 +469,8 @@ def log_configs(self, data: Optional[Dict[str, Union[float, bool, int, str, date
data: Dictionary of configs or other values to log.
Available types: float, integer, Boolean, string, and datetime.

Any `datetime` values that don't have the `tzinfo` attribute set are assumed to be in the local timezone.

Example:
```
from neptune_scale import Run
Expand Down Expand Up @@ -540,7 +549,7 @@ def log(
verify_type("tags_add", tags_add, (dict, type(None)))
verify_type("tags_remove", tags_remove, (dict, type(None)))

timestamp = datetime.now() if timestamp is None else timestamp
timestamp = datetime.now(tz=timezone.utc) if timestamp is None else ensure_utc(timestamp)
configs = {} if configs is None else configs
metrics = {} if metrics is None else metrics
tags_add = {} if tags_add is None else tags_add
Expand Down Expand Up @@ -568,7 +577,7 @@ def log(
run_id=self._run_id,
step=step,
timestamp=timestamp,
fields=configs,
configs=configs,
metrics=metrics,
add_tags=tags_add,
remove_tags=tags_remove,
Expand Down
6 changes: 3 additions & 3 deletions src/neptune_scale/core/metadata_splitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def __init__(
run_id: str,
step: Optional[Union[int, float]],
timestamp: datetime,
fields: Dict[str, Union[float, bool, int, str, datetime]],
configs: Dict[str, Union[float, bool, int, str, datetime]],
metrics: Dict[str, float],
add_tags: Dict[str, Union[List[str], Set[str]]],
remove_tags: Dict[str, Union[List[str], Set[str]]],
Expand All @@ -53,7 +53,7 @@ def __init__(
self._timestamp = datetime_to_proto(timestamp)
self._project = project
self._run_id = run_id
self._fields = peekable(fields.items())
self._configs = peekable(configs.items())
self._metrics = peekable(starmap(lambda k, v: (k, float(v)), metrics.items()))
self._add_tags = peekable(add_tags.items())
self._remove_tags = peekable(remove_tags.items())
Expand Down Expand Up @@ -84,7 +84,7 @@ def __next__(self) -> Tuple[RunOperation, int]:
size = update.ByteSize()

size = self.populate(
assets=self._fields,
assets=self._configs,
update_producer=lambda key, value: update.assign[key].MergeFrom(value),
size=size,
)
Expand Down
4 changes: 3 additions & 1 deletion src/neptune_scale/core/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
Value,
)

from .util import ensure_utc


def make_value(value: Union[Value, float, str, int, bool, datetime, List[str], Set[str]]) -> Value:
if isinstance(value, Value):
Expand All @@ -43,7 +45,7 @@ def make_value(value: Union[Value, float, str, int, bool, datetime, List[str], S


def datetime_to_proto(dt: datetime) -> Timestamp:
dt_ts = dt.timestamp()
dt_ts = ensure_utc(dt).timestamp()
return Timestamp(seconds=int(dt_ts), nanos=int((dt_ts % 1) * 1e9))


Expand Down
13 changes: 13 additions & 0 deletions src/neptune_scale/core/util.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import signal
from datetime import (
datetime,
timezone,
)


def safe_signal_name(signum: int) -> str:
Expand All @@ -8,3 +12,12 @@ def safe_signal_name(signum: int) -> str:
signame = str(signum)

return signame


def ensure_utc(dt: datetime) -> datetime:
"""If `dt` has no TZ info, assume it's local time, and convert to UTC. Otherwise return as is."""

if dt.tzinfo is None:
return dt.astimezone(timezone.utc)

return dt
31 changes: 17 additions & 14 deletions tests/unit/test_metadata_splitter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from datetime import datetime
from datetime import (
datetime,
timezone,
)

from freezegun import freeze_time
from google.protobuf.timestamp_pb2 import Timestamp
Expand All @@ -23,8 +26,8 @@ def test_empty():
project="workspace/project",
run_id="run_id",
step=1,
timestamp=datetime.now(),
fields={},
timestamp=datetime.now(timezone.utc),
configs={},
metrics={},
add_tags={},
remove_tags={},
Expand All @@ -50,13 +53,13 @@ def test_fields():
project="workspace/project",
run_id="run_id",
step=1,
timestamp=datetime.now(),
fields={
timestamp=datetime.now(timezone.utc),
configs={
"some/string": "value",
"some/int": 2501,
"some/float": 3.14,
"some/bool": True,
"some/datetime": datetime.now(),
"some/datetime": datetime.now(timezone.utc),
"some/tags": {"tag1", "tag2"},
},
metrics={},
Expand Down Expand Up @@ -94,8 +97,8 @@ def test_metrics():
project="workspace/project",
run_id="run_id",
step=1,
timestamp=datetime.now(),
fields={},
timestamp=datetime.now(timezone.utc),
configs={},
metrics={
"some/metric": 3.14,
},
Expand Down Expand Up @@ -128,8 +131,8 @@ def test_tags():
project="workspace/project",
run_id="run_id",
step=1,
timestamp=datetime.now(),
fields={},
timestamp=datetime.now(timezone.utc),
configs={},
metrics={},
add_tags={
"some/tags": {"tag1", "tag2"},
Expand Down Expand Up @@ -174,7 +177,7 @@ def test_tags():
def test_splitting():
# given
max_size = 1024
timestamp = datetime.now()
timestamp = datetime.now(timezone.utc)
metrics = {f"metric{v}": 7 / 9.0 * v for v in range(1000)}
fields = {f"field{v}": v for v in range(1000)}
add_tags = {f"add/tag{v}": {f"value{v}"} for v in range(1000)}
Expand All @@ -186,7 +189,7 @@ def test_splitting():
run_id="run_id",
step=1,
timestamp=timestamp,
fields=fields,
configs=fields,
metrics=metrics,
add_tags=add_tags,
remove_tags=remove_tags,
Expand Down Expand Up @@ -220,7 +223,7 @@ def test_splitting():
def test_split_large_tags():
# given
max_size = 1024
timestamp = datetime.now()
timestamp = datetime.now(timezone.utc)
metrics = {}
fields = {}
add_tags = {"add/tag": {f"value{v}" for v in range(1000)}}
Expand All @@ -232,7 +235,7 @@ def test_split_large_tags():
run_id="run_id",
step=1,
timestamp=timestamp,
fields=fields,
configs=fields,
metrics=metrics,
add_tags=add_tags,
remove_tags=remove_tags,
Expand Down
Loading