Example #1
0
def _bulk_create_part_inventories(api: Api, df: pd.DataFrame, parts_dict: dict) -> dict:
    """
    Bulk create inventory items for every unique serial and part number in CSV.

    Args:
        api (Api): API instance to send authenticated requests
        df (pd.DataFrame): Dataframe of batch run creation CSV
        parts_dict (dict): Mapping from part number to part id

    Returns:
        dict: Mapping of part/serial number tuple to newly created inventory id
    """
    create_mutations = []
    for part_info in df.groupby(['Part number', 'Serial number']).indices:
        mutation_input = {'serialNumber': part_info[1], 'quantity': 1,
                          'partId': parts_dict[part_info[0]]}
        create_mutations.append(
            {'query': mutations.CREATE_PART_INVENTORY,
             'variables': {'input': mutation_input}})
    inventories = api.send_api_request(create_mutations)
    inventory_dict = {}
    for inventory in inventories:
        item = inventory['data']['createPartInventory']['partInventory']
        inventory_dict[(item['part']['partNumber'], item['serialNumber'])] = item
    return inventory_dict
Example #2
0
def import_values(api: Api, input_file: str) -> None:
    df = get_parts_df(input_file)
    logging.info('Starting part importer.')
    to_upload = get_upload_dict(df)
    to_upload = create_upload_items(df, to_upload)
    # Get API token
    access_token = api.get_access_token()
    cache = {}
    # Get pre existing uoms
    to_upload['uoms'], cache = get_existing_items(
        auth_token=access_token, values=to_upload['uoms'], unique_field='type',
        query_type='GET_UNITS_OF_MEASUREMENTS', cache_type='unit_of_measurement',
        cache=cache)
    # Get pre existing locations
    to_upload['locations'], cache = get_existing_items(
        auth_token=access_token, values=to_upload['locations'], unique_field='name',
        query_type='GET_LOCATIONS', cache_type='location', cache=cache)
    # Upload parts, units of measurment and locations
    resp = create_bulk_upload_request(
        access_token, CREATE_UNITS_OF_MEASUREMENT=to_upload['uoms'],
        CREATE_PART=to_upload['parts'], CREATE_LOCATION=to_upload['locations'])
    # Fill cache with ids from newely created objects
    cache = update_cache(resp, cache)
    to_upload = fill_from_cache(to_upload=to_upload, cache=cache,
                                to_fill=['parts_inventories'])
    # Upload part inventories and part lots
    resp = create_bulk_upload_request(
        access_token, CREATE_PART_INVENTORY=to_upload['parts_inventories'],)
    logging.info('Importing finished!')
Example #3
0
def import_bom(api: Api, input_file: str) -> None:
    """
    Parse SolidWorks BOM export into pandas DataFrame.

    Upload each row from the excel file into ION as an MBOM item. If the part being
    referenced by the MBOM item does not exist, create that as well.

    Args:
        input_file (str): Path to SolidWorks BOM to be imported

    Returns:
        bool: Returns true if the BOM was successfully imported
    """
    # Read SolidWorks Level as string to correctly parse hierarchy.
    df = pd.read_excel(input_file, dtype={'Level': str, 'Part Number': str})
    # Use level as index
    df.set_index('Level', inplace=True)
    # Get top level part from file name
    top_level_part_number = args.input_file.split('/')[-1].split('.')[0]

    # TODO: Replace access token fetching with just using the Api object
    # and abstract access tokens and requests from business logic
    # Get API token
    access_token = api.get_access_token()
    part_dict, part_numbers = _get_parts_info(access_token, df,
                                              top_level_part_number)
    _create_mbom_items(access_token, df, part_dict, part_numbers)
    return True
Example #4
0
def _get_locations(api: Api, df: pd.DataFrame) -> dict:
    """
    Get ids for all location names in the excel sheet.

    Args:
        api (Api): API instance to send authenticated requests
        df (pd.DataFrame): Dataframe of excel file passed in arguments

    Returns:
        dict: Mapping from location name to location id
    """
    query_info = {
        'query': mutations.GET_LOCATIONS,
        'variables': {
            'filters': {
                'name': {
                    'in': df['Location'].unique().tolist()
                }
            }
        }
    }
    query_data = api.send_api_request(query_info)
    return {
        edge['node']['name']: edge['node']['id']
        for edge in query_data['data']['locations']['edges']
    }
