-
Notifications
You must be signed in to change notification settings - Fork 2
Add V1 endpoints for factory, machine, machine_type #24
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
base: master
Are you sure you want to change the base?
Changes from 10 commits
ae370cd
259faf1
45288ca
e1e36f9
fa4c575
e685cf4
daf5261
65a7b8f
99cb7b8
9fec588
05556db
4d5eb96
3f7cfa1
b3e6f03
14b5313
e3a0631
13fe6bc
c954b27
220bdbe
c3add50
d3ac6ef
2b1d17a
407e9d8
ae793a7
4d12842
69e0706
101f987
b0440c2
3070b87
fd75a78
b0ae92f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,7 +46,7 @@ def dict_to_df(data, normalize=True): | |
| # machine type stats are list | ||
| cols = [*data[0]] | ||
| cols.remove('stats') | ||
| df = json_normalize(data, 'stats', cols, record_prefix='stats.') | ||
| df = json_normalize(data, 'stats', cols, record_prefix='stats.', errors='ignore') | ||
| else: | ||
| try: | ||
| df = json_normalize(data) | ||
|
|
@@ -62,7 +62,7 @@ def dict_to_df(data, normalize=True): | |
|
|
||
| if 'id' in df.columns: | ||
| df.set_index('id', inplace=True) | ||
|
|
||
| return df | ||
|
|
||
|
|
||
|
|
@@ -154,13 +154,17 @@ def get_data_v1(self, ename, util_name, normalize=True, *args, **kwargs): | |
| # dict params strictly follow {'key':'value'} format | ||
|
|
||
| # sub_kwargs = kwargs | ||
| if util_name in ['get_cycles', 'get_downtime', 'get_parts']: | ||
| if util_name in ['get_cycles', 'get_downtime', 'get_parts', 'get_factories', 'get_machines', 'get_machine_types']: | ||
| sub_kwargs = [kwargs] | ||
| else: | ||
| sub_kwargs = self.fix_only(kwargs) | ||
|
|
||
| if len(sub_kwargs) == 1: | ||
| data = dict_to_df(getattr(cls, util_name)(*args, **sub_kwargs[0]), normalize) | ||
| if util_name in ['get_factories', 'get_machines', 'get_machine_types']: | ||
| # data = dict_to_df(getattr(cls, util_name)(*args, **sub_kwargs[0]), normalize) | ||
| return getattr(cls, util_name)(normalize, *args, **sub_kwargs[0]) | ||
mks-sight marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| else: | ||
| data = dict_to_df(getattr(cls, util_name)(*args, **sub_kwargs[0]), normalize) | ||
| else: | ||
| data = dict_to_df(getattr(cls, util_name)(*args, **sub_kwargs[0]), normalize) | ||
| for sub in sub_kwargs[1:]: | ||
|
|
@@ -225,3 +229,118 @@ def get_machine_schema(self, machine_source, types=[], return_mtype=False, **kwa | |
| f"Unknow stat schema identified :: machine_type {machine_source} - " | ||
| f"title_prefix :: {stat.get('display', {}).get('title_prefix', '')}") | ||
| return fields | ||
|
|
||
| def _get_factories(self, normalize=True, *args, **kwargs): | ||
mks-sight marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| """ | ||
| Get list of factories and associated metadata. Note this includes extensive internal metadata. | ||
|
|
||
| :param normalize: Flatten nested data structures | ||
| :type normalize: bool | ||
| :return: pandas dataframe | ||
| """ | ||
| return self.get_data_v1('factory_v1', 'get_factories', normalize, *args, **kwargs) | ||
|
|
||
| def _get_machines(self, normalize=True, *args, **kwargs) -> pd.DataFrame: | ||
| """ | ||
| Get list of machines and associated metadata. Note this includes extensive internal metadata. If you only want to get a list of machine names | ||
| then see also get_machine_names(). | ||
|
|
||
| :param normalize: Flatten nested data structures | ||
| :type normalize: bool | ||
| :return: pandas dataframe | ||
| """ | ||
| return self.get_data_v1('machine_v1', 'get_machines', normalize, *args, **kwargs) | ||
|
|
||
| def _get_machine_types(self, normalize=True, *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 | ||
| then see also get_machine_type_names(). | ||
|
|
||
| :param normalize: Flatten nested data structures | ||
| :type normalize: bool | ||
| :return: pandas dataframe | ||
| """ | ||
|
|
||
| return self.get_data_v1('machine_type_v1', 'get_machine_types', normalize, *args, **kwargs) | ||
|
|
||
| def get_factories(self, normalize=True, *args, **kwargs): | ||
| generator = self._get_factories(normalize=normalize, *args, **kwargs) | ||
| data = [] | ||
| for page in generator: | ||
| try: | ||
| data.append(page) | ||
| except Exception as e: | ||
| print(e) | ||
mks-sight marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| data = pd.concat(data) | ||
| return data | ||
|
|
||
| def get_machines(self, normalize=True, *args, **kwargs): | ||
| generator = self._get_machines(normalize=normalize, *args, **kwargs) | ||
| data = [] | ||
| for page in generator: | ||
| try: | ||
| data.append(page) | ||
| except Exception as e: | ||
| print(e) | ||
| data = pd.concat(data) | ||
| return data | ||
|
|
||
| def get_machine_types(self, normalize=True, *args, **kwargs): | ||
| generator = self._get_machine_types(normalize=normalize, *args, **kwargs) | ||
| data = [] | ||
| for page in generator: | ||
mks-sight marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| try: | ||
| data.append(page) | ||
| except Exception as e: | ||
| print(e) | ||
| data = pd.concat(data) | ||
| return data | ||
|
|
||
| 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(). | ||
|
|
||
| :param source_type: filter the list to only the specified source_type | ||
| :type source_type: str | ||
| :param clean_strings_out: If true, return the list using the UI-based display names. If false, the list contains the Sight Machine internal machine names. | ||
| :return: list | ||
| """ | ||
|
|
||
| query_params = {'_only': ['source', 'source_clean', 'source_type'], | ||
| '_order_by': 'source_clean'} | ||
mks-sight marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if source_type: | ||
| # Double check the type | ||
| mt = self.get_machine_types(source_type=source_type) | ||
| # If it was found, then no action to take, otherwise try looking up from clean string | ||
| mt = self.get_machine_types(source_type_clean=source_type) if not len(mt) else [] | ||
mks-sight marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if len(mt): | ||
| source_type = mt['source_type'].iloc[0] | ||
| else: | ||
| log.error('Machine Type not found') | ||
| return [] | ||
|
|
||
| query_params['source_type'] = source_type | ||
|
|
||
| machines = self.get_data_v1('machine_v1', 'get_machines', normalize=True, **query_params) | ||
|
|
||
| if clean_strings_out: | ||
| return machines['source_clean'].to_list() | ||
| else: | ||
| return machines['source'].to_list() | ||
|
|
||
| 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(). | ||
|
|
||
| :param clean_strings_out: If true, return the list using the UI-based display names. If false, the list contains the Sight Machine internal machine types. | ||
| :return: list | ||
| """ | ||
| query_params = {'_only': ['source_type', 'source_type_clean'], | ||
| '_order_by': 'source_type_clean'} | ||
mks-sight marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| machine_types = self.get_data_v1('machine_type_v1', 'get_machine_types', normalize=True, **query_params) | ||
|
|
||
| if clean_strings_out: | ||
| return machine_types['source_type_clean'].to_list() | ||
| else: | ||
| return machine_types['source_type'].to_list() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this module kept around for? What v0 interfaces remain? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,8 @@ | |
| import requests | ||
|
|
||
| import numpy as np | ||
| import pandas as pd | ||
| from pandas import json_normalize | ||
|
|
||
| from requests.structures import CaseInsensitiveDict | ||
| from requests.sessions import Session | ||
|
|
@@ -22,6 +24,35 @@ | |
| SM_AUTH_HEADER_SECRET_ID_OLD = RESOURCE_CONFIG["auth_header-api-secret_old"] | ||
| SM_AUTH_HEADER_KEY_ID = RESOURCE_CONFIG["auth_header-api-key"] | ||
|
|
||
| def dict_to_df(data, normalize=True): | ||
| if normalize: | ||
| # special case to handle the 'stats' block | ||
| if data and 'stats' in data[0]: | ||
| if isinstance(data[0]['stats'], dict): | ||
| # part stats are dict | ||
| df = json_normalize(data) | ||
| else: | ||
| # machine type stats are list | ||
| cols = [*data[0]] | ||
| cols.remove('stats') | ||
| df = json_normalize(data, 'stats', cols, record_prefix='stats.', errors='ignore') | ||
| else: | ||
| try: | ||
| df = json_normalize(data) | ||
| except: | ||
| # From cases like _distinct which don't have a "normal" return format | ||
| return pd.DataFrame({'values': data}) | ||
| else: | ||
| df = pd.DataFrame(data) | ||
|
|
||
| if len(df) > 0: | ||
| if '_id' in df.columns: | ||
| df.set_index('_id', inplace=True) | ||
|
|
||
| if 'id' in df.columns: | ||
mks-sight marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| df.set_index('id', inplace=True) | ||
| return df | ||
|
|
||
| import logging | ||
| log = logging.getLogger(__name__) | ||
|
|
||
|
|
@@ -96,8 +127,7 @@ def _get_records( | |
|
|
||
| except: | ||
| import traceback | ||
|
|
||
| print(traceback.print_exc()) | ||
| log.error(traceback.print_exc()) | ||
mks-sight marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
mks-sight marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return records | ||
|
|
||
| def _get_schema( | ||
|
|
@@ -203,7 +233,7 @@ def _get_records_v1( | |
| except: | ||
| import traceback | ||
|
|
||
| print(traceback.print_exc()) | ||
| log.error(traceback.print_exc()) | ||
mks-sight marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return records | ||
|
|
||
| def get_json_headers(self): | ||
|
|
@@ -243,3 +273,83 @@ def get_starttime_endtime_keys(self, **kwargs): | |
| continue | ||
|
|
||
| return starttime_key, endtime_key | ||
|
|
||
| def _get_records_mongo_v1( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does mongo in the function name mean? |
||
| self, | ||
| endpoint, | ||
| normalize=True, | ||
| method="get", | ||
| limit=np.Inf, | ||
mks-sight marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| offset=1, | ||
| **url_params | ||
| ): | ||
| """ | ||
| Function to get api call and fetch data from MA APIs | ||
| :param endpoint: complete url endpoint | ||
| :param method: Reqested method. Default = get | ||
| :param enable_pagination: if pagination is enabled then | ||
| the records are fetched with limit offset pagination | ||
| :param limit: Limit the number of records for pagination | ||
| :param offset: pagination offset | ||
| :param url_params: dict of params for API ex filtering, columns etc | ||
| :return: List of records | ||
| """ | ||
| next_page = "" | ||
| offset = int(offset) | ||
| try: | ||
| limit = int(limit) | ||
| except: | ||
| limit = float(limit) | ||
|
|
||
| if 'machine_type' in url_params: | ||
| url_params.pop('machine_type') | ||
| max_page_size = 2000 | ||
| limit = min(max_page_size, limit) | ||
| if not url_params.get("per_page"): | ||
| url_params["per_page"] = 5 | ||
|
|
||
| def _fetch_data(endpoint, url_params): | ||
| response = getattr(self.session, method.lower())( | ||
| endpoint, params=url_params | ||
| ) | ||
| if response.text: | ||
| if response.status_code not in [200, 201]: | ||
| raise ValueError("Error - {}".format(response.text)) | ||
| try: | ||
| data = response.json() | ||
| try: | ||
| next_page = data["next_page"] | ||
| except: | ||
| next_page = "" | ||
| if data["success"]: | ||
| data = data['objects'] | ||
| except JSONDecodeError as e: | ||
| print(f'No valid JSON returned {e}') | ||
| data = [] | ||
| else: | ||
| data = [] | ||
| return data, next_page | ||
| while limit > 0: | ||
| if next_page: | ||
| data, next_page = _fetch_data(endpoint=next_page, url_params={}) | ||
| if not next_page: | ||
| limit = 0 | ||
| else: | ||
| limit -= len(data) | ||
| else: | ||
| data, next_page = _fetch_data(endpoint=endpoint, url_params=url_params) | ||
| if not next_page: | ||
| limit = 0 | ||
| else: | ||
| limit -= len(data) | ||
| data = dict_to_df(data, normalize=normalize) | ||
|
|
||
| # To keep consistent, rename columns back from '.' to '__' | ||
| data.columns = [name.replace('.', '__') for name in data.columns] | ||
mks-sight marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if 'endtime' in data.columns: | ||
| data['endtime'] = pd.to_datetime(data['endtime']) | ||
| if 'starttime' in data.columns: | ||
| data['starttime'] = pd.to_datetime(data['starttime']) | ||
|
|
||
| yield data | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unit Tests are a separate PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A useful combination of tools to see how well the code created has test coverage is:
ex.
The report given goes off the code vs the diff, but still useful.