Skip to content

Commit

Permalink
Added minimal Run classes (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
Raalsky authored Jul 29, 2024
1 parent 8aa38d3 commit 6cf6715
Show file tree
Hide file tree
Showing 11 changed files with 358 additions and 1 deletion.
28 changes: 28 additions & 0 deletions .github/actions/install-package/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: Package
description: Install python and package
inputs:
python-version:
description: "Python version"
required: true
os:
description: "Operating system"
required: true

runs:
using: "composite"
steps:
- name: Install Python ${{ inputs.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ inputs.python-version }}

- name: Install dependencies
run: |
pip install -r dev_requirements.txt
shell: bash

- name: List dependencies
run: |
pip list
shell: bash
47 changes: 47 additions & 0 deletions .github/actions/test-unit/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
name: Test Unit
description: Check unit tests
inputs:
python-version:
description: "Python version"
required: true
os:
description: "Operating system"
required: true
report_job:
description: "Job name to update by JUnit report"
required: true

runs:
using: "composite"
steps:
- name: Install package
uses: ./.github/actions/install-package
with:
python-version: ${{ inputs.python-version }}
os: ${{ inputs.os }}-latest

- name: Test
run: |
pytest -v ./tests/unit/ \
--timeout=120 --timeout_method=thread \
--color=yes \
--junitxml="./test-results/test-unit-new-${{ inputs.os }}-${{ inputs.python-version }}.xml"
shell: bash

- name: Upload test reports
uses: actions/upload-artifact@v3
if: always()
with:
name: test-artifacts
path: ./test-results

- name: Report
uses: mikepenz/[email protected]
if: always()
with:
report_paths: './test-results/test-unit-*.xml'
update_check: true
include_passed: true
annotate_notice: true
job_name: ${{ inputs.report_job }}
30 changes: 30 additions & 0 deletions .github/workflows/unit-in-pull-request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Unittests

on:
workflow_dispatch:
push:
branches-ignore:
- main

jobs:
test:
timeout-minutes: 75
strategy:
fail-fast: false
matrix:
os: [ubuntu, windows, macos]
python-version: ["3.8"]
name: 'test (${{ matrix.os }} - py${{ matrix.python-version }})'
runs-on: ${{ matrix.os }}-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Run tests
uses: ./.github/actions/test-unit
with:
python-version: ${{ matrix.python-version }}
os: ${{ matrix.os }}
report_job: 'test (${{ matrix.os }} - py${{ matrix.python-version }})'
35 changes: 35 additions & 0 deletions .github/workflows/unit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: unit

on:
workflow_call:
workflow_dispatch:
schedule:
- cron: "0 4 * * *" # Run every day at arbitrary time (4:00 AM UTC)
push:
branches:
- main

jobs:
test:
timeout-minutes: 75
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
os: [ubuntu, windows, macos]

name: 'test (${{ matrix.os }} - py${{ matrix.python-version }})'
runs-on: ${{ matrix.os }}-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.client_payload.pull_request.head.ref }}