Example #5
0
def _get_parts(api: Api, df: pd.DataFrame) -> dict:
    """
    Get ids for all part numbers in the excel sheet.

    Args:
        api (Api): API instance to send authenticated requests
        df (pd.DataFrame): Dataframe of excel file passed in arguments

    Returns:
        dict: Mapping from part number to part id
    """
    query_info = {
        'query': mutations.GET_PARTS,
        'variables': {
            'filters': {
                'partNumber': {
                    'in': df['Part Number'].unique().tolist()
                },
                'isLatestRevision': {
                    'eq': True
                }
            }
        }
    }
    query_data = api.send_api_request(query_info)
    return {
        edge['node']['partNumber']: edge['node']['id']
        for edge in query_data['data']['parts']['edges']
    }
Example #6
0
def _create_mbom(api: Api, mbom_map: dict, parts: dict) -> bool:
    """
    Batch create MBOM items for every part in the import excel.

    Args:
        api (Api): API instance to send authenticated requests
        mbom_map (dict): Mapping of part number to parent part and quantity
        parts (dict): Mapping from part number to part id

    Returns:
        bool: True if part import was successful.
    """
    create_mutations = []
    for part_number, mbom_item in mbom_map.items():
        if mbom_item['parent'] in parts and part_number in parts:
            mutation_input = {
                'partId': parts[part_number],
                'parentId': parts[mbom_item['parent']],
                'quantity': mbom_item['quantity']
            }
            create_mutations.append({
                'query': mutations.CREATE_MBOM_ITEM,
                'variables': {
                    'input': mutation_input
                }
            })
    mboms = api.send_api_request(create_mutations)
    for idx, mbom_item in enumerate(mboms):
        if 'errors' in mbom_item and len(mbom_item['errors']) > 0:
            logging.warning(mbom_item['errors'][0]['message'])
    return True
Example #7
0
def _bulk_create_part_inventories(api: Api, df: pd.DataFrame, parts: dict,
                                  locations: dict) -> bool:
    """
    Bulk create inventory items for every row in excel.

    Validates that the part number the inventory object is referencing exists.

    Args:
        api (Api): API instance to send authenticated requests
        df (pd.DataFrame): Dataframe of excel file passed in arguments
        parts (dict): Mapping from part number to part id

    Returns:
        bool: True if inventory import was successful.
    """
    create_mutations = []
    for _, row in df.iterrows():
        if row['Part Number'] not in parts:
            logging.warning('Cannot create inventory because part '
                            f'{row["Part Number"]} does not exist.')
            continue
        quantity = row['Quantity'] if row['Serial Number'] is None else 1
        location = None
        if row['Location'] in locations:
            location = locations[row['Location']]
        mutation_input = {
            'serialNumber': row['Serial Number'],
            'quantity': quantity,
            'partId': parts[row['Part Number']],
            'lotNumber': row['Lot Number'],
            'locationId': location
        }
        create_mutations.append({
            'query': mutations.CREATE_PART_INVENTORY,
            'variables': {
                'input': mutation_input
            }
        })
    inventories = api.send_api_request(create_mutations)
    for idx, inventory in enumerate(inventories):
        if 'errors' in inventory and len(inventory['errors']) > 0:
            logging.warning(inventory['errors'][0]['message'])
    return True
Example #8
0
def _get_procedures(api: Api, df: pd.DataFrame) -> dict:
    """
    Get procedure titles for all procedures in the CSV.

    Args:
        api (Api): API instance to send authenticated requests
        df (pd.DataFrame): Dataframe of batch run creation CSV

    Returns:
        dict: Mapping from procedure id to procedure title
    """
    query_info = {
        'query': mutations.GET_PROCEDURES,
        'variables': {
            'filters': {'id': {'in': df['Procedure (ID)'].unique().tolist()}}
        }
    }
    query_data = api.send_api_request(query_info)
    return {edge['node']['id']: edge['node']['title']
            for edge in query_data['data']['procedures']['edges']}
Example #9
0
def _get_parts(api: Api, df: pd.DataFrame) -> dict:
    """
    Get ids for all part numbers in the CSV.

    Args:
        api (Api): API instance to send authenticated requests
        df (pd.DataFrame): Dataframe of batch run creation CSV

    Returns:
        dict: Mapping from part number to part id
    """
    query_info = {
        'query': mutations.GET_PARTS,
        'variables': {
            'filters': {'partNumber': {'in': df['Part number'].unique().tolist()}}
        }
    }
    query_data = api.send_api_request(query_info)
    return {edge['node']['partNumber']: edge['node']['id']
            for edge in query_data['data']['parts']['edges']}
