Skip to content
Open
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
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,34 @@ When accessing Sight Machine via the SDK, the first step is always to initialize
will point to the name of the tenant on Sight Machine. For example, if you access Sight Machine at the URL *mycompany*.sightmachine.io, then *mycompany* is
the name of the tenant you will use. For purposes of this Quick Start documentation, we will use demo as the tenant name.

To initialize a Client:
To initialize a Client:

```
from smsdk import client
tenant = 'demo'
cli = client.Client(tenant)
```

#### Nested Path Support

For deployments where Sight Machine is hosted under a nested path (e.g., `https://tenant.sightmachine.io/nested/one/two/`), you can specify the base path in two ways:

**Method 1: Explicit base_path parameter**
```python
cli = client.Client('demo', base_path='/nested/one/two')
```

**Method 2: Include path in tenant URL**
```python
cli = client.Client('https://demo.sightmachine.io/nested/one/two')
```

The SDK will automatically construct URLs with the correct path prefix for all API calls. For example:
- Without path: `https://demo.sightmachine.io/v1/datatab/cycle`
- With path: `https://demo.sightmachine.io/nested/one/two/v1/datatab/cycle`

**Note:** The `base_path` parameter takes precedence if both methods are used.

### Authenticating

Sight Machine currently supports two methods of authentication via the SDK:
Expand Down
1 change: 1 addition & 0 deletions smsdk/Auth/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def __init__(self, client):
client.tenant,
client.config["site.domain"],
client.config["port"],
client.config.get("base.path"),
)
self.session.headers = default_headers()

Expand Down
30 changes: 28 additions & 2 deletions smsdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import pandas as pd
import numpy as np
import typing as t_

try:
# for newer pandas versions >1.X
Expand Down Expand Up @@ -128,7 +129,11 @@ class Client(ClientV0):
"""Connection point to the Sight Machine platform to retrieve data"""

def __init__(
self, tenant: str, site_domain: str = "sightmachine.io", protocol: str = "https"
self,
tenant: str,
site_domain: str = "sightmachine.io",
protocol: str = "https",
base_path: t_.Optional[str] = None,
):
"""
Initialize the client.
Expand All @@ -139,9 +144,15 @@ def __init__(
The site domain to connect to. Necessary to change if deploying in
a non-standard environment.
:type site_domain: :class:`string`
:param protocol: Protocol to use (https or http).
:type protocol: :class:`string`
:param base_path: Optional path prefix for nested deployments (e.g., "/nested/one/two")
:type base_path: :class:`string` or None
"""

super().__init__(tenant, site_domain=site_domain, protocol=protocol)
super().__init__(
tenant, site_domain=site_domain, protocol=protocol, base_path=base_path
)

@version_check_decorator
def select_db_schema(self, schema_name):
Expand Down Expand Up @@ -176,6 +187,7 @@ def get_data_v1(self, ename, util_name, normalize=True, *args, **kwargs):
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)

df = pd.DataFrame()
Expand Down Expand Up @@ -292,6 +304,7 @@ def get_kpis(self, **kwargs):
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)
return kpis(self.session, base_url).get_kpis(**kwargs)

Expand Down Expand Up @@ -345,6 +358,7 @@ def get_kpis_for_asset(self, **kwargs):
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)
if "machine_type" in kwargs["asset_selection"]:
# updating kwargs with machine_type's system name in case of user provides display name.
Expand Down Expand Up @@ -394,6 +408,7 @@ def get_kpi_data_viz(
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)

if "asset_selection" in kwargs and "machine_type" in kwargs["asset_selection"]:
Expand All @@ -419,6 +434,7 @@ def get_type_from_machine(self, machine_source=None, **kwargs):
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)
return machine(self.session, base_url).get_type_from_machine_name(
machine_source, **kwargs
Expand All @@ -440,6 +456,7 @@ def get_machine_schema(
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)
fields = machineType(self.session, base_url).get_fields(machine_type, **kwargs)
fields = [
Expand Down Expand Up @@ -479,6 +496,7 @@ def get_fields_of_machine_type(
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)
fields = machineType(self.session, base_url).get_fields(machine_type, **kwargs)
fields = [
Expand All @@ -505,6 +523,7 @@ def get_cookbooks(self, **kwargs):
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)
return cookbook(self.session, base_url).get_cookbooks(**kwargs)

Expand All @@ -522,6 +541,7 @@ def get_cookbook_top_results(self, recipe_group_id=None, limit=10, **kwargs):
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)
return cookbook(self.session, base_url).get_top_results(
recipe_group_id, limit, **kwargs
Expand All @@ -541,6 +561,7 @@ def get_cookbook_current_value(self, variables=[], minutes=1440, **kwargs):
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)
return cookbook(self.session, base_url).get_current_value(
variables, minutes, **kwargs
Expand Down Expand Up @@ -582,6 +603,7 @@ def get_lines(self, **kwargs):
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)
return lines(self.session, base_url).get_lines(**kwargs)

Expand Down Expand Up @@ -613,6 +635,7 @@ def get_line_data(
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)

asset_selection = []
Expand Down Expand Up @@ -664,6 +687,7 @@ def get_line_data_lineviz(
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)

if i_vars:
Expand Down Expand Up @@ -707,6 +731,7 @@ def create_share_link(
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)
if assets and model == "cycle" or assets and model == "kpi":
machine_types = []
Expand Down Expand Up @@ -851,6 +876,7 @@ def get_raw_data(
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)
select = [{"name": field} for field in fields]
kwargs["asset_selection"] = {"raw_data_table": raw_data_table}
Expand Down
49 changes: 35 additions & 14 deletions smsdk/client_v0.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ def convert_to_valid_url(
input_url: str,
default_domain: str = "sightmachine.io",
default_protocol: str = "https",
):
) -> t_.Tuple[str, t_.Optional[str]]:
port = ""
path = ""
path = None