- name: Run tests
uses: ./.github/actions/test-unit
with:
python-version: ${{ matrix.python-version }}
os: ${{ matrix.os }}
report_job: 'test (${{ matrix.os }} - py${{ matrix.python-version }})'
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
## [UNRELEASED] neptune-client-scale 0.1.0
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- Added minimal Run classes ([#6](https://github.com/neptune-ai/neptune-client-scale/pull/6))
2 changes: 2 additions & 0 deletions dev_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@

# dev
pre-commit
pytest
pytest-timeout
73 changes: 73 additions & 0 deletions src/neptune_scale/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""
Python package
"""

from __future__ import annotations

__all__ = ["Run"]

from contextlib import AbstractContextManager
from types import TracebackType

from neptune_scale.core.validation import (
verify_max_length,
verify_non_empty,
verify_project_qualified_name,
verify_type,
)
from neptune_scale.parameters import (
MAX_FAMILY_LENGTH,
MAX_RUN_ID_LENGTH,
)


class Run(AbstractContextManager):
"""
Representation of tracked metadata.
"""

def __init__(self, *, project: str, api_token: str, family: str, run_id: str) -> None:
"""
Initializes a run that logs the model-building metadata to Neptune.
Args:
project: Name of the project where the metadata is logged, in the form `workspace-name/project-name`.
api_token: Your Neptune API token.
family: Identifies related runs. For example, the same value must apply to all runs within a run hierarchy.
Max length: 128 characters.
run_id: Unique identifier of a run. Must be unique within the project. Max length: 128 characters.
"""
verify_type("api_token", api_token, str)
verify_type("family", family, str)
verify_type("run_id", run_id, str)

verify_non_empty("api_token", api_token)
verify_non_empty("family", family)
verify_non_empty("run_id", run_id)

verify_project_qualified_name("project", project)

verify_max_length("family", family, MAX_FAMILY_LENGTH)
verify_max_length("run_id", run_id, MAX_RUN_ID_LENGTH)

self._project: str = project
self._api_token: str = api_token
self._family: str = family
self._run_id: str = run_id

def __enter__(self) -> Run:
return self

def close(self) -> None:
"""
Stops the connection to Neptune and synchronizes all data.
"""
pass

def __exit__(
self,
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
self.close()
Empty file.
48 changes: 48 additions & 0 deletions src/neptune_scale/core/validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
__all__ = (
"verify_type",
"verify_non_empty",
"verify_max_length",
"verify_project_qualified_name",
)

from typing import (
Any,
Union,
)


def get_type_name(var_type: Union[type, tuple]) -> str:
return var_type.__name__ if hasattr(var_type, "__name__") else str(var_type)


def verify_type(var_name: str, var: Any, expected_type: Union[type, tuple]) -> None:
try:
if isinstance(expected_type, tuple):
type_name = " or ".join(get_type_name(t) for t in expected_type)
else:
type_name = get_type_name(expected_type)
except Exception as e:
# Just to be sure that nothing weird will be raised here
raise TypeError(f"Incorrect type of {var_name}") from e

if not isinstance(var, expected_type):
raise TypeError(f"{var_name} must be a {type_name} (was {type(var)})")


def verify_non_empty(var_name: str, var: Any) -> None:
if not var:
raise ValueError(f"{var_name} must not be empty")


def verify_max_length(var_name: str, var: Any, max_length: int) -> None:
if len(var) > max_length:
raise ValueError(f"{var_name} must not exceed {max_length} characters")


def verify_project_qualified_name(var_name: str, var: Any) -> None:
verify_type(var_name, var, str)
verify_non_empty(var_name, var)

project_parts = var.split("/")
if len(project_parts) != 2:
raise ValueError(f"{var_name} is not in expected format, should be 'workspace-name/project-name")
2 changes: 2 additions & 0 deletions src/neptune_scale/parameters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
MAX_RUN_ID_LENGTH = 128
MAX_FAMILY_LENGTH = 128
82 changes: 82 additions & 0 deletions tests/unit/test_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import uuid

import pytest

from neptune_scale import Run


def test_context_manager():
# given
project = "workspace/project"
api_token = "API_TOKEN"
run_id = str(uuid.uuid4())
family = run_id

# when
with Run(project=project, api_token=api_token, family=family, run_id=run_id):
...

# then
assert True


def test_close():
# given
project = "workspace/project"
api_token = "API_TOKEN"
run_id = str(uuid.uuid4())
family = run_id

# and
run = Run(project=project, api_token=api_token, family=family, run_id=run_id)

# when
run.close()

# then
assert True


def test_family_too_long():
# given
project = "workspace/project"
api_token = "API_TOKEN"
run_id = str(uuid.uuid4())

# and
family = "a" * 1000

# when
with pytest.raises(ValueError):
with Run(project=project, api_token=api_token, family=family, run_id=run_id):
...


def test_run_id_too_long():
# given
project = "workspace/project"
api_token = "API_TOKEN"
family = str(uuid.uuid4())

# and
run_id = "a" * 1000

# then
with pytest.raises(ValueError):
with Run(project=project, api_token=api_token, family=family, run_id=run_id):
...


def test_invalid_project_name():
# given
api_token = "API_TOKEN"
run_id = str(uuid.uuid4())
family = run_id

# and
project = "just-project"

# then
with pytest.raises(ValueError):
with Run(project=project, api_token=api_token, family=family, run_id=run_id):
...

0 comments on commit 6cf6715

Please sign in to comment.