Example #10
0
def _batch_create_runs(api: Api, df: pd.DataFrame, procedures: dict,
                       inventory_dict: dict, parts: dict) -> bool:
    """
    Batch create runs for every row in the import CSV.

    Args:
        api (Api): API instance to send authenticated requests
        df (pd.DataFrame): Dataframe of batch run creation CSV
        procedures (dict): Mapping from procedure id to procedure title
        inventory (dict): Mapping of part/serial number tuple to inventory id
        parts_dict (dict): Mapping from part number to part id

    Returns:
        bool: True if runs were successfully created.
    """
    create_mutations = []
    for _, row in df.iterrows():
        procedure_id = row['Procedure (ID)']
        inventory = inventory_dict.get((row["Part number"], row["Serial number"]), {})
        title = row['Run title (leave blank for default format*)']
        if not isinstance(title, str):
            procedure = procedures[procedure_id]
            title = f'{row["Part number"]} - {row["Serial number"]} - {procedure}'
        mutation_input = {'title': title, 'procedureId': procedure_id,
                          'partInventoryId': inventory.get('id', None),
                          'partId': parts[row['Part number']]}
        create_mutations.append(
            {'query': mutations.CREATE_RUN,
             'variables': {'input': mutation_input}})
        # CREATE ABOM TRACE FOR INVENTORY
        if inventory:
            create_mutations.append(
                {'query': mutations.CREATE_ABOM_FOR_PART_INVENTORY,
                'variables': {'id': inventory['id'], 'etag': inventory['_etag']}})
    runs = api.send_api_request(create_mutations)
    for run in runs:
        if 'errors' in run and len(run['errors']) > 0:
            logging.warning(run['errors'][0]['message'])
    return True
Example #11
0
"""Verify connection and authentication with the target API."""

import argparse
from getpass import getpass
import logging
import pandas as pd
import sys
import os
sys.path.append(os.getcwd())
from importers import Api, API_URL

logging.basicConfig(level=logging.INFO,
                    format='[%(asctime)s] - [%(levelname)s] - %(message)s')

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description='Verify connection and authentication with the ion API.')
    parser.add_argument('--client_id', type=str, help='Your API client ID')
    args = parser.parse_args()
    client_secret = getpass('Client secret: ')
    if not args.client_id or not client_secret:
        raise argparse.ArgumentError('Must input client ID and client secret.')
    try:
        api = Api(client_id=args.client_id, client_secret=client_secret)
        print('Successful connection!')
        print(f'API: {API_URL}')
        print(f'Audience: {api.audience}')
        print(f'Client ID: {api.client_id}')
    except KeyError:
        raise ConnectionError('Not able to connect to API.')
Example #12
0
def _bulk_create_parts(api: Api, df: pd.DataFrame, parts: dict) -> bool:
    """
    Batch create parts and MBOM relations for every row in the import excel file.

    For each row representing a part, there is also a depth and quantity column which
    define its MBOM relations. Depth is an int value defining a part as a child to
    another part which is the first instance of the current row's depth - 1.
    Example Rows:
    Depth   Part Number
    1       fr-1
    2       fr-2
    3       fr-3
    3       fr-4
    2       fr-5

    This defines fr-1 as the top level assembly with children fr-2 and fr-5.
    Also fr-3 and fr-4 represent the MBOM for fr-2.

    Args:
        api (Api): API instance to send authenticated requests
        df (pd.DataFrame): Dataframe of excel file passed in arguments
        parts (dict): Mapping from part number to part id

    Returns:
        bool: True if part import was successful.
    """
    mbom_map = {}
    create_mutations = []
    depth = 0
    parent_part_queue = []
    parts_dict = {}
    for idx, row in df.iterrows():
        if row['Part Number'] in parts:
            logging.warning(
                f'Cannot create part {row["Part Number"]} because it already exists.'
            )
        if row['Depth'] > depth:
            parent_part_queue.append(row["Part Number"])
        elif row['Depth'] < depth:
            parent_part_queue = parent_part_queue[:row['Depth'] - depth - 1]
            parent_part_queue.append(row["Part Number"])
        else:
            parent_part_queue[-1] = row["Part Number"]
        if len(parent_part_queue) > 1:
            mbom_map[row['Part Number']] = {
                'parent': parent_part_queue[-2],
                'quantity': row['Quantity']
            }
        depth = row['Depth']
        mutation_input = {
            'partNumber': row['Part Number'],
            'description': row['Description'],
            'trackingType': row['Tracking Level'].upper()
        }
        if row.get('Revision', None) is not None:
            mutation_input['revision'] = row.get('Revision', None)
        create_mutations.append({
            'query': mutations.CREATE_PART,
            'variables': {
                'input': mutation_input
            }
        })
    parts = api.send_api_request(create_mutations)
    for idx, part in enumerate(parts):
        if 'errors' in part and len(part['errors']) > 0:
            logging.warning(part['errors'][0]['message'])
        else:
            p = part['data']['createPart']['part']
            parts_dict[p['partNumber']] = p['id']
    return _create_mbom(api, mbom_map, parts_dict)