# Check if the input URL has a protocol specified
if "://" in input_url:
Expand All @@ -100,9 +100,14 @@ def convert_to_valid_url(

if len(parts) == 1:
domain = parts[0]
path = ""
path = None
else:
domain, path = parts
# Only keep path if it's not empty after stripping
if path and path.strip():
path = "/" + path.rstrip("/")
else:
path = None

# Check if the domain has a port specified
splits = domain.split(":", 1)
Expand All @@ -125,11 +130,7 @@ def convert_to_valid_url(
valid_url = f"{valid_url}:{port}"
# log.warning(f"Ignored the user specified port.")

if path:
# valid_url = f"{valid_url}/{path}"
log.warning(f"Ignored the user specified path.")

return valid_url
return valid_url, path


# We don't have a downtime schema, so hard code one
Expand Down Expand Up @@ -164,7 +165,11 @@ class ClientV0(object):
config = {}

def __init__(
self, tenant: str, site_domain: str = "sightmachine.io", protocol: str = "https"
self,
tenant: str,
site_domain: str = "sightmachine.io",
protocol: str = "https",
base_path: t_.Optional[str] = None,
):
"""
Initialize the client.
Expand All @@ -175,17 +180,22 @@ def __init__(
The site domain to connect to. Necessary to change if deploying in
a non-standard environment.
:type site_domain: :class:`string`
:param protocol: Protocol to use (https or http).
:type protocol: :class:`string`
:param base_path: Optional path prefix for nested deployments (e.g., "/nested/one/two")
:type base_path: :class:`string` or None
"""

port = None
extracted_path = None
if tenant:
# Convert the input tenant into a valid url
url = convert_to_valid_url(
# Convert the input tenant into a valid url and extract any path
url_without_path, extracted_path = convert_to_valid_url(
tenant, default_domain=site_domain, default_protocol=protocol
)

# Parse the input string
parsed_uri = urlparse(url)
# Parse the input string (now without path)
parsed_uri = urlparse(url_without_path)

tenant = parsed_uri.netloc.split(".", 1)[0]
protocol = parsed_uri.scheme
Expand All @@ -194,8 +204,16 @@ def __init__(
# Extract port
port = parsed_uri.port

# Determine final base_path: explicit param takes precedence over extracted
final_base_path = base_path if base_path is not None else extracted_path

self.tenant = tenant
self.config = {"protocol": protocol, "site.domain": site_domain, "port": port}
self.config = {
"protocol": protocol,
"site.domain": site_domain,
"port": port,
"base.path": final_base_path,
}

# Setup Authenticator
self.auth = Authenticator(self)
Expand Down Expand Up @@ -280,6 +298,7 @@ def get_data(self, ename, util_name, normalize=True, *args, **kwargs):
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)

df = pd.DataFrame()
Expand Down Expand Up @@ -1492,6 +1511,7 @@ def get_cycle_count(
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)
cls = smsdkentities.get("dataviz_cycle")(self.session, base_url)

Expand Down Expand Up @@ -1606,6 +1626,7 @@ def get_part_count(self, start_time="", end_time="", part_type=None, **kwargs):
self.tenant,
self.config["site.domain"],
self.config["port"],
self.config.get("base.path"),
)
cls = smsdkentities.get("dataviz_part")(self.session, base_url)

Expand Down
17 changes: 16 additions & 1 deletion smsdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ def all(self) -> t_.Dict[str, t_.Callable[..., t_.Any]]:


def get_url(
protocol: str, tenant: str, site_domain: str, port: t_.Optional[int] = None
protocol: str,
tenant: str,
site_domain: str,
port: t_.Optional[int] = None,
base_path: t_.Optional[str] = None,
) -> str:
"""
Get the URL of the web address.
Expand All @@ -34,6 +38,8 @@ def get_url(
:type site_domain: :class:`string`
:param port: The port number (defaults to None).
:type port: :Int
:param base_path: Optional path prefix for nested deployments (e.g., "/nested/one/two")
:type base_path: :class:`string` or None
"""

url = ""
Expand All @@ -43,4 +49,13 @@ def get_url(
else:
url = f"{protocol}://{tenant}.{site_domain}"

# Add base_path if provided
if base_path:
# Normalize: ensure starts with / but doesn't end with /
normalized_path = base_path.strip()
if not normalized_path.startswith("/"):
normalized_path = "/" + normalized_path
normalized_path = normalized_path.rstrip("/")
url = f"{url}{normalized_path}"

return url
Loading
Loading