Skip to content

Commit

Permalink
Releases/v1.26.0 branch: Implementation of SDK Version comparison for…
Browse files Browse the repository at this point in the history
… API's (#60)

* ENG-3221: Adding release version checks to SDK API's

* Fixed syntax errors and decorator use

* Resolved all type-checking errors in the recently added file '_Version.py'

* Moved _version.py to smsdk folder

* Updated mypy.ini

* Addressing review feedback:

1) Refactored code to replace the global variable with a class attribute.
2) Implemented sorting of GitHub releases to obtain the latest version.

* Addressing the code review comments.

---------

Co-authored-by: JMkrish <[email protected]>
  • Loading branch information
mrnthv and JMkrish authored Jan 3, 2024
1 parent 79d8e5a commit 219b718
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 2 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Change Log

## v1.26.0

---
1. Added release version checks for API's
2.
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ include README.md LICENSE
include smsdk/config/*

recursive-include tests *
include requirements.txt
include CHANGELOG.md
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ follow_imports = silent


# Files free from Static type check errors
files = smsdk/utils.py, smsdk/tool_register.py, smsdk/ma_session.py
files = smsdk/_version.py, smsdk/utils.py, smsdk/tool_register.py, smsdk/ma_session.py
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from setuptools import setup, find_packages
from smsdk._version import version

with open("README.md", "r") as fh:
long_description = fh.read()
Expand All @@ -12,7 +13,7 @@

setup(
name="smsdk",
version="1.0.26",
version=version,
packages=find_packages(exclude=["test*"]),
include_package_data=True,
install_requires=install_requires,
Expand Down
122 changes: 122 additions & 0 deletions smsdk/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#
"""
Track version information for sightmachine-sdk module here.
"""
import datetime
import typing as t_
import itertools
import requests
import warnings
import functools
import re


class VersionInfo(t_.NamedTuple):
major: int
minor: int
patchlevel: int
releaselevel: t_.Optional[str]
serial: t_.Optional[int]

def __str__(self) -> str:
result = "".join(
itertools.chain(
".".join(str(i) for i in self[:3] if i is not None),
(str(i) for i in self[3:] if i is not None),
)
)
return result


version_info = VersionInfo(1, 26, 0, "", None)
version = str(f"v{version_info}")


def sort_releases_descending(
releases: t_.List[t_.Dict[str, t_.Any]]
) -> t_.List[t_.Dict[str, t_.Any]]:
def version_key(release: t_.Dict[str, t_.Any]) -> t_.List[t_.Union[int, str]]:
return [
(int(i) if i.isdigit() else i)
for i in re.split(r"(\d+|\W+)", release["tag_name"].lower())
if i
]

return (
sorted(releases, key=version_key, reverse=True)
if len(releases) > 1
else releases
)


def get_latest_release_version(
releases: t_.List[t_.Dict[str, t_.Any]]
) -> t_.Optional[t_.Any]:
if releases:
sorted_releases = sort_releases_descending(releases)
return sorted_releases[0]["tag_name"]
return None


def get_latest_sdk_release() -> t_.Optional[t_.Any]:
api_url = f"https://api.github.com/repos/{owner}/{repo}/releases"

try:
response = requests.get(api_url, timeout=10)
response.raise_for_status() # To raise an HTTPError for any bad responses
releases = response.json()

if releases:
return get_latest_release_version(releases)
except requests.RequestException as e:
print(f"Error fetching latest SDK release: {e}")

return None


class VersionCheckDecorator:
api_version_printed = False
last_version_check_time = None

@classmethod
def version_check_decorator(cls, func: t_.Any) -> t_.Any:
@functools.wraps(func)
def wrapper(*args: t_.Any, **kwargs: t_.Any) -> t_.Any:
current_time = datetime.datetime.now()

# Check if a week has passed since the last version check
if cls.last_version_check_time is None or (
current_time - cls.last_version_check_time > datetime.timedelta(days=7)
):
cls.api_version_printed = False
cls.last_version_check_time = current_time

if not cls.api_version_printed:
latest_sdk_release = get_latest_sdk_release()
installed_sdk_release = version

if (
installed_sdk_release is not None
and latest_sdk_release is not None
and latest_sdk_release != installed_sdk_release
):
warnings.warn(
f"Installed SDK Version: {installed_sdk_release}. "
f"It is recommended to install release version ({latest_sdk_release}).",
DeprecationWarning,
)
cls.api_version_printed = True

return func(*args, **kwargs)

return wrapper


# Initialize the flag to False
api_version_printed = False

owner = "sightmachine"
repo = "sightmachine-sdk"

# Define version_check_decorator here
version_check_decorator = VersionCheckDecorator.version_check_decorator
28 changes: 28 additions & 0 deletions smsdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# coding: utf-8
""" Sight Machine SDK Client """
from __future__ import unicode_literals, absolute_import
from smsdk._version import version_check_decorator

import pandas as pd
import numpy as np
Expand Down Expand Up @@ -151,17 +152,20 @@ def __init__(self, tenant, site_domain="sightmachine.io", protocol="https"):
self.auth = Authenticator(self)
self.session = self.auth.session

@version_check_decorator
def select_db_schema(self, schema_name):
# remove X_SM_WRKSPACE_ID from self.session.headers
self.session.headers.update({X_SM_DB_SCHEMA: schema_name})
if X_SM_WORKSPACE_ID in self.session.headers:
del self.session.headers[X_SM_WORKSPACE_ID]

@version_check_decorator
def select_workspace_id(self, workspace_id):
self.session.headers.update({X_SM_WORKSPACE_ID: str(workspace_id)})
if X_SM_DB_SCHEMA in self.session.headers:
del self.session.headers[X_SM_DB_SCHEMA]

@version_check_decorator
def get_data_v1(self, ename, util_name, normalize=True, *args, **kwargs):
"""
Main data fetching function for all the entities. Note this is the general data fetch function. You probably want to use the model-specific functions such as get_cycles().
Expand Down Expand Up @@ -234,6 +238,7 @@ def get_data_v1(self, ename, util_name, normalize=True, *args, **kwargs):

return data

@version_check_decorator
@ClientV0.validate_input
@ClientV0.cycle_decorator
def get_cycles(
Expand All @@ -248,6 +253,7 @@ def get_cycles(

return df

@version_check_decorator
@ClientV0.validate_input
@ClientV0.downtime_decorator
def get_downtimes(
Expand All @@ -262,6 +268,7 @@ def get_downtimes(

return df

@version_check_decorator
@ClientV0.validate_input
@ClientV0.part_decorator
def get_parts(
Expand All @@ -277,13 +284,15 @@ def get_parts(

return df

@version_check_decorator
def get_kpis(self, **kwargs):
kpis = smsdkentities.get("kpi")
base_url = get_url(
self.config["protocol"], self.tenant, self.config["site.domain"]
)
return kpis(self.session, base_url).get_kpis(**kwargs)

@version_check_decorator
def get_machine_type_from_clean_name(self, kwargs):
# Get machine_types dataframe to check display name
machine_types_df = self.get_machine_types()
Expand All @@ -304,6 +313,7 @@ def get_machine_type_from_clean_name(self, kwargs):

return machine_types

@version_check_decorator
def get_machine_source_from_clean_name(self, kwargs):
# Get machines dataframe to check display/clean name
machine_sources_df = self.get_machines()
Expand All @@ -324,6 +334,7 @@ def get_machine_source_from_clean_name(self, kwargs):

return machine_sources

@version_check_decorator
def get_kpis_for_asset(self, **kwargs):
kpis = smsdkentities.get("kpi")
base_url = get_url(
Expand All @@ -343,6 +354,7 @@ def get_kpis_for_asset(self, **kwargs):

return kpis(self.session, base_url).get_kpis_for_asset(**kwargs)

@version_check_decorator
def get_kpi_data_viz(
self,
machine_sources=None,
Expand Down Expand Up @@ -390,6 +402,7 @@ def get_kpi_data_viz(
] = self.get_machine_source_from_clean_name(kwargs)
return kpi_entity(self.session, base_url).get_kpi_data_viz(**kwargs)

@version_check_decorator
def get_type_from_machine(self, machine_source=None, **kwargs):
machine = smsdkentities.get("machine")
base_url = get_url(
Expand All @@ -399,6 +412,7 @@ def get_type_from_machine(self, machine_source=None, **kwargs):
machine_source, **kwargs
)

@version_check_decorator
def get_machine_schema(
self,
machine_source=None,
Expand Down Expand Up @@ -436,6 +450,7 @@ def get_machine_schema(

return frame

@version_check_decorator
def get_fields_of_machine_type(
self,
machine_type=None,
Expand All @@ -460,6 +475,7 @@ def get_fields_of_machine_type(

return fields

@version_check_decorator
def get_cookbooks(self, **kwargs):
"""
Gets all of the cookbooks accessable to the logged in user.
Expand All @@ -471,6 +487,7 @@ def get_cookbooks(self, **kwargs):
)
return cookbook(self.session, base_url).get_cookbooks(**kwargs)

@version_check_decorator
def get_cookbook_top_results(self, recipe_group_id=None, limit=10, **kwargs):
"""
Gets the top runs for a recipe group.
Expand All @@ -486,6 +503,7 @@ def get_cookbook_top_results(self, recipe_group_id=None, limit=10, **kwargs):
recipe_group_id, limit, **kwargs
)

@version_check_decorator
def get_cookbook_current_value(self, variables=[], minutes=1440, **kwargs):
"""
Gets the current value of a field.
Expand All @@ -501,6 +519,7 @@ def get_cookbook_current_value(self, variables=[], minutes=1440, **kwargs):
variables, minutes, **kwargs
)

@version_check_decorator
def normalize_constraint(self, constraint):
"""
Takes a constraint and returns a string version of it's to and from fields.
Expand All @@ -513,6 +532,7 @@ def normalize_constraint(self, constraint):
from_symbol = "]" if constraint.get("from_is_inclusive") else ")"
return "{}{},{}{}".format(to_symbol, to_val, from_val, from_symbol)

@version_check_decorator
def normalize_constraints(self, constraints):
"""
Takes a list of constraint and returns string versions of their to and from fields.
Expand All @@ -524,6 +544,7 @@ def normalize_constraints(self, constraints):
constraints_normal.append(self.normalize_constraint(constraint))
return constraints_normal

@version_check_decorator
def get_lines(self, **kwargs):
"""
Returns all the lines for the facility
Expand All @@ -534,6 +555,7 @@ def get_lines(self, **kwargs):
)
return lines(self.session, base_url).get_lines(**kwargs)

@version_check_decorator
def get_line_data(
self,
assets=None,
Expand Down Expand Up @@ -583,6 +605,7 @@ def get_line_data(
limit=limit, offset=offset, **kwargs
)

@version_check_decorator
def create_share_link(
self,
assets=None,
Expand Down Expand Up @@ -626,6 +649,7 @@ def create_share_link(
*args, assets, chartType, yAxis, xAxis, model, time_selection, **kwargs
)

@version_check_decorator
def get_machines(self, normalize=True, *args, **kwargs):
"""
Get list of machines and associated metadata. Note this includes extensive internal metadata. If you only want to get a list of machine names
Expand All @@ -639,6 +663,7 @@ def get_machines(self, normalize=True, *args, **kwargs):
"machine_v1", "get_machines", normalize, *args, **kwargs
)

@version_check_decorator
def get_machine_names(self, source_type=None, clean_strings_out=True):
"""
Get a list of machine names. This is a simplified version of get_machines().
Expand Down Expand Up @@ -676,6 +701,7 @@ def get_machine_names(self, source_type=None, clean_strings_out=True):
else:
return machines["source"].to_list()

@version_check_decorator
def get_machine_types(self, source_type=None, *args, **kwargs):
"""
Get list of machine types and associated metadata. Note this includes extensive internal metadata. If you only want to get a list of machine type names
Expand All @@ -695,6 +721,7 @@ def get_machine_types(self, source_type=None, *args, **kwargs):

return mts

@version_check_decorator
def get_machine_type_names(self, clean_strings_out=True):
"""
Get a list of machine type names. This is a simplified version of get_machine_types().
Expand All @@ -715,6 +742,7 @@ def get_machine_type_names(self, clean_strings_out=True):
else:
return machine_types["source_type"].unique().tolist()

@version_check_decorator
def get_raw_data(
self,
raw_data_table=None,
Expand Down

0 comments on commit 219b718

Please sign in to comment.