├── .github └── workflows │ └── build_and_release.yaml ├── .gitignore ├── Examples ├── BOM_table.py ├── CreateComponent copy.py ├── CreateComponent.py ├── CreateMatrix.py ├── CreateVali.py ├── GetAllProjectRequirementsTree.py ├── GetChildComponents.py ├── ImportComponent_fromCSV.py ├── ModifyValis.py ├── ReqExport_toExcel.py ├── ReqExport_toExcel2.py ├── ReqImport.py ├── ReqImport_WithRelationship.py ├── requirement_position_ordering.py └── update_identifiers.py ├── LICENSE ├── MANIFEST ├── README.md ├── setup.cfg ├── setup.py └── valispace ├── __init__.py └── example.py /.github/workflows/build_and_release.yaml: -------------------------------------------------------------------------------- 1 | #################################################################################### 2 | ## This workflow automates the build and release process for Valispace ## 3 | ## Python API. Triggered on pushes to the master branch, the workflow ## 4 | ## sets up Python environments for multiple versions (3.9), ## 5 | ## displays the Python version, installs Twine for package management, builds ## 6 | ## the project using python setup.py bdist_wheel, and releases the distribution ## 7 | ## files to PyPI using Twine. ## 8 | #################################################################################### 9 | 10 | name: Create new Valispace Python API release 11 | 12 | on: 13 | push: 14 | branches: 15 | - master 16 | 17 | jobs: 18 | pre_checks: 19 | runs-on: ubuntu-latest 20 | 21 | # Define variables to share across jobs 22 | outputs: 23 | current_version: ${{ steps.get_version.outputs.current_version}} 24 | skip_action: ${{ steps.check_setup.outputs.skip_action}} 25 | 26 | steps: 27 | 28 | # Project checkout 29 | - name: Checkout project 30 | uses: actions/checkout@v4 31 | 32 | # Get Valispace Python API version 33 | - name: Get version 34 | id: get_version 35 | run: | 36 | version=$(grep --extended-regexp "version" "setup.py" | awk -F"'" '{print $2}') 37 | echo "current_version=$version" >> "$GITHUB_OUTPUT" 38 | 39 | # Check if version present on master branch is the same as latest tag 40 | - name: Check if setup.py was updated 41 | id: check_setup 42 | run: | 43 | latest_tag=$(git describe --tags --abbrev=0 --always) 44 | current_version=${{ steps.get_version.outputs.current_version }} 45 | 46 | echo "Current version: $current_version" 47 | echo "Latest tag: $latest_tag" 48 | 49 | if [ "$current_version" == "$latest_tag" ]; then 50 | echo "Setup.py version is equal to the latest Git tag. Action will be terminated." 51 | echo "skip_action=True" >> "$GITHUB_OUTPUT" 52 | else 53 | echo "Setup.py version is different from the latest Git tag. Action will continue." 54 | echo "skip_action=False" >> "$GITHUB_OUTPUT" 55 | fi 56 | 57 | shell: bash 58 | 59 | # Execute Python API Build and deploy if version present on master branch is different from latest tag 60 | python_api_build_and_deploy: 61 | needs: pre_checks 62 | if: needs.pre_checks.outputs.skip_action == 'false' 63 | runs-on: ubuntu-latest 64 | strategy: 65 | matrix: 66 | python-version: ["3.9"] 67 | 68 | steps: 69 | 70 | # Project checkout 71 | - name: Checkout project 72 | uses: actions/checkout@v4 73 | 74 | # Use python versions defined on strategy.matrix 75 | - name: Set up Python ${{ matrix.python-version }} 76 | uses: actions/setup-python@v4 77 | with: 78 | python-version: ${{ matrix.python-version }} 79 | 80 | # Print python version currently in use 81 | - name: Display Python version 82 | run: python -c "import sys; print(sys.version)" 83 | 84 | # Install dependecies used to build Valispace Python API 85 | - name: Install Dependencies 86 | run: | 87 | pip install twine 88 | pip install wheel 89 | 90 | # Build Valispace Python API 91 | - name: Build project 92 | run: python setup.py bdist_wheel 93 | 94 | #Upload new version to twine 95 | - name: Release API on PyPi 96 | env: 97 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 98 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 99 | run: twine upload dist/* 100 | 101 | # Create new release using the current version 102 | - name: 'Create new release in GitHub' 103 | id: release 104 | uses: ncipollo/release-action@v1 105 | env: 106 | GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' 107 | with: 108 | commit: '${{ steps.setup.outputs.branch }}' 109 | tag: '${{ needs.pre_checks.outputs.current_version }}' 110 | name: '${{ needs.pre_checks.outputs.current_version }}' 111 | generateReleaseNotes: true 112 | allowUpdates: true 113 | prerelease: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | #MacOS 132 | .DS_Store 133 | 134 | *excel-files 135 | .vscode 136 | *.csv 137 | *.xlsx 138 | *local.py 139 | sync.json 140 | utils/__init__.py 141 | .gitignore 142 | Volta Trucks/microservice/launch.json 143 | AstroscaleIsrael/verification-methods.json 144 | *.pdf 145 | -------------------------------------------------------------------------------- /Examples/BOM_table.py: -------------------------------------------------------------------------------- 1 | # This code generates a csv file with a System breakdown, with each level divided in columns, and the selected properties. 2 | # This Script doesn't work with Matrices yet. 3 | 4 | import valispace 5 | import csv 6 | 7 | deployment = input("Deployment Name:") 8 | valispace = valispace.API(url="https://"+deployment+".valispace.com/") 9 | 10 | 11 | # ID of the main component to start the BOM from. 12 | # Obs.: It can be multiple IDs. E.g.: [100, 110] 13 | MainComponents = [] 14 | 15 | # Name of the generated file containing the BOM 16 | fileName = "BOM.csv" 17 | 18 | 19 | # Headers 20 | # You can rename levels by anythign else. For example: ["System", "Subsystem", "Element"] 21 | # but the number of elements in this list, is the depth you go with the BOM. 22 | Levels = ["Level 1", "Level 2", "Level 3", "Level 4"] 23 | # Headers - Which Vali you want to be added to the BOM. 24 | Valis = ["Mass", "Cost"] 25 | Textvalis = ["Material", "Part_Number"] 26 | fields = Levels + Valis + Textvalis 27 | 28 | 29 | # Function that returns the current vali of a given Vali, return empty if the Vali doesn't exist in the current component 30 | def writeField(componentValis, field, property): 31 | try: 32 | return [obj for obj in componentValis if obj['shortname']==field][0][property] 33 | except: 34 | return "" 35 | 36 | # Function that writes each line of the BOM 37 | def writeLine(component_id, currentLevel): 38 | component = valispace.get("components/"+str(component_id)) 39 | componentValis = valispace.get("valis/?parent="+str(component_id)) 40 | componentTextvalis = getTextValis(component_id) 41 | row = {} 42 | 43 | if currentLevel <= len(Levels): 44 | row[Levels[currentLevel-1]]= component["name"] 45 | 46 | for field in Valis: 47 | row[field] = writeField(componentValis, field, "value") 48 | 49 | for field in Textvalis: 50 | row[field] = writeField(componentTextvalis, field, "text") 51 | 52 | writer.writerow(row) 53 | currentLevel += 1 54 | 55 | def getTextValis(component_id): 56 | textvalis = valispace.get('textvalis/?clean_text=text') 57 | componentTextvalis = [] 58 | for textvali in textvalis: 59 | if textvali['parent'] == component_id: 60 | componentTextvalis.append(textvali) 61 | return componentTextvalis 62 | 63 | 64 | # Recursive function the goes into every child element of the given component. 65 | def getChildAndWriteLine(parent_id, currentLevel): 66 | currentLevel += 1 67 | children_list = valispace.get("components/?parent="+str(parent_id)) 68 | 69 | for child in children_list: 70 | writeLine(child["id"], currentLevel) 71 | if child["children"] != []: 72 | getChildAndWriteLine(child["id"], currentLevel) 73 | 74 | 75 | 76 | with open(fileName, mode='w', encoding='utf-8-sig') as file: 77 | currentLevel = 1 78 | writer = csv.DictWriter(file, fieldnames=fields, lineterminator = '\n') 79 | writer.writeheader() 80 | 81 | for component in MainComponents: 82 | writeLine(component, currentLevel) 83 | getChildAndWriteLine(component, currentLevel) 84 | 85 | print('Export Finished.') 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /Examples/CreateComponent copy.py: -------------------------------------------------------------------------------- 1 | import valispace 2 | 3 | deployment = input("Deployment Name:") 4 | valispace = valispace.API(url="https://"+deployment+".valispace.com/") 5 | 6 | 7 | # The ID of the Parent Component; If it is at the highest level, parent is null, but project need to be specified. 8 | parent_component = 9 | 10 | # Object with the new Component Property 11 | component = { 12 | "name": "NewCompentName", 13 | "parent": parent_component 14 | } 15 | 16 | # Function to get Vali by the fullname 17 | componentPosted = valispace.post("components/", component) 18 | 19 | -------------------------------------------------------------------------------- /Examples/CreateComponent.py: -------------------------------------------------------------------------------- 1 | import valispace 2 | 3 | deployment = input("Deployment Name:") 4 | valispace = valispace.API(url="https://"+deployment+".valispace.com/") 5 | 6 | 7 | # The ID of the Parent Component; If it is at the highest level, parent is null, but project need to be specified. 8 | parent_component = 9 | 10 | # Object with the new Component Property 11 | component = { 12 | "name": "NewCompentName", 13 | "parent": parent_component 14 | } 15 | 16 | # Function to get Vali by the fullname 17 | componentPosted = valispace.post("components/", component) 18 | 19 | -------------------------------------------------------------------------------- /Examples/CreateMatrix.py: -------------------------------------------------------------------------------- 1 | import valispace 2 | 3 | deployment = input("Deployment Name:") 4 | valispace = valispace.API(url="https://"+deployment+".valispace.com/") 5 | 6 | 7 | Matrix = { 8 | "name": "MatrixName5", 9 | "number_of_columns": 2, 10 | "number_of_rows": 2, 11 | "parent": , # ID of Parent Component 12 | "unit": "kg" # Change the base unit for the matrix 13 | } 14 | posted_matrix = valispace.post("matrices/", Matrix) 15 | matrix_id = posted_matrix["id"] 16 | 17 | MatrixValues = [[1, 2], [3, 4]] 18 | for row in posted_matrix["cells"]: 19 | for cell in row: 20 | 21 | Vali = { 22 | "shortname": "vali_"+str(cell), 23 | "formula": MatrixValues[posted_matrix["cells"].index(row)][row.index(cell)], 24 | "parent": matrix_id 25 | } 26 | 27 | patched_vali = valispace.request("patch", "valis/"+str(cell)+"/", Vali) 28 | -------------------------------------------------------------------------------- /Examples/CreateVali.py: -------------------------------------------------------------------------------- 1 | import valispace 2 | 3 | deployment = input("Deployment Name:") 4 | valispace = valispace.API(url="https://"+deployment+".valispace.com/") 5 | 6 | 7 | # The ID of the Component where the Vali will be created 8 | component_id = 9 | 10 | # Object with the new Vali Properties 11 | vali = { 12 | "shortname": "Force", 13 | "formula": 10, 14 | "unit": "N", 15 | "parent": component_id 16 | } 17 | 18 | # Function to get Vali by the fullname 19 | Vali = valispace.post("valis/", vali) 20 | 21 | -------------------------------------------------------------------------------- /Examples/GetAllProjectRequirementsTree.py: -------------------------------------------------------------------------------- 1 | """ 2 | pip install valispace 3 | https://github.com/valispace/ValispacePythonAPI 4 | """ 5 | 6 | import argparse 7 | import pprint 8 | import re 9 | from dataclasses import dataclass, field 10 | from typing import Set 11 | 12 | from valispace import API 13 | 14 | 15 | @dataclass 16 | class ValispaceDataSpecIterationContext: 17 | """ 18 | This data container class is used during the iteration over the downloaded 19 | Valispace requirements (i.e., folders, specs, groups, requirements). 20 | Valispace stores group data and requirements with redundancy. For example: 21 | A spec group typically contains links to its own requirements as well as the 22 | requirements of its child spec groups. Since this script provides the 23 | depth-first iteration, this context class collects the already visited 24 | requirements and spec groups, which are used by the iterator to not yield 25 | these objects more than once. 26 | """ 27 | all_spec_requirements: Set = field(default_factory=set) 28 | all_spec_groups: Set = field(default_factory=set) 29 | 30 | 31 | class ValispaceDataContainer: 32 | """ 33 | This helper class stores the data necessary for a complete iteration over a 34 | project's requirements tree. Additionally, the class provides the iterator 35 | methods. 36 | 37 | # Valispace supports the following entities for structuring the requirements: 38 | # 1) Folders 39 | # 2) Specifications. A specification corresponds to a requirement document. 40 | # A specification can be either contained in a folder or be at the project's 41 | # top-level without being included to any folder. 42 | # 3) Groups. A group corresponds to a chapter in a Specification. A group 43 | # can be only found within a specification. 44 | # 4) Requirements. 45 | # A requirement can only be found within a specification. Within a specification, 46 | # it can be contained within a group or be at the specification's top level. 47 | """ 48 | 49 | def __init__(self, map_id_to_folders, map_id_to_specs, map_id_to_groups, map_id_to_reqs, root_folder): 50 | self.map_id_to_folders = map_id_to_folders 51 | self.map_id_to_specs = map_id_to_specs 52 | self.map_id_to_groups = map_id_to_groups 53 | self.map_id_to_reqs = map_id_to_reqs 54 | self.root_folder = root_folder 55 | 56 | @staticmethod 57 | def sort_folders_by_name_key(folders): 58 | def convert(text): int(text) if text.isdigit() else text.lower() 59 | return list(sorted( 60 | folders, 61 | key=lambda name_: [convert(c) for c in re.split('([0-9]+)', name_["name"])] 62 | )) 63 | 64 | def iterate_folder(self, folder, current_parent_stack=None): 65 | if current_parent_stack is None: 66 | current_parent_stack = [] 67 | assert folder is not None 68 | 69 | # Step 1: Yield the folder itself. 70 | 71 | yield folder, "folder", current_parent_stack 72 | 73 | # Step 2: Yield the child folders. 74 | 75 | new_parent_stack = current_parent_stack + [folder] 76 | 77 | children_ids = folder["children"] 78 | unsorted_children = list(map(lambda ch_id: self.map_id_to_folders[ch_id], children_ids)) 79 | sorted_children = ValispaceDataContainer.sort_folders_by_name_key(unsorted_children) 80 | 81 | for child_folder_ in sorted_children: 82 | yield from self.iterate_folder(child_folder_, new_parent_stack) 83 | 84 | # Step 3: Print the child specifications. 85 | 86 | folder_specs_ids = folder["items"] if "items" in folder else [] 87 | folder_specs = list(map( 88 | lambda folder_id: self.map_id_to_specs[folder_id], folder_specs_ids 89 | )) 90 | for spec_ in folder_specs: 91 | yield from self.iterate_spec(spec_, new_parent_stack) 92 | 93 | def iterate_spec(self, spec, current_parent_stack, context=None): 94 | assert isinstance(current_parent_stack, list) 95 | if context is None: 96 | context = ValispaceDataSpecIterationContext() 97 | 98 | assert isinstance(spec, dict) 99 | 100 | # Step 1: Yield the spec itself. 101 | 102 | yield spec, "spec", current_parent_stack 103 | 104 | # Step 2: Yield the child requirements groups. 105 | 106 | new_parent_stack = current_parent_stack + [spec] 107 | 108 | spec_groups_ids = spec["requirement_groups"] 109 | spec_groups = map( 110 | lambda group_id: self.map_id_to_groups[group_id], spec_groups_ids 111 | ) 112 | 113 | for child_spec_group_ in spec_groups: 114 | if child_spec_group_["id"] in context.all_spec_groups: 115 | continue 116 | yield from self.iterate_spec_group(child_spec_group_, new_parent_stack, context=context) 117 | 118 | # Step 3: Yield the print requirements. 119 | 120 | requirements_ids = spec["requirements"] if "requirements" in spec else [] 121 | requirements = map( 122 | lambda requirement_id: self.map_id_to_reqs[requirement_id], requirements_ids 123 | ) 124 | 125 | for requirement_ in requirements: 126 | if requirement_["id"] in context.all_spec_requirements: 127 | continue 128 | yield requirement_, "requirement", new_parent_stack 129 | context.all_spec_requirements.add(requirement_["id"]) 130 | 131 | def iterate_spec_group(self, spec_group, current_parent_stack, context): 132 | assert isinstance(spec_group, dict) 133 | assert isinstance(context, ValispaceDataSpecIterationContext) 134 | 135 | if context is None: 136 | context = ValispaceDataSpecIterationContext() 137 | 138 | # Step 1: Yield the spec group itself. 139 | 140 | yield spec_group, "spec_group", current_parent_stack 141 | context.all_spec_groups.add(spec_group["id"]) 142 | 143 | # Step 2: Yield the child spec groups. 144 | 145 | new_parent_stack = current_parent_stack + [spec_group] 146 | 147 | spec_groups_ids = spec_group["children"] 148 | 149 | for child_spec_group_ in map( 150 | lambda group_id: self.map_id_to_groups[group_id], spec_groups_ids 151 | ): 152 | if child_spec_group_["id"] in context.all_spec_groups: 153 | continue 154 | yield from self.iterate_spec_group(child_spec_group_, new_parent_stack, context) 155 | 156 | # Step 3: Yield the child requirements. 157 | 158 | requirements_ids = spec_group["requirements"] if "requirements" in spec_group else [] 159 | requirements = map( 160 | lambda requirement_id: self.map_id_to_reqs[requirement_id], requirements_ids 161 | ) 162 | for requirement_ in requirements: 163 | if requirement_["id"] in context.all_spec_requirements: 164 | continue 165 | yield requirement_, "requirement", new_parent_stack 166 | context.all_spec_requirements.add(requirement_["id"]) 167 | 168 | 169 | class ProjectTreeIterator: 170 | """ 171 | This example helper class demonstrates how the iteration methods of the 172 | ValispaceDataContainer class can be used to achieve a complete iteration 173 | over a Valispace project's requirements tree. 174 | 175 | Below in this __init__ function, the iterator can be extended with extra 176 | options for what should be iterated or skipped. The iterator function 177 | can use these options for a customized iteration. 178 | """ 179 | 180 | def iterate(self, container: ValispaceDataContainer): 181 | folders_iterated = 0 182 | specs_iterated = 0 183 | groups_iterated = 0 184 | requirements_iterated = 0 185 | 186 | reqs_so_far = set() 187 | for node, node_type, current_stack in container.iterate_folder(container.root_folder): 188 | print(f"Current node: {node_type} at level: {len(current_stack)} => ", end="") 189 | 190 | if node_type == "folder": 191 | folders_iterated += 1 192 | print(f'Folder: {node["name"]}') 193 | 194 | elif node_type == "spec": 195 | specs_iterated += 1 196 | print(f'Spec: {node["name"]}') 197 | 198 | elif node_type == "spec_group": 199 | groups_iterated += 1 200 | print(f'Spec group: {node["name"]}') 201 | 202 | elif node_type == "requirement": 203 | if node["id"] not in reqs_so_far: 204 | requirements_iterated += 1 205 | reqs_so_far.add(node["id"]) 206 | requirement_uid = node.get("identifier", "") 207 | print(f"Requirement: {requirement_uid}") 208 | else: 209 | raise AssertionError 210 | 211 | print(f"Iteration results:") 212 | print(f"Folders: {folders_iterated}") 213 | print(f"Specs: {specs_iterated}") 214 | print(f"Groups: {groups_iterated}") 215 | print(f"Requirements: {requirements_iterated}") 216 | 217 | assert folders_iterated == len(container.map_id_to_folders.values()), container.map_id_to_folders 218 | assert specs_iterated == len(container.map_id_to_specs.values()), container.map_id_to_specs.values() 219 | assert groups_iterated == len(container.map_id_to_groups.values()), container.map_id_to_groups 220 | assert requirements_iterated == len(container.map_id_to_reqs.values()), container.map_id_to_reqs.values() 221 | 222 | 223 | def main(): 224 | parser = argparse.ArgumentParser(description='Valispace requirements export script.') 225 | parser.add_argument("username", help='Valispace user name.') 226 | parser.add_argument("password", help='Valispace user password.') 227 | parser.add_argument("valispace_url", help='Valispace URL, e.g., https://mycompany.valispace.com.') 228 | parser.add_argument("project_id", help='Valispace project ID.') 229 | args = vars(parser.parse_args()) 230 | 231 | username = args["username"] 232 | password = args["password"] 233 | valispace_url = args["valispace_url"] 234 | project_id = args["project_id"] 235 | 236 | valispace = API(url=valispace_url, username=username, password=password) 237 | 238 | map_id_to_folders = {} 239 | map_id_to_specs = {} 240 | map_id_to_groups = {} 241 | map_id_to_reqs = {} 242 | 243 | print("FOLDERS") 244 | result = valispace.get_folders(project_id) 245 | for entry_ in result: 246 | map_id_to_folders[entry_["id"]] = entry_ 247 | print(f"FOLDERS: {(result)}") 248 | 249 | print("SPECS") 250 | result = valispace.get_specifications(project_id) 251 | for entry_ in result: 252 | map_id_to_specs[entry_["id"]] = entry_ 253 | print(f"SPECS: {len(result)} total") 254 | 255 | print("GROUPS") 256 | result = valispace.get_groups(project_id) 257 | for entry_ in result: 258 | map_id_to_groups[entry_["id"]] = entry_ 259 | print(f"GROUPS: {len(result)} total") 260 | 261 | print("REQS:") 262 | result = valispace.get_requirements(project_id) 263 | for requiremnt in result: 264 | map_id_to_reqs[requiremnt["id"]] = requiremnt 265 | print(f"REQS: {len(result)} total") 266 | 267 | # The Root of the project can also contain specifications that are not 268 | # included to any folder. Create an artificial "ROOT" folder that becomes 269 | # a yet another topmost folder and is after all top-level folders. 270 | 271 | top_level_folders = list(filter( 272 | lambda folder_: folder_["parent"] is None, map_id_to_folders.values() 273 | )) 274 | top_level_folders = ValispaceDataContainer.sort_folders_by_name_key(top_level_folders) 275 | top_level_folders_ids = list(map(lambda tlf_: tlf_["id"], top_level_folders)) 276 | 277 | root_level_specs = filter( 278 | lambda spec: spec["folder"] is None, map_id_to_specs.values() 279 | ) 280 | root_level_specs_ids = list(map( 281 | lambda spec: spec["id"], root_level_specs 282 | )) 283 | root_folder = {"id": "ROOT", "name": "ROOT", "items": root_level_specs_ids, "children": top_level_folders_ids} 284 | map_id_to_folders["ROOT"] = root_folder 285 | 286 | container = ValispaceDataContainer( 287 | map_id_to_folders=map_id_to_folders, 288 | map_id_to_specs=map_id_to_specs, 289 | map_id_to_groups=map_id_to_groups, 290 | map_id_to_reqs=map_id_to_reqs, 291 | root_folder=root_folder, 292 | ) 293 | 294 | project_tree_iterator = ProjectTreeIterator() 295 | project_tree_iterator.iterate(container) 296 | 297 | 298 | main() -------------------------------------------------------------------------------- /Examples/GetChildComponents.py: -------------------------------------------------------------------------------- 1 | # This code returns all child components (ids and names) from a given component in a flat list (no hierarchy) 2 | import valispace 3 | 4 | deployment = input("Deployment Name:") 5 | valispace = valispace.API(url="https://"+deployment+".valispace.com/") 6 | 7 | components_ids = {} 8 | component_id = 9 | 10 | def get_child(parent_id): 11 | children_list = valispace.get("components/?parent="+str(parent_id)) 12 | for child in children_list: 13 | components_ids[child["id"]] = child["name"] 14 | if child["children"] != []: 15 | get_child(child["id"]) 16 | 17 | 18 | get_child(component_id) 19 | print(components_ids) -------------------------------------------------------------------------------- /Examples/ImportComponent_fromCSV.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import valispace 3 | 4 | # in this example "component.csv" should be a table with Name and Parent as headers. 5 | # where Parent is the full path of the component. In the same format you get when 6 | # you export the Components table from Valispace 7 | 8 | valispace = valispace.API(url="https://.valispace.com/", username=None, password=None) 9 | 10 | project_id = 11 | file_name = 'components.csv' 12 | 13 | 14 | # Initialize a dictionary to store component IDs 15 | component_ids = {} 16 | 17 | # Read the CSV file 18 | with open(file_name, 'r') as csvfile: 19 | csvreader = csv.reader(csvfile) 20 | next(csvreader) # Skip the header row 21 | 22 | for row in csvreader: 23 | component_name, parent_path = row 24 | 25 | # Determine the parent ID 26 | parent_id = None 27 | if parent_path: 28 | parent_names = parent_path.split('.') 29 | parent_name = parent_names[-1] # The last name in the path is the immediate parent 30 | parent_id = component_ids.get(parent_name) # Get the parent ID from the dictionary 31 | 32 | if parent_id is None: 33 | print(f"Error: Parent {parent_name} not found for {component_name}") 34 | continue # Skip this row and continue with the next one 35 | 36 | # Create the component 37 | component_data = { 38 | "name": component_name, 39 | "project": project_id, 40 | "parent": parent_id 41 | } 42 | 43 | component_posted = valispace.post("components/", component_data) 44 | 45 | # Save the component ID in the dictionary 46 | component_id = component_posted["id"] 47 | component_ids[component_name] = component_id 48 | -------------------------------------------------------------------------------- /Examples/ModifyValis.py: -------------------------------------------------------------------------------- 1 | import valispace 2 | 3 | deployment = input("Deployment Name:") 4 | valispace = valispace.API(url="https://"+deployment+".valispace.com/") 5 | 6 | # The ID of the Vali that you want to modify 7 | vali_id = 8 | 9 | # New value you want to update the Vali with. 10 | newValue = 11 | 12 | # Function to get Vali by the fullname 13 | Vali = valispace.get_vali(vali_id) 14 | 15 | print("The Vali found is named: "+ str(Vali["name"])) 16 | print("The old value was "+ str(Vali["formula"])+" "+str(Vali["unit"])+". The new value of the Vali will be "+str(newValue)+" "+str(Vali["unit"])) 17 | 18 | # This request function is sending a web request to the server to change the formula of the identified vali with the new value you determine 19 | valispace.request("patch", "valis/"+str(Vali["id"])+"/", data={"formula":newValue}) 20 | 21 | -------------------------------------------------------------------------------- /Examples/ReqExport_toExcel.py: -------------------------------------------------------------------------------- 1 | import valispace 2 | import xlsxwriter 3 | 4 | 5 | username = "" 6 | password = "" 7 | deployment_name = "" 8 | 9 | valispace = valispace.API(url='https://'+deployment_name+'.valispace.com', username=username, password=password) 10 | 11 | 12 | workbook = xlsxwriter.Workbook('OutputFilename.xlsx') 13 | worksheet = workbook.add_worksheet() 14 | 15 | cell_format = workbook.add_format() 16 | cell_format.set_text_wrap() 17 | 18 | 19 | # Project ID 20 | project_ID = 21 | # Specification ID 22 | # If you want to export all Specifications on that project, use 0 as the ID 23 | specification_ID = 0 24 | 25 | # Get Complete List of Requirements with VM - The specification will be filtered in the for loop 26 | requirementList = valispace.get("requirements/complete/?project="+str(project_ID)+"&clean_text=text,comment") 27 | # Get Complete List of Files 28 | filesList = valispace.get("files/?project="+str(project_ID)) 29 | 30 | # Generate Req Mapping - id to identifier 31 | reqMapping = {} 32 | for req in requirementList: 33 | reqMapping[req["id"]] = req["identifier"] 34 | 35 | # Generate Req Mapping - id to identifier 36 | filesMapping = {} 37 | for file in filesList: 38 | # Object Id of a File represents the parent to which the file is attached to 39 | if file["file_type"] == 1 or file["file_type"] == 2: 40 | if file["object_id"] not in filesMapping: 41 | filesMapping[file["object_id"]] = [file["download_url"]] 42 | else: 43 | filesMapping[file["object_id"]].append(file["download_url"]) 44 | # Reference Link to another file 45 | if file["file_type"] == 3: 46 | download_url = [obj for obj in filesList if obj['id']==file["reference_file"]][0]['download_url'] 47 | if file["object_id"] not in filesMapping: 48 | filesMapping[file["object_id"]] = [download_url] 49 | else: 50 | filesMapping[file["object_id"]].append(download_url) 51 | 52 | states = valispace.get('requirements/states') 53 | 54 | # Target File 55 | fields = ["identifier", "title", "text", "specification", "rationale", "state", "parent", "children", "verification method", "components", "status", "attachments"] 56 | 57 | col = 0 58 | for field in fields: 59 | worksheet.write(0, col, field, cell_format) 60 | col += 1 61 | 62 | 63 | row_num = 1 64 | 65 | 66 | for req in requirementList: 67 | row = dict.fromkeys(fields, "") 68 | 69 | if req["specification"] == specification_ID or specification_ID==0 : 70 | row["title"] = req["title"] 71 | row["identifier"] = req["identifier"] 72 | row["text"] = req["text"] 73 | row["specification"] = valispace.get("requirements/specifications/"+str(req["specification"]))["name"] 74 | row["state"] = next((state['name'] for state in states if state['id'] == req["state"]), "") 75 | 76 | if req["comment"] != None: 77 | row["rationale"] = req["comment"] 78 | 79 | # Add File URL if there is any file attached to this requirement 80 | if req["id"] in filesMapping: 81 | for file in filesMapping[req["id"]]: 82 | if row["attachments"] != "" : row["attachments"] += "\n" 83 | row["attachments"] += file 84 | 85 | for child in req["children"]: 86 | try: 87 | if row["children"] != "" : row["children"] += "\n" 88 | row["children"] += "" + reqMapping[child] 89 | except: 90 | print("Error finding child with id: ", child) 91 | for parent in req["parents"]: 92 | try: 93 | if row["parent"] != "" : row["parent"] += "\n" 94 | row["parent"] += "" + reqMapping[parent] 95 | except: 96 | print("Error finding parent with id: ", parent) 97 | 98 | for verification in req["verification_methods"]: 99 | for componentVM in verification["component_vms"]: 100 | 101 | if verification["method"] == None: 102 | if row["verification method"] != "" : row["verification method"] += "\n" 103 | row["verification method"] += "Method Not Defined" 104 | else: 105 | if row["verification method"] != "" : row["verification method"] += "\n" 106 | row["verification method"] += verification["method"]["name"] 107 | 108 | if row["components"] != "" : row["components"] += "\n" 109 | row["components"] += valispace.get("components/"+str(componentVM["component"]))["name"] 110 | 111 | if row["status"] != "" : row["status"] += "\n" 112 | if componentVM["status"] != None: 113 | row["status"] += componentVM["status"] 114 | else: 115 | row["status"] += "No Status" 116 | 117 | col = 0 118 | for field in fields: 119 | worksheet.write(row_num, col, row[field], cell_format) 120 | col += 1 121 | 122 | row_num += 1 123 | 124 | workbook.close() -------------------------------------------------------------------------------- /Examples/ReqExport_toExcel2.py: -------------------------------------------------------------------------------- 1 | import valispace 2 | import xlsxwriter 3 | 4 | # TODO: Option to make verification methods muli-line? 5 | 6 | # START OF CONFIG ------------------------------------- 7 | deployment_name = "" 8 | 9 | fileName = "" 10 | 11 | # Project ID 12 | project_ID = 13 | # Specification ID 14 | # If you want to export all Specifications on that project, use 0 as the ID 15 | specification_ID = 16 | 17 | # For requirements Attachments/Files use 'attachments' 18 | # For Verification Components attachments use 'cvm_attachments' 19 | # Target File - Add or remove any field you want to get exported. 20 | fields = ['identifier', "title", "text", "specification", "rationale", "applicability", "state", "type", "parents", "children", 'verification method', 'components','closeout reference', 'status','verification comment', 'verified on', 'verified by', 'attachments', 'cvm_attachments'] 21 | 22 | 23 | standardSeparator = ', ' 24 | verificationMethodSeparator = "\n" 25 | componentsSeparator = ', ' 26 | 27 | orderReqsBy = 'identifier' 28 | 29 | # END OF CONFIG ------------------------------------- 30 | 31 | # Connect to Valispace 32 | valispace = valispace.API(url='https://'+deployment_name+'.valispace.com') 33 | 34 | # Set up Excel Config 35 | workbook = xlsxwriter.Workbook(fileName+'.xlsx') 36 | worksheet = workbook.add_worksheet() 37 | cell_format = workbook.add_format() 38 | cell_format.set_text_wrap() 39 | 40 | 41 | # Get Complete List of Requirements with VM - The specification will be filtered in the for loop 42 | requirementList = valispace.get("requirements/complete/?project="+str(project_ID)+"&clean_text=text,comment") 43 | requirementList = sorted(requirementList, key = lambda i: i[orderReqsBy]) 44 | applicabilityList = valispace.get("requirements/applicability-conditions/?project="+str(project_ID)) 45 | applicabilityMap_req = {} 46 | for applicability in applicabilityList: 47 | req_id = applicability['requirement'] 48 | if req_id not in applicabilityMap_req: 49 | applicabilityMap_req[req_id] = [applicability] 50 | else: 51 | applicabilityMap_req[req_id].append(applicability) 52 | componentTypeList = valispace.get("components/types/") 53 | componentTypeMap = {compType['id']:compType['name'] for compType in componentTypeList} 54 | 55 | 56 | idMappingName = {} 57 | fileList = None 58 | 59 | if 'attachments' in fields: 60 | # Get Complete List of Files 61 | fileList = valispace.get('files/?project='+str(project_ID)) 62 | idMappingName['attachments'] = fileList 63 | if 'state' in fields: 64 | # Get States 65 | stateList = valispace.get('requirements/states/?project='+str(project_ID)) 66 | idMappingName['state'] = stateList 67 | if 'tags' in fields: 68 | # Get States 69 | tagList = valispace.get('tags/?project='+str(project_ID)) 70 | idMappingName['tags'] = tagList 71 | if 'type' in fields: 72 | # Get Types 73 | typeList = valispace.get('requirements/types/?project='+str(project_ID)) 74 | idMappingName['type'] = typeList 75 | if 'specification' in fields: 76 | # Get Types 77 | specificationList = valispace.get('requirements/specifications/?project='+str(project_ID)) 78 | idMappingName['specification'] = specificationList 79 | if 'section' in fields: 80 | # Get Types 81 | sectionList = valispace.get('requirements/groups/?project='+str(project_ID)) 82 | idMappingName['group'] = sectionList 83 | if 'compliance' or 'custom compliance' in fields: 84 | # Get Types 85 | complianceList = valispace.get('requirements/compliance-statements/?project='+str(project_ID)) 86 | idMappingName['compliance'] = complianceList 87 | if 'components' in fields: 88 | # Get Types 89 | componentsList = valispace.get('components/?project='+str(project_ID)) 90 | idMappingName['components'] = componentsList 91 | if 'closeout reference' in fields: 92 | if fileList is None: fileList = valispace.get('files/?project='+str(project_ID)) 93 | analysisList = valispace.get('analyses/?project='+str(project_ID)) 94 | testList = valispace.get('testing/test-procedures/?project='+str(project_ID)) 95 | testStepsList = valispace.get('testing/test-procedure-steps/?project='+str(project_ID)) 96 | if 'cvm_attachments' in fields: 97 | # Get Complete List of Files 98 | cvm_attachmentList = valispace.get('attachments') 99 | if 'owner' in fields or 'verified by' in fields: 100 | userList = valispace.get('user') 101 | idMappingName['users'] = userList 102 | # Generate Req Mapping - id to identifier 103 | # reqMapping = {} 104 | # for req in requirementList: 105 | # reqMapping[req["id"]] = req["identifier"] 106 | 107 | # Generate Req Mapping - id to identifier 108 | filesMapping = {} 109 | req_to_files = {} 110 | if fileList: 111 | for file in fileList: 112 | # Object Id of a File represents the parent to which the file is attached to 113 | if file["file_type"] == 1 or file["file_type"] == 2: 114 | if file["object_id"] not in filesMapping: 115 | filesMapping[file["object_id"]] = [file["name"]] 116 | else: 117 | filesMapping[file["object_id"]].append(file["name"]) 118 | # Reference Link to another file 119 | if file["file_type"] == 3: 120 | file_name = [obj for obj in fileList if obj['id']==file["reference_file"]][0]['name'] 121 | if file["object_id"] not in filesMapping: 122 | filesMapping[file["object_id"]] = [file_name] 123 | else: 124 | filesMapping[file["object_id"]].append(file_name) 125 | 126 | col = 0 127 | for field in fields: 128 | worksheet.write(0, col, field, cell_format) 129 | col += 1 130 | 131 | 132 | if 'rationale' in fields: 133 | fields[fields.index('rationale')] = 'comment' 134 | if 'section' in fields: 135 | fields[fields.index('section')] = 'group' 136 | if 'compliance' in fields: 137 | fields[fields.index('compliance')] = 'compliance_statement' 138 | if 'compliance comment' in fields: 139 | fields[fields.index('compliance comment')] = 'compliance_comment' 140 | if 'verified on' in fields: 141 | fields[fields.index('verified on')] = 'verified_on' 142 | 143 | 144 | 145 | 146 | 147 | directMappingFields = ['id','identifier', 'title', 'text', 'comment', 'position', 'created', 'updated', 'compliance_statement','compliance_comment'] 148 | directMultipleMappingFields = ['tags'] 149 | idMappingFields = ['specification', 'group', 'type', 'state', ] 150 | idMultipleMappingFields = ['parents', 'children'] 151 | 152 | def main(): 153 | col = 0 154 | row_num = 1 155 | for req in requirementList: 156 | row = dict.fromkeys(fields, "") 157 | 158 | if req["specification"] == specification_ID or specification_ID==0 : 159 | 160 | for field in fields: 161 | if field in directMappingFields: 162 | row[field] = req[field] 163 | 164 | elif field in idMappingFields and req[field] != None: 165 | row[field] = next((object_['name'] for object_ in idMappingName[field] if object_['id'] == req[field]), "") 166 | elif field in idMultipleMappingFields: 167 | output = "" 168 | itemList = req[field] 169 | for item in itemList: 170 | if output != "" : output += standardSeparator 171 | if field in ['parents', 'children']: 172 | output += next((object_['identifier'] for object_ in requirementList if object_['id'] == item), "") 173 | row[field] = output 174 | 175 | elif field in directMultipleMappingFields: 176 | output = "" 177 | itemList = req[field] 178 | for item in itemList: 179 | if output != "" : output += standardSeparator 180 | output += item 181 | row[field] = output 182 | 183 | elif field == 'owner' and req['owner'] != None: 184 | user = next((object_ for object_ in idMappingName['users'] if object_['id'] == req['owner']['id']), "") 185 | if user != '': 186 | if user['first_name'] == '': 187 | row['owner'] += user['username'] 188 | else: 189 | row['owner'] += user['first_name'] + ' ' + user['last_name'] 190 | 191 | elif field == '#verified methods': 192 | row['#verified methods'] = "{:s}/{:s}".format(str(req['verified_items']),str(req['total_items'])) 193 | elif field == '#verified children': 194 | row['#verified children'] = "{:s}/{:s}".format(str(req['verified_children']),str(req['total_children'])) 195 | 196 | elif field == "verification method": 197 | for verification in req["verification_methods"]: 198 | if verification["method"] == None: 199 | if row["verification method"] != "" : row["verification method"] += verificationMethodSeparator 200 | row["verification method"] += "Method Not Defined" 201 | else: 202 | if row["verification method"] != "" : row["verification method"] += verificationMethodSeparator 203 | row["verification method"] += verification["method"]["name"] 204 | 205 | if 'components' in fields: 206 | if row["components"] != "": row["components"] += verificationMethodSeparator 207 | if 'closeout reference' in fields and row["closeout reference"] != "" : row["closeout reference"] += verificationMethodSeparator 208 | if 'cvm_attachments' in fields and row["cvm_attachments"] != "" : row["cvm_attachments"] += verificationMethodSeparator 209 | if 'verified by' in fields and row["verified by"] != "" : row["verified by"] += verificationMethodSeparator 210 | 211 | for componentVM in verification["component_vms"]: 212 | if row["components"] != "" and not row["components"].endswith('\n'): row["components"] += componentsSeparator 213 | row["components"] += next((object_['name'] for object_ in componentsList if object_['id'] == componentVM["component"]), "") 214 | 215 | if 'closeout reference' in fields: 216 | if verification["method"] != None and verification["method"]["name"] == "Rules": 217 | if row["closeout reference"] != "" and not row["closeout reference"].endswith('\n'): row["closeout reference"] += componentsSeparator 218 | row["closeout reference"] += "Rules" 219 | else: 220 | contentType = componentVM['content_type'] 221 | if row["closeout reference"] != "" and not row["closeout reference"].endswith('\n'): row["closeout reference"] += componentsSeparator 222 | 223 | if componentVM['content_type'] == 171: # File 224 | row["closeout reference"] += next((object_['name'] for object_ in fileList if object_['id'] == componentVM['object_id']), "") 225 | elif componentVM['content_type'] == 248: # Test 226 | testStep = next((testStep for testStep in testStepsList if testStep['id'] == componentVM['object_id']), "") 227 | testProcedure = next((testProcedure for testProcedure in testList if testProcedure['id'] == testStep['test_procedure']), "") 228 | row["closeout reference"] += "{:s}: {:s}".format(testStep['title'], testProcedure['name']) 229 | elif componentVM['content_type'] == 82: # Analysis 230 | row["closeout reference"] += next((object_['name'] for object_ in analysisList if object_['id'] == componentVM['object_id']), "") 231 | 232 | if 'cvm_attachments' in fields: 233 | if componentVM["vattachments"]: 234 | if row["cvm_attachments"] != "" and not row["cvm_attachments"].endswith('\n'): row["cvm_attachments"] += componentsSeparator 235 | cvm_attachment = next((attachment for attachment in cvm_attachmentList if attachment['id'] == componentVM["vattachments"][0]),None) 236 | if cvm_attachment['target_type'] == 171: # File 237 | row["cvm_attachments"] += next((object_['name'] for object_ in fileList if object_['id'] == cvm_attachment['target_id']), "") 238 | if cvm_attachment['target_type'] == 82: # Analysis 239 | row["cvm_attachments"] += next((object_['name'] for object_ in analysisList if object_['id'] == cvm_attachment['target_id']), "") 240 | else: 241 | if row["cvm_attachments"] != "" and not row["cvm_attachments"].endswith('\n'): row["cvm_attachments"] += componentsSeparator 242 | 243 | if 'verified by' in fields: 244 | user = next((object_ for object_ in userList if object_['id'] == componentVM["verified_by"]), "") 245 | if row["verified by"] != "" and not row["verified by"].endswith('\n'): row["verified by"] += componentsSeparator 246 | if user != '': 247 | if user['first_name'] == '': 248 | row['verified by'] += user['username'] 249 | else: 250 | row['verified by'] += user['first_name'] + ' ' + user['last_name'] 251 | 252 | 253 | if 'status' in fields: 254 | row["status"] = VMComponentPropertiesDirect(componentVM, row['status'], 'status', "-") 255 | 256 | if 'verification comment' in fields: 257 | row["verification comment"] = VMComponentPropertiesDirect(componentVM, row['verification comment'], 'comment', "") 258 | 259 | if 'custom compliance' in fields: 260 | row["custom compliance"] = VMComponentPropertiesID(componentVM, row['custom compliance'], 'custom_compliance_statement', complianceList, "") 261 | if 'custom compliance comment' in fields: 262 | row["custom compliance comment"] = VMComponentPropertiesDirect(componentVM, row['custom compliance comment'], 'custom_compliance_comment', "") 263 | if 'verification tags' in fields: 264 | if row["verification tags"] != "": row["verification tags"] += verificationMethodSeparator 265 | output = "" 266 | tagList = componentVM['tags'] 267 | for item in tagList: 268 | if output != "" : output += standardSeparator 269 | output += item 270 | row["verification tags"] += output 271 | 272 | elif field == 'attachments' and req['id'] in filesMapping: 273 | row['attachments'] = ', '.join(map(str, filesMapping[req['id']])) 274 | 275 | elif field == 'applicability' and req['id'] in applicabilityMap_req: 276 | reqApplicabilities = applicabilityMap_req[req['id']] 277 | output = "" 278 | for reqApplicability in reqApplicabilities: 279 | componentsApplicable = [componentTypeMap[compType] for compType in reqApplicability['component_types']] 280 | if output != "" : output += standardSeparator 281 | output += "|".join(componentsApplicable) 282 | row['applicability'] = output 283 | 284 | for field in fields: 285 | worksheet.write(row_num, fields.index(field), row[field], cell_format) 286 | 287 | # # Add File URL if there is any file attached to this requirement 288 | # if req["id"] in filesMapping: 289 | # for file in filesMapping[req["id"]]: 290 | # if row["attachments"] != "" : row["attachments"] += "\n" 291 | # row["attachments"] += file 292 | 293 | 294 | 295 | # col = 0 296 | # for field in fields: 297 | # worksheet.write(row_num, col, row[field], cell_format) 298 | # col += 1 299 | 300 | row_num += 1 301 | 302 | workbook.close() 303 | 304 | def VMComponentPropertiesDirect(componentVM, row, field, empty): 305 | if row != "": row += verificationMethodSeparator 306 | if componentVM[field] != None: 307 | row += componentVM[field] 308 | else: 309 | row += empty 310 | return row 311 | 312 | def VMComponentPropertiesID(componentVM, row, field, list, empty): 313 | if row != "": row += verificationMethodSeparator 314 | if componentVM[field] != None: 315 | object_ = next((object_ for object_ in list if object_['id'] == componentVM[field]), "") 316 | row += object_['name'] 317 | else: 318 | row += empty 319 | return row 320 | 321 | 322 | main() 323 | -------------------------------------------------------------------------------- /Examples/ReqImport.py: -------------------------------------------------------------------------------- 1 | import valispace 2 | import csv, json 3 | 4 | 5 | deployment = input("Deployment Name:") 6 | valispace = valispace.API(url="https://"+deployment+".valispace.com/") 7 | 8 | 9 | ## Add File path e.g. - Make sure you use /, not \, to divide folders 10 | csvFilePath = ".../file.csv" 11 | # Id of the specification the requirements should added to 12 | specification_ID = 0 13 | 14 | 15 | def import_req(csvFilePath, specification_ID): 16 | with open(csvFilePath, encoding="utf8") as csvFile: 17 | csvReader = csv.DictReader(csvFile) 18 | for rows in csvReader: 19 | req = { 20 | "specification": specification_ID, 21 | "identifier" : rows['ID'], 22 | "title" : rows['Name'], 23 | "text" : rows['Description'] 24 | } 25 | requirementPosted = valispace.post('requirements/', req) 26 | 27 | 28 | 29 | import_req(csvFilePath, specification_ID) -------------------------------------------------------------------------------- /Examples/ReqImport_WithRelationship.py: -------------------------------------------------------------------------------- 1 | # This script allows you to import all your requirements from an csv file including all the Parent/Child relationship 2 | # To import it to valispace, you just need one end of the relationship, either the Parents pointing to the Children, or the Children pointing to the Parents 3 | # The csv file must have headers and you need to adtapt the code to the respective headers it has. 4 | 5 | import valispace 6 | import csv, json 7 | 8 | 9 | deployment = input("Deployment Name:") 10 | valispace = valispace.API(url="https://"+deployment+".valispace.com/") 11 | 12 | 13 | ## Add File path e.g. - Make sure you use /, not \, to divide folders 14 | csvFilePath = ".../RequirementsFile.csv" 15 | 16 | # Id of the specification the requirements will be added to 17 | specification_ID = 33933 18 | 19 | # How are the Parent/Children are separated in the csv. E.g.: Children: "Req-005, Req-006"; the separator is "," 20 | separator = "," 21 | 22 | # Variable that holds a mapping from the Requirement Identifier to the object ID in Valispace. 23 | ReqMapping = {} 24 | 25 | def import_req(csvFilePath, specification_ID): 26 | 27 | # First run populates Valispace with all requirements, withouth relationship 28 | with open(csvFilePath, encoding="utf8") as csvFile: 29 | csvReader = csv.DictReader(csvFile) 30 | for rows in csvReader: 31 | req = { 32 | "specification": specification_ID, 33 | "identifier" : rows['IDENTIFIER'], # Replace 'IDENTIFIER' with the header title you have on the file where you have the Identifiers 34 | "title" : rows['TITLE'], # Optional: Replace 'TITLE' with the header title you have on the file where you have the Req. Title 35 | "text" : rows['TEXT'] # Replace 'TEXT' with the header title you have on the file where you have the Req. Text 36 | } 37 | requirementPosted = valispace.post('requirements/', req) 38 | ReqMapping[req["identifier"]] = requirementPosted["id"] 39 | 40 | # Second run populates all the relationship - Populating from the Parents to the child 41 | 42 | # If you would like to use a column that defines the parents only, change the child filds 43 | # ToDo: Write a Function for either Parents or Child. 44 | 45 | with open(csvFilePath, encoding="utf8") as csvFile: 46 | csvReader = csv.DictReader(csvFile) 47 | for rows in csvReader: 48 | currentReqId = ReqMapping[rows['IDENTIFIER']] 49 | if rows['CHILDREN'] != "": 50 | children = rows['CHILDREN'].split(separator) # Replace 'CHILDREN' with the header title you have on the file where you have the children of the requirement 51 | for child in children: 52 | childId = ReqMapping[child.lstrip()] 53 | valispace.request('PUT', 'requirements/'+str(currentReqId)+'/add-child/', {"child": childId}) 54 | 55 | 56 | 57 | 58 | import_req(csvFilePath, specification_ID) -------------------------------------------------------------------------------- /Examples/requirement_position_ordering.py: -------------------------------------------------------------------------------- 1 | from valispace import API 2 | import requests 3 | 4 | # Constants for the project ID and Valispace credentials 5 | PROJECT_ID = # Replace with your project ID 6 | VALISPACE = { 7 | 'domain': 'https://.valispace.com/', # Valispace domain URL 8 | 'username': '', # Valispace account username 9 | 'password': '' # Valispace account password 10 | } 11 | 12 | 13 | def main(): 14 | # Initializing the Valispace API with credentials and domain 15 | api = API( 16 | url = VALISPACE.get('domain'), 17 | username = VALISPACE.get('username'), 18 | password = VALISPACE.get('password'), 19 | warn_https = VALISPACE.get('warn_https', False), # Disable HTTPS warnings if necessary 20 | ) 21 | 22 | # Fetching requirements and specifications from the Valispace API 23 | requirements = api.get(f"requirements/?project={PROJECT_ID}") 24 | specifications = api.get(f"requirements/specifications/?project={PROJECT_ID}") 25 | 26 | # Updating position of specifications based on a key identifier 27 | update_position_by_key_within_spec(api, specifications, requirements, key="identifier") 28 | 29 | # Function to update position of specifications based on a given key 30 | def update_position_by_key_within_spec(api, specifications, requirements, key="created"): 31 | bulk_update_payload = {} # Dictionary to store update information 32 | spec_counter = 1 # Counter for specifications 33 | for specification in specifications: 34 | req_counter = 0.1 # Sub-counter for requirements within a specification 35 | # Filtering requirements that belong to the current specification 36 | spec_requirements = [req for req in requirements if req["id"] in specification["requirements"]] 37 | # Sorting requirements based on the specified key 38 | sorted_requirements = sorted(spec_requirements, key=lambda x: x[key]) 39 | for req in sorted_requirements: 40 | # Preparing the update payload 41 | bulk_update_payload[req["id"]] = {"position": spec_counter+req_counter} 42 | req_counter += 0.1 # Incrementing sub-counter 43 | spec_counter += 1 # Incrementing main counter 44 | 45 | try: 46 | # Sending a POST request to update requirements in bulk 47 | response = api.request("POST", "requirements/bulk-update/", bulk_update_payload) 48 | except requests.exceptions.HTTPError as err: 49 | print(f"HTTP Error: {err}") # Printing HTTP error if any 50 | 51 | 52 | # Function to update position of requirements based on a given key 53 | def update_position_by_key(api, requirements, key="created"): 54 | bulk_update_payload = {} # Dictionary to store update information 55 | # Sorting requirements based on the specified key 56 | sorted_requirements = sorted(requirements, key=lambda x: x[key]) 57 | counter = 1 # Counter for requirements 58 | for requirement in sorted_requirements: 59 | # Preparing the update payload 60 | bulk_update_payload[requirement['id']] = {"position": counter} 61 | counter += 1 # Incrementing the counter 62 | 63 | try: 64 | # Sending a POST request to update requirements in bulk 65 | response = api.request("POST", "requirements/bulk-update/", bulk_update_payload) 66 | except requests.exceptions.HTTPError as err: 67 | print(f"HTTP Error: {err}") # Printing HTTP error if any 68 | 69 | 70 | main() -------------------------------------------------------------------------------- /Examples/update_identifiers.py: -------------------------------------------------------------------------------- 1 | import valispace 2 | 3 | """ 4 | This script gets all Specification Abbreviations and rename all requirements (identifiers) with the abbreviation as prefix 5 | plus a numerical indication with a defined number of digits. 6 | The requirements identifier will be ordered by the creation date, but this can be modified in the "sorted" function. 7 | If no abbreviation is defined in a specification, requirements will take REQ as a prefix. 8 | """ 9 | 10 | project_id = PROJECT_ID 11 | valispace_url = "https://.valispace.com/" 12 | 13 | valispace = valispace.API(valispace_url) 14 | 15 | # Get all requirements for the project and create a dictionary mapping requirement IDs to requirements 16 | requirements = valispace.request("GET", "requirements/?project="+str(project_id)) 17 | req_mapping = {req["id"]: req for req in requirements} 18 | 19 | # Get all specifications for the project 20 | specifications = valispace.request("GET", "requirements/specifications/?project="+str(project_id)) 21 | 22 | # Define the number of digits for the identifier and the starting number 23 | num_identifier_digits = 4 24 | start_number = 1 25 | 26 | # Loop through each specification 27 | for spec in specifications: 28 | # Get the abbreviation for the specification. If no abbreviation, uses "REQ" as default 29 | abbreviation = spec.get("abbr", "REQ") 30 | 31 | # Get all requirements for the specification and sort them by creation date 32 | spec_reqs = [req_mapping[req_id] for req_id in spec["requirements"]] 33 | sorted_requirements_by_creation = sorted(spec_reqs, key=lambda x: x['created']) 34 | 35 | # Loop through each requirement and update its identifier 36 | for i, req in enumerate(sorted_requirements_by_creation, start_number): 37 | identifier_number = str(i).zfill(num_identifier_digits) 38 | new_identifier = f"{abbreviation}-{identifier_number}" 39 | 40 | print(new_identifier) 41 | 42 | # Update the identifier for the requirement in Valispace 43 | valispace.request("PATCH", f"requirements/{req['id']}/", {"identifier": new_identifier}) 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Valispace 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.cfg 3 | setup.py 4 | valispace\__init__.py 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Valispace Python API 2 | 3 | The Valispace python API lets you access and update objects in your Valispace deployment. 4 | 5 | ## Getting Started 6 | 7 | To make use of the Valispace API you must have a valid login to any Valispace deployment. If you don't have an account, you can get a demo account at [demo.valispace.com](https://demo.valispace.com). You can also find further documentation in [docs.valispace.com](https://docs.valispace.com/). 8 | 9 | ### Installing 10 | 11 | Install the Valispace python API with pip: 12 | 13 | ```bash 14 | pip install valispace 15 | ``` 16 | 17 | ### Import the API 18 | 19 | Import valispace API module in a python script: 20 | 21 | ```python 22 | import valispace 23 | ``` 24 | 25 | **And initialize with:** 26 | 27 | ```python 28 | valispace = valispace.API() 29 | ``` 30 | 31 | At this step you will need to enter your Valispace url (e.g. ), username and password for authentication, or use the one line function: 32 | 33 | ```python 34 | valispace = valispace.API(url='https://demo.valispace.com', username='your_user_name', password='******') 35 | ``` 36 | Alternative login can also be performed with session_token instead of username and password for authentication 37 | ```python 38 | valispace = valispace.API(url='https://demo.valispace.com', session_token="Bearer your_session_token") 39 | ``` 40 | 41 | 42 | Then use the Valispace API like this: 43 | 44 | ### GET 45 | 46 | A dict of Valis: 47 | 48 | ```python 49 | valis = valispace.get_vali_list() 50 | ``` 51 | 52 | **All Vali** ids and names: 53 | 54 | ```python 55 | all_vali_names = valispace.get_vali_names() 56 | ``` 57 | 58 | A **Vali** with all properties: 59 | 60 | Argument | Example 61 | ------------- | ------------- 62 | id | `valispace.get_vali(id=1)` 63 | unique_name | `valispace.get_vali_by_name(vali_name='Blade', project_name='Fan')` 64 | 65 | A **matrix**: 66 | 67 | ```python 68 | matrix = valispace.get_matrix(id=57) 69 | ``` 70 | 71 | or a condensed version with only Vali ids and values: 72 | 73 | ```python 74 | matrix = valispace.get_matrix_str(id=57) 75 | ``` 76 | 77 | A **Component** with all properties: 78 | 79 | Argument | Example 80 | ------------- | ------------- 81 | id | `valispace.get_component(id=1)` 82 | unique_name | `valispace.get_component_by_name(unique_name='Fan.Blade', project_name='Fan')` 83 | 84 | A **Project** with all properties: 85 | 86 | Argument | Example 87 | ------------- | ------------- 88 | id | `valispace.get_project(id=1)` 89 | name | `valispace.get_project_by_name(name='Fan')` 90 | 91 | ### FILTER 92 | 93 | List of **Valis** with the specified arguments: 94 | 95 | Argument | Example 96 | ------------- | ------------- 97 | workspace_id | `valispace.get_vali_list(workspace_id=1)` 98 | workspace_name | `valispace.get_vali_list(workspace_name='Default Workspace')` 99 | project_id | `valispace.get_vali_list(project_id=1)` 100 | project_name | `valispace.get_vali_list(project_name='Saturn_V')` 101 | parent_id | `valispace.get_vali_list(parent_id=1)` 102 | parent_name | `valispace.get_vali_list(parent_name='Fan')` 103 | tag_id | `valispace.get_vali_list(tag_id=10)` 104 | tag_name | `valispace.get_vali_list(tag_id='example_tag')` 105 | vali_marked_as_impacted | `valispace.get_vali_list(vali_marked_as_impacted='10')` 106 | 107 | List of **Components** with the specified arguments: 108 | 109 | Argument | Example 110 | ------------- | ------------- 111 | workspace_id | `valispace.get_component_list(workspace_id=1)` 112 | workspace_name | `valispace.get_component_list(workspace_name='Default Workspace')` 113 | project_id | `valispace.get_component_list(project_id=1)` 114 | project_name | `valispace.get_component_list(project_name='Fan')` 115 | parent_id | `valispace.get_component_list(parent_id=1)` 116 | parent_name | `valispace.get_component_list(parent_name='Fan')` 117 | tag_id | `valispace.get_component_list(tag_id=10)` 118 | tag_name | `valispace.get_component_list(tag_name='example_tag')` 119 | 120 | List of **Projects** with the specified arguments: 121 | 122 | Argument | Example 123 | ------------- | ------------- 124 | workspace_id | `valispace.get_project_list(workspace_id=1)` 125 | workspace_name | `valispace.get_project_list(workspace_name='Default Workspace')` 126 | 127 | ### UPDATE 128 | 129 | A Vali formula: 130 | 131 | ```python 132 | valispace.update_vali(id=50, formula=str(value + 1)) 133 | ``` 134 | 135 | A matrix: 136 | 137 | ```python 138 | valispace.update_matrix_formulas(id=57, matrix_formula=[[2.1], [0.0], [0.0]]) 139 | ``` 140 | 141 | Creating a dataset: 142 | 143 | ```python 144 | vali_id = 1 145 | 146 | data = [ 147 | [1, 2], 148 | [3, 4], 149 | [5, 6], 150 | ] 151 | 152 | valispace.create_dataset_and_set_values(vali_id, data) 153 | ``` 154 | 155 | ### POST 156 | 157 | ```python 158 | valispace.post_data(type='vali', data=json_object) 159 | ``` 160 | 161 | The input data should be a single JSON object. Check the examples: 162 | 163 | ```python 164 | import valispace 165 | valispace = valispace.API() 166 | 167 | # -- Insert new Component -- 168 | valispace.post_data(type='component', data="""{ 169 | "name": "component_name", 170 | "description": "Insert description here", 171 | "parent": null, 172 | "project": 25, 173 | "tags": [30, 31, 32] 174 | }""") 175 | 176 | # -- Insert new Vali -- 177 | valispace.post_data(type='vali', data="""{ 178 | "parent": 438, 179 | "shortname": "mass", 180 | "description": "", 181 | "unit": "kg", 182 | "formula": "5", 183 | "minimum": null, 184 | "maximum": null, 185 | "margin_minus": "0", 186 | "margin_plus": "0", 187 | "uses_default_formula": false, 188 | "reference": "", 189 | "type": null 190 | }""") 191 | 192 | # -- Insert new Textvali -- 193 | valispace.post_data(type='textvali', data="""{ 194 | "shortname": "Message", 195 | "text": "Message text", 196 | "parent": 438 197 | }""") 198 | 199 | # -- Insert new Tag -- 200 | valispace.post_data(type='tag', data="""{ 201 | "name": "white-tag", 202 | "color": "#FFFFFF" 203 | }""") 204 | 205 | # -- Create / update dataset -- 206 | valispace.vali_import_dataset(vali_id, [ 207 | [435, 5], 208 | [67567, 34], 209 | [89564, 567830], 210 | [2345, 5687], 211 | [2345, 678], 212 | ]) 213 | ``` 214 | 215 | **Notes:** 216 | 217 | - The "name" fields should never be repeated, this will result in a error in the REST API. 218 | - The "valis" are automatically updated when new valis with this componenet id are inserted 219 | 220 | ### RAW POST / GET 221 | 222 | You can make any custom requests not covered by a specific method using *request*. 223 | 224 | Argument | Example 225 | ------------- | ------------- 226 | method | `'GET', 'POST', 'DELETE'` 227 | url | `'vali/1` 228 | data *(optional)* | *{"name": "example"}* 229 | 230 | ```python 231 | try: 232 | v = valispace.API('http://demo.valispace.com/', 'user', 'pass') 233 | projects = v.request('GET', 'project/') 234 | except: 235 | print('Error') 236 | ``` 237 | 238 | As a shortcut if the REST method is GET you can use the *get(url, data)* function, or if it's a POST request use *post(url, data)*. 239 | 240 | 243 | 244 | ## Authors 245 | 246 | - **Valispace** 247 | 248 | See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. 249 | 250 | ## License 251 | 252 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 253 | 254 | 259 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='valispace', 5 | packages=find_packages(exclude=['contrib', 'docs', 'tests*']), 6 | install_requires=['requests', 'six'], 7 | version='0.1.24', 8 | description='Valispace Python API', 9 | author='Valispace', 10 | author_email='support@valispace.com', 11 | license='MIT', 12 | url='https://github.com/valispace/ValispacePythonAPI', 13 | keywords='hardware engineering satellites rockets space technology', 14 | classifiers=[], 15 | ) 16 | -------------------------------------------------------------------------------- /valispace/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | import getpass 6 | import json 7 | import requests 8 | import sys 9 | import six 10 | import re 11 | 12 | 13 | class API: 14 | """ 15 | Defines REST API endpoints for Valispace. Note that the core methods for 16 | sending requests are get() and post(). Many other methods are convenience 17 | methods that call into get/post. If you are missing a particular convenience 18 | method, it should be easy to implement one following the same convention as 19 | the other methods do. 20 | 21 | If you are missing a particular endpoint, the REST documentation is available 22 | on the Valispace deployment as follows: 23 | https://.valispace.com/rest/ 24 | """ 25 | 26 | _writable_vali_fields = [ 27 | 'reference', 'margin_plus', 'margin_minus', 'unit', 28 | 'formula', 'description', 'parent', 'tags', 'shortname', 29 | 'minimum', 'maximum', 30 | ] 31 | offline_assistant = False 32 | 33 | 34 | def __init__( 35 | self, url=None, 36 | username=None, 37 | password=None, 38 | keep_credentials=False, 39 | warn_https=True, 40 | session_token=None, 41 | ): 42 | print("\nAuthenticating Valispace...\n") 43 | if url is None: 44 | url = six.moves.input('Your Valispace url: ') 45 | 46 | url = url.strip().rstrip("/") 47 | 48 | if not (url.startswith('http://') or url.startswith('https://')): 49 | url = 'https://' + url 50 | 51 | # Check for SSL connection before sending the username and password. 52 | if warn_https and url[:5] != "https": 53 | sys.stdout.write("Are you sure you want to use a non-SSL connection? " 54 | "This will expose your password to the network and might be a significant security risk [y/n]: ") 55 | while True: 56 | choice = six.moves.input().lower() 57 | if choice == "y": 58 | break 59 | if choice == "n": 60 | return 61 | print("Please answer 'y' or 'n'") 62 | 63 | self._url = url + '/rest/' 64 | self._oauth_url = url + '/o/token/' 65 | self._session = requests.Session() 66 | self.username, self.password = None, None 67 | if session_token: 68 | self._session.headers['Authorization'] = session_token 69 | print("You are currently using the provided Token") 70 | elif self.login(username, password): 71 | if keep_credentials: 72 | self.username, self.password = username, password 73 | print("You have been successfully connected to the {} API.".format(self._url)) 74 | self.get_configs() 75 | 76 | 77 | 78 | def login(self, username=None, password=None): 79 | """ 80 | Performs the password-based oAuth 2.0 login for read/write access. 81 | """ 82 | # clear out old auth headers 83 | self._session.headers = {} 84 | if username is None: 85 | username = six.moves.input('Username: ').strip() 86 | if password is None: 87 | password = getpass.getpass('Password: ').strip() 88 | 89 | try: 90 | client_id = "ValispaceREST" # registered client-id in Valispace Deployment 91 | response = self._session.post(self._oauth_url, data={ 92 | 'grant_type': 'password', 93 | 'username': username, 94 | 'password': password, 95 | 'client_id': client_id, 96 | }) 97 | except requests.exceptions.RequestException as e: 98 | raise Exception("VALISPACE-ERROR: " + str(e)) 99 | except: 100 | # TODO: 101 | # T: Capture specific exceptions, bc it is also possible that 102 | # the endpoint does not work or something like that... 103 | raise Exception("VALISPACE-ERROR: Invalid credentials or url.") 104 | 105 | response.raise_for_status() 106 | 107 | json = response.json() 108 | 109 | if 'error' in json and json['error'] != None: 110 | if 'error_description' in json: 111 | raise Exception("VALISPACE-ERROR: " + json['error_description']) 112 | else: 113 | raise Exception("VALISPACE-ERROR: " + json['error']) 114 | return 115 | 116 | access = "Bearer " + json['access_token'] 117 | self._session.headers = { 118 | 'Authorization': access, 119 | 'Content-Type': 'application/json', 120 | 'User-Agent': 'valispacePythonSDK/0.1.16' 121 | } 122 | return True 123 | 124 | 125 | def get_all_data(self, type=None): 126 | """ 127 | Returns a dict of all component/vali/textvali/tags with their properties. 128 | """ 129 | # Check if valid argument was passed. 130 | if type not in ('component', 'vali', 'textvali', 'tag'): 131 | raise Exception("VALISPACE-ERROR: Type argument expected (component/vali/textvali/tags)") 132 | 133 | if type in ('component', 'vali', 'textvali'): 134 | url = type + 's/' # add an s to the end to get to the right endpoint 135 | else: 136 | url = type + '/' 137 | 138 | get_data = self.get(url) 139 | 140 | return_dictionary = {} 141 | for data in get_data: 142 | return_dictionary[str(data["id"])] = data 143 | 144 | return return_dictionary 145 | 146 | def get_configs(self): 147 | """ 148 | Get the configurations of the deployment for Vali Assistant 149 | """ 150 | response = self.get("ui/angular-settings/") 151 | assistant_settings = response.get("vali_assistant") 152 | self.offline_assistant = assistant_settings.get("offline_assistant", False) 153 | 154 | def get_folders(self, project_id): 155 | return self.get(f"requirements/specifications/folders?project={project_id}") 156 | 157 | def get_specifications(self, project_id): 158 | return self.get(f"requirements/specifications?project={project_id}") 159 | 160 | def get_groups(self, project_id): 161 | return self.get(f"requirements/groups?project={project_id}") 162 | 163 | def get_requirements(self, project_id): 164 | return self.get(f"requirements/?project={project_id}") 165 | 166 | def get_vali_list(self, workspace_id=None, workspace_name=None, project_id=None, project_name=None, parent_id=None, 167 | parent_name=None, tag_id=None, tag_name=None, vali_marked_as_impacted=None): 168 | """ 169 | Returns JSON with all the Valis that mach the input arguments. 170 | Inputs are integers for IDs or vali_marked_as_impacted strings, and strings for names. 171 | Use the component 'unique_name' (not the 'name') in the parent_name argument. 172 | """ 173 | if workspace_id: 174 | try: 175 | workspace_id = int(workspace_id) 176 | except: 177 | raise Exception("VALISPACE-ERROR: Workspace id must be an integer.") 178 | 179 | if project_id: 180 | try: 181 | project_id = int(project_id) 182 | except: 183 | raise Exception("VALISPACE-ERROR: Project id must be an integer") 184 | 185 | if parent_id: 186 | try: 187 | parent_id = int(parent_id) 188 | except: 189 | raise Exception("VALISPACE-ERROR: Parent id must be an integer") 190 | 191 | if tag_id: 192 | try: 193 | tag_id = int(tag_id) 194 | except: 195 | raise Exception("VALISPACE-ERROR: Tag id must be an integer") 196 | 197 | if vali_marked_as_impacted: 198 | try: 199 | vali_marked_as_impacted = int(vali_marked_as_impacted) 200 | except: 201 | raise Exception("VALISPACE-ERROR: Vali_marked_as_impacted must be an integer") 202 | 203 | # Construct URL. 204 | url = "valis/?" 205 | if workspace_id: 206 | url += "parent__project__workspace={}".format(workspace_id) 207 | if workspace_name: 208 | url = self.__increment_url(url) + "parent__project__workspace__name={}".format(workspace_name) 209 | if project_id: 210 | url = self.__increment_url(url) + "project={}".format(project_id) 211 | if project_name: 212 | project = self.get_project_by_name(project_name) 213 | url = self.__increment_url(url) + "project={}".format(project[0]['id']) 214 | if parent_id: 215 | url = self.__increment_url(url) + "parent={}".format(parent_id) 216 | if parent_name: 217 | url = self.__increment_url(url) + "parent__unique_name={}".format(parent_name) 218 | if tag_id: 219 | url = self.__increment_url(url) + "tags__id={}".format(tag_id) 220 | if tag_name: 221 | url = self.__increment_url(url) + "tags__name={}".format(tag_name) 222 | if vali_marked_as_impacted: 223 | url = self.__increment_url(url) + "valis_marked_as_impacted={}".format(vali_marked_as_impacted) 224 | 225 | try: 226 | return self.get(url) 227 | except Exception as e: 228 | print('Something went wrong with the request. Details: {}'.format(e)) 229 | 230 | # if response.status_code != 200: 231 | # print('Response:', response) 232 | # print('Status code:', response.status_code) 233 | # print('Text:', response.text) 234 | # return None 235 | # else: 236 | # return response.json() 237 | 238 | 239 | def get_vali_names(self, project_name=None): 240 | """ 241 | Returns a list of all Valis with only names and IDs. 242 | :returns: JSON object. 243 | """ 244 | if project_name: 245 | project = self.get_project_by_name(project_name) 246 | if project: 247 | project = project[0] 248 | return self.get("valis/?fields=id,name&_project={}".format(project["id"])) 249 | else: 250 | return None 251 | else: 252 | return self.get("valis/?fields=id,name") 253 | 254 | 255 | def get_vali(self, id): 256 | """ 257 | Returns JSON of a unique Vali. 258 | :param id: ID of the Vali to fetch. 259 | :returns: JSON object. 260 | """ 261 | if type(id) != int: 262 | raise Exception("VALISPACE-ERROR: The function requires an ID (int) as parameter.") 263 | return self.get("valis/{}/".format(id)) 264 | 265 | 266 | def get_vali_by_name(self, vali_name, project_name): 267 | """ 268 | Returns JSON of a unique Vali. 269 | :param vali_name: unique name of the vali. 270 | :returns: JSON object. 271 | """ 272 | if type(vali_name) != str: 273 | raise Exception("VALISPACE-ERROR: The function requires a valid Vali name (str) as parameter.") 274 | 275 | if type(project_name) != str: 276 | raise Exception("VALISPACE-ERROR: The function requires a valid Project name (str) as parameter.") 277 | 278 | valinames = self.get_vali_names() 279 | for entry in valinames: 280 | if entry['name'] == vali_name: 281 | return self.get("valis/{}/".format(entry["id"])) 282 | 283 | raise Exception("VALISPACE-ERROR: There is no Vali with this name and project, make sure you admit a " 284 | "valid full name for the vali (e.g. ComponentX.TestVali) and a valid project name.") 285 | 286 | 287 | def fuzzysearch_vali(self, searchterm): 288 | """ 289 | Returns JSON of a unique Vali given a similar name 290 | :param searchterm: (not necessarily exact) name of vali 291 | :returns: JSON object. 292 | """ 293 | if type(searchterm) != str: 294 | raise Exception("VALISPACE-ERROR: The function requires a string as parameter.") 295 | 296 | url = "fuzzysearch/Vali/name/{}/".format(searchterm) 297 | result = self.get(url, allow_redirects=True) 298 | 299 | if result == {}: 300 | raise Exception("VALISPACE-ERROR: Could not find a matching vali for {}".format(searchterm)) 301 | else: 302 | return result 303 | 304 | 305 | def get_vali_value(self, id): 306 | """ 307 | Returns the value of a vali. 308 | :param id: ID of Vali. 309 | :returns: int. 310 | """ 311 | try: 312 | vali = self.get_vali(id) 313 | return vali["value"] 314 | except: 315 | raise Exception("VALISPACE-ERROR: Could not retrieve Vali value.") 316 | 317 | # def create_vali(parent=None, type=None, shortname=None, description=None, formula=None, margin_plus=None, 318 | # margin_minus=None, minimum=None, maximum=None, reference=None, tags=None, data={}): 319 | # """ 320 | # Creates a new Vali. 321 | # """ 322 | # # TBD... 323 | 324 | 325 | def update_vali(self, id, shortname=None, formula=None, data=None): 326 | """ 327 | Finds the Vali that corresponds to the input id 328 | and updates it with the input shortname, formula and/or data. 329 | """ 330 | if data == None : 331 | data = {} 332 | elif type(data) != dict: 333 | raise Exception('VALISPACE-ERROR: data needs to be a dictionary. To update formula / value use "formula"') 334 | 335 | if not id: 336 | raise Exception("VALISPACE-ERROR: You need to pass an ID.") 337 | 338 | if not shortname and not formula and not data: 339 | raise Exception("VALISPACE-ERROR: You have to pass data to update.") 340 | 341 | # Write Vali. 342 | if shortname: 343 | data["shortname"] = shortname 344 | if formula: 345 | data["formula"] = formula 346 | if not data: 347 | raise Exception("You have not entered any valid fields. Here is a list of all fields \ 348 | that can be updated:\n{}.".format(", ".join(self._writable_vali_fields))) 349 | url = "valis/{}/".format(id) 350 | return self.request('PATCH', url, data=data) 351 | 352 | 353 | def impact_analysis(self, id, target_vali_id, range_from, range_to, range_step_size): 354 | data = {} 355 | 356 | if not id: 357 | raise Exception("VALISPACE-ERROR: You need to pass an ID.") 358 | 359 | url = "valis/{}/impact-analysis-graph-for/{}/?range_min={}&range_max={}&range_step_size={}".format(id, target_vali_id, range_from, range_to, range_step_size) 360 | # FIXME: (patrickyeon) I special-cased this because there's some 361 | # printing of returned values on error, but I suspect 362 | # that is really better handled by the normal error- 363 | # handling path and getting rid of these print()s 364 | print(url) 365 | result = self._session.get(self._url + url, data=data) 366 | if result.status_code != 200: 367 | print(result.text) 368 | raise Exception("Invalid Request.") 369 | return json.loads(result.text) 370 | 371 | 372 | def what_if(self, vali_name, target_name, value): 373 | if not id or not target_name or not value: 374 | raise Exception("VALISPACE-ERROR: You need to pass an ID.") 375 | 376 | url = "alexa_what_if/{}/{}/{}/".format(vali_name, target_name, value) 377 | # FIXME: (patrickyeon) same comment as on impact_analysis() 378 | print(url) 379 | result = self._session.get(self._url + url) 380 | if result.status_code != 200: 381 | print(result.text) 382 | raise Exception("Invalid Request.") 383 | return json.loads(result.text) 384 | 385 | 386 | def get_component_list( 387 | self, 388 | workspace_id=None, 389 | workspace_name=None, 390 | project_id=None, 391 | project_name=None, 392 | parent_id=None, 393 | parent_name=None, 394 | tag_id=None, 395 | tag_name=None, 396 | ): 397 | """ 398 | Returns JSON with all the Components that match the input arguments. 399 | Inputs are integers for IDs and strings for names. 400 | Use the component 'unique_name' (not the 'name') in the parent_name argument. 401 | :returns: JSON object. 402 | """ 403 | if workspace_id: 404 | try: 405 | workspace_id = int(workspace_id) 406 | except: 407 | raise Exception("VALISPACE-ERROR: Workspace id must be an integer.") 408 | if project_id: 409 | try: 410 | project_id = int(project_id) 411 | except: 412 | raise Exception("VALISPACE-ERROR: Project id must be an integer.") 413 | if parent_id: 414 | try: 415 | parent_id = int(parent_id) 416 | except: 417 | raise Exception("VALISPACE-ERROR: Parent id must be an integer.") 418 | if tag_id: 419 | try: 420 | tag_id = int(tag_id) 421 | except: 422 | raise Exception("VALISPACE-ERROR: Tag id must be an integer.") 423 | 424 | # Construct URL. 425 | url = "components/?" 426 | if workspace_id: 427 | url += "project__workspace={}".format(workspace_id) 428 | elif workspace_name: 429 | url = self.__increment_url(url) + "workspace__name={}".format(workspace_name) 430 | elif project_id: 431 | url = self.__increment_url(url) + "project={}".format(project_id) 432 | elif project_name: 433 | url = self.__increment_url(url) + "project__name={}".format(project_name) 434 | elif parent_id: 435 | url = self.__increment_url(url) + "parent={}".format(parent_id) 436 | elif parent_name: 437 | url = self.__increment_url(url) + "parent__unique_name={}".format(parent_name) 438 | elif tag_id: 439 | url = self.__increment_url(url) + "tags__id={}".format(tag_id) 440 | elif tag_name: 441 | url = self.__increment_url(url) + "tags__name={}".format(tag_name) 442 | 443 | return self.get(url) 444 | 445 | 446 | def get_component(self, id): 447 | """ 448 | Returns JSON of a unique Component. 449 | :param id: ID of the component to fetch. 450 | :returns: JSON object. 451 | """ 452 | if type(id) != int: 453 | raise Exception("VALISPACE-ERROR: The function requires an id (int) as argument.") 454 | 455 | return self.get("components/{}/".format(id)) 456 | 457 | 458 | def get_component_by_name(self, unique_name, project_name): 459 | """ 460 | Returns JSON of a unique Component. 461 | :param name: unique name of the component to fetch. 462 | :returns: JSON object. 463 | """ 464 | if type(unique_name) != str: 465 | raise Exception("VALISPACE-ERROR: The function requires a component unique name (str) as argument.") 466 | 467 | if type(project_name) != str: 468 | raise Exception("VALISPACE-ERROR: The function requires a valid Project name (str) as argument.") 469 | 470 | project = self.get_project_by_name(name=project_name) 471 | results = [] 472 | component_list = self.get_component_list(project_name=project_name) 473 | for entry in component_list: 474 | if entry['unique_name'] == unique_name: 475 | results.append(entry) 476 | #return self.get("valis/{}/".format(entry["id"])) 477 | num_results = len(results) 478 | 479 | if num_results == 1: 480 | return results[0] 481 | if num_results == 0: 482 | raise Exception("VALISPACE-ERROR: A Component with this name does not exist. Please check for typos.") 483 | else: 484 | raise Exception("VALISPACE-ERROR: The name you admitted is ambiguous, are you sure you used the Component's full name?") 485 | 486 | 487 | def get_project_list(self, workspace_id=None, workspace_name=None): 488 | """ 489 | Returns JSON with all the Projects that mach the input arguments. 490 | Inputs are integers for IDs and strings for names. 491 | :returns: JSON object. 492 | """ 493 | # Construct URL. 494 | url = "project/?" 495 | if workspace_id: 496 | if type(workspace_id) != int: 497 | raise Exception("VALISPACE-ERROR: workspace_id must be an integer.") 498 | url += "workspace={}".format(workspace_id) 499 | elif workspace_name: 500 | if type(workspace_name) != str: 501 | raise Exception("VALISPACE-ERROR: workspace_name must be a string.") 502 | url = self.__increment_url(url) + "workspace__name={}".format(workspace_name) 503 | return self.get(url) 504 | 505 | 506 | def get_project(self, id): 507 | """ 508 | Retrieve a Project via ID. 509 | :param id: ID of the project. 510 | :returns: JSON object. 511 | """ 512 | if type(id) != int: 513 | raise Exception("VALISPACE-ERROR: The function requires an id (int) as argument.") 514 | return self.get("project/{}/".format(id)) 515 | 516 | 517 | def get_project_by_name(self, name): 518 | """ 519 | Retrieve a Project via name. 520 | :param name: name of the project (unique). 521 | :returns: JSON object. 522 | """ 523 | if type(name) != str: 524 | raise Exception("VALISPACE-ERROR: The function requires a valid project name (str) as argument.") 525 | 526 | # Construct URL. 527 | url = "project/?name={}".format(name) 528 | response = self.get(url) 529 | if len(response) == 0: 530 | raise Exception("VALISPACE-ERROR: A Project with this name does not exist. Please check for typos.") 531 | else: 532 | return response 533 | 534 | 535 | def post_data(self, type=None, data=None): 536 | """ 537 | Post new component/vali/textvali/tags with the input data 538 | Data is expected to be a JSON string with some required fields like name. 539 | :param type: type of object to create/update. 540 | :param data: dict with key value pairs for the object attributes. 541 | :returns: JSON object. 542 | """ 543 | # Check if no argument was passed 544 | if data is None: 545 | data = {} 546 | if type not in ('component', 'vali', 'textvali', 'tag'): 547 | raise Exception("VALISPACE-ERROR: Type argument expected (component/vali/textvali/tags).") 548 | 549 | if type in ('component', 'vali', 'textvali'): 550 | url = type + 's/' # Add an s to the end of the type for component, vali and textvali to get to the correct endpoint 551 | else: 552 | url = type + '/' 553 | 554 | # FIXME: (patrickyeon) special-casing this, but maybe this whole 555 | # method is not required now that post() exists? 556 | result = self._session.post(self._url + url, data=data) 557 | 558 | if result.status_code == 201: 559 | print("Successfully updated Vali:\n" + str(data) + "\n") 560 | elif result.status_code == 204: 561 | raise Exception( 562 | "The server successfully processed the request, but is not " 563 | "returning any content (status code: 204)\n" 564 | ) 565 | elif result.status_code == 500: 566 | raise Exception( 567 | "The server encountered an unexpected condition which " 568 | "prevented it from fulfilling the request (status code: 500)\n" 569 | ) 570 | else: 571 | raise Exception("Invalid Request (status code: {}): {}\n".format(result.status_code, result.content)) 572 | 573 | return result.json() 574 | 575 | 576 | def post(self, url, data=None, **kwargs): 577 | """ 578 | Posts data 579 | :param url: the relative url 580 | :param data: the data 581 | :param **kwargs: additional args passed to the request call 582 | :returns: JSON object. 583 | """ 584 | 585 | return self.request('POST', url, data, **kwargs) 586 | 587 | 588 | def get(self, url, data=None, **kwargs): 589 | """ 590 | Posts data 591 | :param url: the relative url 592 | :param data: the data 593 | :param **kwargs: additional args passed to the request call 594 | :returns: JSON object. 595 | """ 596 | 597 | return self.request('GET', url, data, **kwargs) 598 | 599 | 600 | def request(self, method, url, data=None, **kwargs): 601 | """ 602 | Generic request data 603 | :param method: the method 604 | :param url: the relative url 605 | :param data: the data 606 | :param **kwargs: additional args passed to the request call 607 | :returns: JSON object. 608 | """ 609 | 610 | url = self._url + url 611 | result = self._session.request(method, url, json=data, **kwargs) 612 | 613 | if result.status_code == 401: 614 | # authentication expired 615 | if self.username is None: 616 | print("Authentication expired, please re-login") 617 | # otherwise, we've got it saved and this is transparent 618 | self.login(self.username, self.password) 619 | # try the request one more time 620 | result = self._session.request(method, url, json=data, **kwargs) 621 | 622 | result.raise_for_status() 623 | return result.json() 624 | 625 | 626 | def get_matrix(self, id): 627 | """ 628 | Returns the correct Matrix. 629 | :param id: ID of Matrix. 630 | :returns: list of lists. 631 | """ 632 | url = "matrices/{}/".format(id) 633 | matrix_data = self.get(url) 634 | try: 635 | # TODO: 636 | # T: there is probably a faster and more efficient way... 637 | matrix = [] 638 | for row in range(matrix_data['number_of_rows']): 639 | matrix.append([]) 640 | for col in range(matrix_data['number_of_columns']): 641 | matrix[row].append(self.get_vali(matrix_data['cells'][row][col])) 642 | return matrix 643 | except KeyError: 644 | raise Exception("VALISPACE-ERROR: Matrix with id {} not found.".format(id)) 645 | except: 646 | raise Exception("VALISPACE-ERROR: Unknown error.") 647 | 648 | 649 | def get_matrix_str(self, id): 650 | """ 651 | Returns the correct Matrix. 652 | :param id: ID of Matrix. 653 | :returns: list of lists. 654 | """ 655 | url = "matrices/{}/".format(id) 656 | matrix_data = self.get(url) 657 | try: 658 | matrix = [] 659 | for row in range(matrix_data['number_of_rows']): 660 | matrix.append([]) 661 | for col in range(matrix_data['number_of_columns']): 662 | matrix[row].append({ 663 | "vali": matrix_data['cells'][row][col], 664 | "value": self.get_vali(matrix_data['cells'][row][col])["value"], 665 | }) 666 | return matrix 667 | except KeyError: 668 | raise Exception("VALISPACE-ERROR: Matrix with id {} not found.".format(id)) 669 | except: 670 | raise Exception("VALISPACE-ERROR: Unknown error.") 671 | 672 | def update_matrix_formulas(self, id, matrix_formula): 673 | """ 674 | Finds the Matrix that corresponds to the input id, 675 | Finds each of the Valis that correspond to the vali id (contained in each cell of the matrix) 676 | Updates the formula of each of the Valis with the formulas contained in each cell of the input matrix. 677 | """ 678 | # Read Matrix. 679 | url = "matrices/{}/".format(id) 680 | matrix_data = self.get(url) 681 | 682 | # Check matrix dimensions. 683 | if not len(matrix_formula) == matrix_data["number_of_rows"] and len(matrix_formula[0]) == matrix_data["number_of_columns"]: 684 | raise Exception('VALISPACE-ERROR: The dimensions of the local and the remote matrix do not match.') 685 | 686 | # Update referenced valis in each matrix cell 687 | for row in range(matrix_data['number_of_rows']): 688 | for col in range(matrix_data['number_of_columns']): 689 | self.update_vali(id=matrix_data['cells'][row][col], formula=matrix_formula[row][col]) 690 | 691 | # Increment function to add multiple fields to url 692 | def __increment_url(self, url): 693 | # TODO: 694 | # T: Replace this with a proper query param function... 695 | if not url.endswith('?'): 696 | url += "&" 697 | return url 698 | 699 | 700 | def vali_create_dataset(self, vali_id): 701 | """ 702 | Creates a new dataset in vali. 703 | :param vali_id: Id of the vali where we want to create the dataset. 704 | :returns: New datset id. 705 | """ 706 | url = 'rest/valis/' + vali_id + '/convert-to-dataset/' 707 | 708 | data = {} 709 | return self.post(url, data) 710 | 711 | 712 | def create_dataset_and_set_values(self, vali_id, input_data): 713 | """ 714 | Sets a dataset. 715 | :param vali_id: Id of the vali where we want to create the dataset. 716 | :param data: Data, in the format of [[x0, y0...], [x1, y1...], ...] 717 | """ 718 | if type(input_data) != list: 719 | raise Exception('input_data must be an array') 720 | 721 | url = 'datasets/' 722 | data = { 723 | "vali": vali_id 724 | } 725 | try: 726 | response = self.post(url, data) 727 | except: 728 | raise Exception("VALISPACE ERROR: Is the vali_id valid?") 729 | 730 | dataset_id = response['id'] 731 | variable_id = response['points'][0]['variables'][0]['id'] 732 | point_id = response['points'][0]['id'] 733 | 734 | s = 0 735 | 736 | for d in input_data: 737 | if s != 0: 738 | if len(d) != s: 739 | raise Exception("Data members with inconsistent length. Found {}, expected {}.".format(len(d), s)) 740 | url = 'vali/functions/datasets/points/' 741 | data = { 742 | "dataset": dataset_id 743 | } 744 | response = self.post(url, data) 745 | point_id = response['id'] 746 | variable_id = response['variables'][0]['id'] 747 | else: 748 | s = len(d) 749 | 750 | url = 'vali/functions/datasets/points/{}/'.format(point_id) 751 | data = { 752 | "value": d[0] 753 | } 754 | self.request('PATCH', url, json=data) 755 | 756 | for v in d[1:]: 757 | url = 'vali/functions/datasets/points/variables/{}/'.format(variable_id) 758 | data = { 759 | "value_number": v 760 | } 761 | self.request('PATCH', url, json=data) 762 | 763 | return dataset_id 764 | 765 | 766 | def vali_import_dataset(self, vali_id, data, headers=None): 767 | if headers is None: 768 | headers = [] 769 | for i in range(len(data[0])): 770 | headers.append(chr(ord('a') + i)) 771 | self.request('POST', 'valis/' + str(vali_id) + '/import-dataset/', data={'headers': headers, 'data': data}) 772 | 773 | def general_prompt( 774 | self, 775 | custom_prompt: str, 776 | model: str, 777 | field: str, 778 | objects_ids: list[int], 779 | parallel: bool, 780 | replace_valis_by_id: bool = True, 781 | service_type: int = None, 782 | **kwargs 783 | ): 784 | """ 785 | Sends a general prompt to the vali assistant. 786 | :param custom_prompt: The custom prompt to send. 787 | :param model: Model name of the objects. 788 | :param field: The field to apply the prompt to. 789 | :param objects_ids: The list of objects to update. 790 | :param parallel: Whether to run the prompt in parallel or not. 791 | :param replace_valis_by_id: Whether to replace valis by their ids or not. 792 | :param service_type: The service type to use. 793 | """ 794 | if service_type is None: 795 | service_type = 0 if self.offline_assistant else 1 796 | 797 | data = { 798 | "custom_prompt": custom_prompt, 799 | "content_type_id": self.get_content_type_id(model), 800 | "field": field, 801 | "objects_ids": objects_ids, 802 | "parallel": parallel, 803 | "replace_valis_by_id": replace_valis_by_id, 804 | "service_type": service_type, 805 | } 806 | return self.request('PUT', 'vali-assistant/general-custom-prompt/', data, **kwargs) 807 | 808 | def get_content_type_id(self, model: str): 809 | """ 810 | Gets the content type id of a content type. 811 | :param model: The model to get the content type id of. 812 | :returns: The content type id. 813 | """ 814 | content_type = self.request('GET', 'contenttypes/', data={'model': model})[0] 815 | return content_type["id"] 816 | -------------------------------------------------------------------------------- /valispace/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | # TEST COMAND EXAMPLES WITH THE SATURN V PROJECT 4 | 5 | # Before: 6 | # Saturn V project id should be = 1 (change in example if it isn't) 7 | # In CommandModule.Mass mark impact to LaunchEscapeSystem.Mass 8 | # Tag CommandModule component and CommandModule.Mass with a tag named "test". 9 | # Tag id should be = 1 (change in example if it isn't) 10 | 11 | import valispace 12 | 13 | vali = valispace.API() 14 | 15 | print("\n--- GET VALI ---") 16 | a = vali.get_vali(id=3) 17 | print("id=3: \n" + str(a)) 18 | b = vali.get_vali_by_name(vali_name='CommandModule.Mass', project_name='Rocket__SaturnV') 19 | print("\nname='CommandModule.Mass' \n" + str(b)) 20 | 21 | print("\n\n--- GET FILTERED VALI LISTS ---") 22 | c = vali.get_vali_list(workspace_id=1) 23 | print("workspace_id=1: \n" + str(c)) 24 | d = vali.get_vali_list(workspace_name='Default Workspace') 25 | print("\nworkspace_name='Default Workspace' \n" + str(d)) 26 | e = vali.get_vali_list(project_id=2) 27 | print("\nproject_id=2: \n" + str(e)) 28 | f = vali.get_vali_list(project_name='Rocket__SaturnV') 29 | print("\nproject_name='Rocket__SaturnV' \n" + str(f)) 30 | g = vali.get_vali_list(parent_id=3) 31 | print("\nparent_id=2 \n" + str(g)) 32 | f = vali.get_vali_list(parent_name='ApolloSpacecraft') 33 | print("\nparent_name='ApolloSpacecraft' \n" + str(f)) 34 | g = vali.get_vali_list(tag_id=1) 35 | print("\ntag_id=1 \n" + str(g)) 36 | h = vali.get_vali_list(tag_name='test') 37 | print("\ntag_name='test' \n" + str(h)) 38 | i = vali.get_vali_list(vali_marked_as_impacted=4) 39 | print("\nvali_marked_as_impacted=4 \n" + str(i)) 40 | 41 | del a, b, c, d, e, f, g, h 42 | 43 | print("\n--- GET COMPONENT ---") 44 | a = vali.get_component(3) 45 | print("id=3: \n" + str(a)) 46 | b = vali.get_component_by_name(unique_name='CommandModule', project_name='Rocket__SaturnV') 47 | print("\nname='CommandModule' \n" + str(b)) 48 | 49 | print("\n\n--- GET FILTERED COMPONENT LIST ---") 50 | c = vali.get_component_list(workspace_id=1) 51 | print("workspace_id=1: \n" + str(c)) 52 | d = vali.get_component_list(workspace_name='Default Workspace') 53 | print("\nworkspace_name='Default Workspace' \n" + str(d)) 54 | e = vali.get_component_list(project_id=2) 55 | print("\nproject_id=2: \n" + str(e)) 56 | f = vali.get_component_list(project_name='Rocket__SaturnV') 57 | print("\nproject_name='Rocket__SaturnV' \n" + str(f)) 58 | g = vali.get_component_list(parent_id=2) 59 | print("\nparent_id=2 \n" + str(g)) 60 | f = vali.get_component_list(parent_name='ApolloSpacecraft') 61 | print("\nparent_name='ApolloSpacecraft' \n" + str(f)) 62 | g = vali.get_component_list(tag_id=1) 63 | print("\ntag_id=1 \n" + str(g)) 64 | h = vali.get_component_list(tag_name='test') 65 | print("\ntag_name='test' \n" + str(h)) 66 | 67 | 68 | del a, b, c, d, e, f, g, h 69 | 70 | print("\n--- GET PROJECT ---") 71 | a = vali.get_project(id=2) 72 | print("id=2: \n" + str(a)) 73 | b = vali.get_project_by_name(name='Rocket__SaturnV') 74 | print("\nname='Rocket__SaturnV' \n" + str(b)) 75 | 76 | print("\n\n--- GET FILTERED PROJECT LIST ---") 77 | c = vali.get_project_list(workspace_id=1) 78 | print("workspace_id=1: \n" + str(c)) 79 | d = vali.get_project_list(workspace_name='Default Workspace') 80 | print("\nworkspace_name='Default Workspace' \n" + str(d)) 81 | --------------------------------------------------------------------------------