├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── NEWS.md ├── README.md ├── USAGE.md ├── code_snippets ├── applications.py ├── attributes.py ├── bots.py ├── connect.py ├── contact_group_mgmt.py ├── contacts.py ├── content_cache.py ├── content_groups.py ├── create_super_cube.py ├── cube_cache.py ├── cube_report.py ├── dashboard.py ├── datasource_mgmt.py ├── device_mgmt.py ├── document.py ├── dynamic_recipient_list.py ├── events.py ├── fact.py ├── fences.py ├── filter.py ├── incremental_refresh_report.py ├── intelligent_cube.py ├── job_monitor.py ├── languages.py ├── license_mgmt.py ├── metrics.py ├── migration.py ├── object_mgmt.py ├── olap_cube_refresh_status.py ├── project_languages_mgmt.py ├── project_mgmt.py ├── report_cache.py ├── reports.py ├── schedules.py ├── schema_mgmt.py ├── script_usage_types.json ├── security_filters.py ├── security_roles_and_privileges.py ├── server_mgmt.py ├── shortcuts.py ├── subscription_mgmt.py ├── table_mgmt.py ├── transformation.py ├── translations.py ├── transmitter_mgmt.py ├── user_hierarchy_mgmt.py ├── user_library.py ├── user_mgmt.py ├── variables.json └── vldb.py ├── deprecation.png ├── docs └── pull_request_template.md ├── mstrio ├── __init__.py ├── access_and_security │ ├── __init__.py │ ├── privilege.py │ ├── privilege_mode.py │ └── security_role.py ├── api │ ├── __init__.py │ ├── administration.py │ ├── applications.py │ ├── attributes.py │ ├── authentication.py │ ├── bookmarks.py │ ├── browsing.py │ ├── calendars.py │ ├── change_journal.py │ ├── changesets.py │ ├── contact_groups.py │ ├── contacts.py │ ├── content_groups.py │ ├── cubes.py │ ├── datasets.py │ ├── datasources.py │ ├── devices.py │ ├── documents.py │ ├── drivers.py │ ├── emails.py │ ├── events.py │ ├── facts.py │ ├── filters.py │ ├── folders.py │ ├── gateways.py │ ├── hierarchies.py │ ├── hooks.py │ ├── incremental_refresh_reports.py │ ├── languages.py │ ├── library.py │ ├── license.py │ ├── metrics.py │ ├── migration.py │ ├── misc.py │ ├── monitors.py │ ├── objects.py │ ├── palettes.py │ ├── projects.py │ ├── registrations.py │ ├── reports.py │ ├── runtimes.py │ ├── schedules.py │ ├── schema.py │ ├── scripts.py │ ├── security.py │ ├── security_filters.py │ ├── subscriptions.py │ ├── tables.py │ ├── timezones.py │ ├── transformations.py │ ├── transmitters.py │ ├── user_hierarchies.py │ ├── usergroups.py │ └── users.py ├── config.py ├── connection.py ├── datasources │ ├── __init__.py │ ├── database_connections.py │ ├── datasource_connection.py │ ├── datasource_instance.py │ ├── datasource_login.py │ ├── datasource_map.py │ ├── dbms.py │ ├── driver.py │ ├── embedded_connection.py │ ├── gateway.py │ └── helpers.py ├── distribution_services │ ├── __init__.py │ ├── device │ │ ├── __init__.py │ │ ├── device.py │ │ └── device_properties.py │ ├── email.py │ ├── event.py │ ├── schedule │ │ ├── __init__.py │ │ ├── schedule.py │ │ └── schedule_time.py │ ├── subscription │ │ ├── __init__.py │ │ ├── base_subscription.py │ │ ├── cache_update_subscription.py │ │ ├── common.py │ │ ├── content.py │ │ ├── delivery.py │ │ ├── dynamic_recipient_list.py │ │ ├── email_subscription.py │ │ ├── file_subscription.py │ │ ├── ftp_subscription.py │ │ ├── history_list_subscription.py │ │ ├── mobile_subscription.py │ │ ├── subscription_manager.py │ │ └── subscription_status.py │ └── transmitter │ │ ├── __init__.py │ │ └── transmitter.py ├── helpers.py ├── modeling │ ├── __init__.py │ ├── expression │ │ ├── __init__.py │ │ ├── dynamic_date_time.py │ │ ├── enums.py │ │ ├── expression.py │ │ ├── expression_nodes.py │ │ ├── fact_expression.py │ │ └── parameters.py │ ├── filter │ │ ├── __init__.py │ │ └── filter.py │ ├── metric │ │ ├── __init__.py │ │ ├── dimensionality.py │ │ ├── metric.py │ │ └── metric_format.py │ ├── schema │ │ ├── __init__.py │ │ ├── attribute │ │ │ ├── __init__.py │ │ │ ├── attribute.py │ │ │ ├── attribute_form.py │ │ │ └── relationship.py │ │ ├── fact │ │ │ ├── __init__.py │ │ │ └── fact.py │ │ ├── helpers.py │ │ ├── schema_management.py │ │ ├── table │ │ │ ├── __init__.py │ │ │ ├── logical_table.py │ │ │ ├── physical_table.py │ │ │ └── warehouse_table.py │ │ ├── transformation │ │ │ ├── __init__.py │ │ │ └── transformation.py │ │ └── user_hierarchy │ │ │ ├── __init__.py │ │ │ └── user_hierarchy.py │ └── security_filter │ │ ├── __init__.py │ │ └── security_filter.py ├── object_management │ ├── __init__.py │ ├── folder.py │ ├── migration │ │ ├── __init__.py │ │ ├── migration.py │ │ └── package.py │ ├── object.py │ ├── predefined_folders.py │ ├── search_enums.py │ ├── search_operations.py │ ├── shortcut.py │ └── translation.py ├── project_objects │ ├── __init__.py │ ├── applications.py │ ├── bots.py │ ├── content_cache.py │ ├── content_group.py │ ├── dashboard.py │ ├── datasets │ │ ├── __init__.py │ │ ├── cube.py │ │ ├── cube_cache.py │ │ ├── helpers.py │ │ ├── olap_cube.py │ │ └── super_cube.py │ ├── document.py │ ├── helpers.py │ ├── incremental_refresh_report │ │ ├── __init__.py │ │ └── incremental_refresh_report.py │ ├── library.py │ ├── palette.py │ ├── prompt.py │ └── report.py ├── server │ ├── __init__.py │ ├── calendar.py │ ├── cluster.py │ ├── environment.py │ ├── fence.py │ ├── job_monitor.py │ ├── language.py │ ├── license.py │ ├── node.py │ ├── project.py │ ├── project_languages.py │ ├── server.py │ ├── setting_types.py │ ├── storage.py │ └── timezone.py ├── types.py ├── users_and_groups │ ├── __init__.py │ ├── contact.py │ ├── contact_group.py │ ├── user.py │ ├── user_connections.py │ └── user_group.py └── utils │ ├── __init__.py │ ├── acl.py │ ├── api_helpers.py │ ├── cache.py │ ├── certified_info.py │ ├── collections.py │ ├── datasources.py │ ├── dependence_mixin.py │ ├── dev_helpers.py │ ├── dict_filter.py │ ├── encoder.py │ ├── entity.py │ ├── enum_helper.py │ ├── enums.py │ ├── error_handlers.py │ ├── filter.py │ ├── format.py │ ├── formjson.py │ ├── helper.py │ ├── model.py │ ├── monitors.py │ ├── object_mapping.py │ ├── parser.py │ ├── progress_bar_mixin.py │ ├── related_subscription_mixin.py │ ├── resources │ └── __init__.py │ ├── response_processors │ ├── __init__.py │ ├── browsing.py │ ├── calendars.py │ ├── cubes.py │ ├── datasources.py │ ├── drivers.py │ ├── gateways.py │ ├── languages.py │ ├── migrations.py │ ├── monitors.py │ ├── objects.py │ ├── projects.py │ ├── subscriptions.py │ ├── tables.py │ ├── usergroups.py │ └── users.py │ ├── sessions.py │ ├── settings │ ├── __init__.py │ ├── base_settings.py │ ├── setting_types.py │ ├── settings_helper.py │ └── settings_io.py │ ├── time_helper.py │ ├── translation_mixin.py │ ├── version_helper.py │ ├── vldb_mixin.py │ └── wip.py ├── pyproject.toml ├── strategy-logo.png └── workflows ├── add_email_to_new_users.py ├── application_end_to_end_showcase.py ├── assign_ACLs_for_objects.py ├── compare_advanced_properties.py ├── control_users_and_security.py ├── create_bursting_subscription.py ├── create_content_group_and_application.py ├── create_mobile_subscription.py ├── create_prompted_subscription.py ├── delete_addresses_from_departed_users.py ├── delete_inactive_cube_caches.py ├── delete_subscriptions_of_departed_users.py ├── disable_all_active_bots.py ├── disable_inactive_users.py ├── duplicate_user_subscriptions.py ├── export_and_import_translations_to_database.py ├── export_selected_information_to_csv.py ├── get_all_columns_in_table.py ├── get_subscriptions_by_emails.py ├── import_cube_data_into_dataframe.py ├── import_report_data_into_dataframe.py ├── importing_usergroups_user_group_structure_from_csv.py ├── list_active_user_privileges.py ├── list_empty_user_groups.py ├── list_security_roles_per_user.py ├── manage_active_and_inactive_users.py ├── manage_pre_post_execution.py ├── manage_project_languages.py ├── manage_project_settings.py ├── manage_subscriptions.py ├── manage_translation_for_metric.py ├── migrate_active_users.py ├── migrate_last_modified_objects.py ├── migrate_user_contacts_and_addresses.py ├── script_usage_types.json ├── send_dashboard_email_subscription_after_cube_refresh.py └── user_group_maintenance.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: BUG Report 3 | about: Create a BUG report for mstrio-py 4 | title: "[BUG]: ___" 5 | labels: ["bug", "new"] 6 | 7 | --- 8 | 9 | ## Description 10 | 11 | > _A clear and concise description of what the bug is._ 12 | 13 | ### MicroStrategy Support Case 14 | 15 | > _If the issue has been raised via MicroStrategy Support Team already, add the Case Number and/or any additional details here_ 16 | 17 | ## Steps To Reproduce 18 | 19 | > _Steps to reproduce the behavior / a list of methods creating a problem / additional information the code snippet below will not clarify_ 20 | 21 | ## Code Snippet 22 | 23 | > _Full, reusable code snippet highlighting the problem_ 24 | > 25 | > _It may contain placeholders expected to be filled before running the code_ 26 | 27 | ```python 28 | # Add your code snippet here 29 | ``` 30 | 31 | ## Additional Context (_optional_) 32 | 33 | > _Any other context about the problem, that does not fit into the sections above_ 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New Feature Request 3 | about: Suggest a new functionality or enhancement for mstrio-py 4 | title: "[ER]: ___" 5 | labels: ["enhancement", "new"] 6 | 7 | --- 8 | 9 | ## Description 10 | 11 | > _A clear and concise description of what the new / enhanced functionality is._ 12 | 13 | ### MicroStrategy Support Case 14 | 15 | > _If the enhancement request has been raised via MicroStrategy Support Team already, add the Case Number and/or any additional details here_ 16 | 17 | ## Additional Context (_optional_) 18 | 19 | > _Any other context about the feature, that does not fit into the sections above_ 20 | -------------------------------------------------------------------------------- /.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 | demos/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | sphinx/ 23 | tests/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.sh 29 | *.egg 30 | *node_modules* 31 | *DS_Store* 32 | Untitled* 33 | *.vscode* 34 | MANIFEST 35 | .pre-commit* 36 | validation_scripts 37 | tox.ini 38 | container/ 39 | scripts/ 40 | script-templates/ 41 | 42 | # PyInstaller 43 | # Usually these files are written by a python script from a template 44 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 45 | *.manifest 46 | *.spec 47 | 48 | # Installer logs 49 | pip-log.txt 50 | pip-delete-this-directory.txt 51 | 52 | # Unit test / coverage reports 53 | htmlcov/ 54 | .tox/ 55 | .coverage 56 | .coverage.* 57 | .cache 58 | coverage.xml 59 | *.cover 60 | .hypothesis/ 61 | *test* 62 | 63 | # Translations 64 | *.mo 65 | *.pot 66 | 67 | # Django stuff: 68 | *.log 69 | local_settings.py 70 | db.sqlite3 71 | 72 | # Flask stuff: 73 | instance/ 74 | .webassets-cache 75 | 76 | # Scrapy stuff: 77 | .scrapy 78 | 79 | # Sphinx documentation 80 | docs/_build/ 81 | 82 | # PyBuilder 83 | target/ 84 | 85 | # Jupyter Notebook 86 | *.ipynb* 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # celery beat schedule file 92 | celerybeat-schedule 93 | 94 | # SageMath parsed files 95 | *.sage.py 96 | 97 | # Environments 98 | .env 99 | .venv 100 | env/ 101 | venv/ 102 | ENV/ 103 | env.bak/ 104 | venv.bak/ 105 | 106 | # Spyder project settings 107 | .spyderproject 108 | .spyproject 109 | 110 | # Rope project settings 111 | .ropeproject 112 | 113 | # mkdocs documentation 114 | /site 115 | 116 | # mypy 117 | .mypy_cache/ 118 | *.xml 119 | *.pyc 120 | *.pyc 121 | *.iml 122 | -------------------------------------------------------------------------------- /code_snippets/applications.py: -------------------------------------------------------------------------------- 1 | """This is the demo script to show how to manage applications. 2 | Its basic goal is to present what can be done with this module and to ease 3 | its usage. 4 | """ 5 | 6 | from mstrio.connection import get_connection 7 | from mstrio.project_objects.applications import Application, list_applications 8 | 9 | # Define a variable which can be later used in a script 10 | PROJECT_ID = $project_id # Insert name of project here 11 | 12 | conn = get_connection(workstationData, project_id=PROJECT_ID) 13 | 14 | 15 | # Define variables which can be later used in a script 16 | LIMIT = $limit 17 | NAME_FILTER = $name_filter 18 | 19 | # list applications with different conditions 20 | # Note: No Applications exist in a default environment 21 | list_of_all_apps = list_applications(connection=conn) 22 | list_of_several_apps = list_applications(connection=conn, limit=LIMIT) 23 | list_of_apps_name_filtered = list_applications(connection=conn, name=NAME_FILTER) 24 | list_of_all_apps_as_dicts = list_applications(connection=conn, to_dictionary=True) 25 | 26 | # Define variables which can be later used in a script 27 | APP_NAME = $app_name 28 | APP_DESCRIPTION = $app_description 29 | APP_HOME_SCREEN_MODE = $app_home_screen_mode 30 | APP_HOME_DOCUMENT_URL = $app_home_document_url 31 | APP_HOME_DOCUMENT_TYPE = $app_home_document_type 32 | APP_HOME_ICON = $app_home_icon 33 | APP_HOME_TOOLBAR_MODE = $app_home_toolbar_mode 34 | APP_HOME_LIBRARY_CONTENT_BUNDLE_ID = $app_home_library_content_bundle_id 35 | APP_HOME_LIBRARY_SIDEBAR = $app_home_library_sidebar 36 | 37 | # Create an Application 38 | app = Application.create( 39 | connection=conn, 40 | name='Application Validation Scripts', 41 | description='This is a demo application created by the validation script.', 42 | home_screen=Application.HomeSettings( 43 | mode=APP_HOME_SCREEN_MODE, 44 | home_document=Application.HomeSettings.HomeDocument( 45 | url=APP_HOME_DOCUMENT_URL, 46 | home_document_type=APP_HOME_DOCUMENT_TYPE, 47 | icons=[APP_HOME_ICON], 48 | toolbar_mode=APP_HOME_TOOLBAR_MODE, 49 | toolbar_enabled=True, 50 | ), 51 | home_library=Application.HomeSettings.HomeLibrary( 52 | content_bundle_ids=[APP_HOME_LIBRARY_CONTENT_BUNDLE_ID], 53 | show_all_contents=False, 54 | icons=[APP_HOME_ICON], 55 | toolbar_mode=APP_HOME_TOOLBAR_MODE, 56 | sidebars=[APP_HOME_LIBRARY_SIDEBAR], 57 | toolbar_enabled=True, 58 | ), 59 | ) 60 | ) 61 | 62 | # Define variables which can be later used in a script 63 | APP_ID = $app_id 64 | APP_NEW_NAME = $app_new_name 65 | 66 | # Get an Application by it's id or name 67 | app = Application(connection=conn, id=APP_ID) 68 | app = Application(connection=conn, name=APP_NAME) 69 | 70 | # Alter an Application 71 | app.alter( 72 | home_screen=app.home_screen, 73 | name=APP_NEW_NAME, 74 | general=app.general, 75 | email_settings=app.email_settings, 76 | ai_settings=app.ai_settings, 77 | auth_modes=app.auth_modes, 78 | ) 79 | 80 | # Delete an Application without prompt 81 | app.delete(force=True) 82 | -------------------------------------------------------------------------------- /code_snippets/bots.py: -------------------------------------------------------------------------------- 1 | """This is the demo script to show how to manage bots. Its basic goal 2 | is to present what can be done with this module and to ease its usage. 3 | """ 4 | 5 | from mstrio.connection import get_connection 6 | from mstrio.project_objects.bots import Bot, list_bots 7 | 8 | # Define a variable which can be later used in a script 9 | PROJECT_NAME = $project_name # Insert name of project here 10 | 11 | # Get connection to the environment 12 | conn = get_connection(workstationData, project_name=PROJECT_NAME) 13 | 14 | # List all bots 15 | bots = list_bots(connection=conn) 16 | # List all bots as dictionaries 17 | bots_as_dicts = list_bots(connection=conn, to_dictionary=True) 18 | 19 | # Define a variable which can be later used in a script 20 | BOT_ID = $bot_id 21 | 22 | # Initialize a bot 23 | test_bot = Bot(connection=conn, id=BOT_ID) 24 | 25 | # Define variables which can be later used in a script 26 | NEW_NAME = $new_name 27 | NEW_DESCRIPTION = $new_description 28 | NEW_ABBREVIATION = $new_abbreviation 29 | NEW_HIDDEN = $new_hidden == 'True' 30 | NEW_STATUS = $new_status 31 | NEW_COMMENTS = $new_comments 32 | 33 | # Alter the bot's properties 34 | test_bot.alter( 35 | name=NEW_NAME, 36 | description=NEW_DESCRIPTION, 37 | abbreviation=NEW_ABBREVIATION, 38 | hidden=NEW_HIDDEN, 39 | status=NEW_STATUS, 40 | comments=NEW_COMMENTS, 41 | ) 42 | 43 | # Disable and enable a bot 44 | test_bot.disable() 45 | test_bot.enable() 46 | 47 | # Delete a bot 48 | test_bot.delete(force=True) 49 | -------------------------------------------------------------------------------- /code_snippets/content_cache.py: -------------------------------------------------------------------------------- 1 | """This is the demo script to show how to manage content cache. Its basic goal 2 | is to present what can be done with this module and to ease its usage. 3 | """ 4 | 5 | from mstrio.connection import get_connection 6 | from mstrio.project_objects.content_cache import ContentCache 7 | 8 | # Define a variable which can be later used in a script 9 | PROJECT_NAME = $project_name # Insert name of project here 10 | 11 | conn = get_connection(workstationData, PROJECT_NAME) 12 | 13 | # Content cache management 14 | # Listing caches with different conditions 15 | list_of_all_caches = ContentCache.list_caches(connection=conn) 16 | 17 | # Define a variable which can be later used in a script 18 | CACHE_ID = $cache_id # Insert ID of cache on which you want to perform actions 19 | 20 | list_of_one_cache = ContentCache.list_caches(connection=conn, id=CACHE_ID) 21 | list_of_caches_as_dicts = ContentCache.list_caches(connection=conn, to_dictionary=True) 22 | 23 | # Get single content cache by its id 24 | content_cache = ContentCache(connection=conn, cache_id=CACHE_ID) 25 | 26 | # Unload content cache 27 | content_cache.unload() 28 | 29 | # Load content cache 30 | content_cache.load() 31 | 32 | # Refresh content cache instance data 33 | content_cache.fetch() 34 | 35 | # Listing properties of content cache 36 | properties = content_cache.list_properties() 37 | 38 | # Delete content cache 39 | content_cache.delete(force=True) 40 | 41 | # Define a variable which can be later used in a script 42 | OTHER_CACHE_ID = $other_cache_id # Insert ID of cache on which you want to perform actions 43 | 44 | # Unload multiple content caches 45 | ContentCache.unload_caches(connection=conn, cache_ids=[CACHE_ID, OTHER_CACHE_ID]) 46 | 47 | # Load multiple content caches 48 | ContentCache.load_caches(connection=conn, cache_ids=[CACHE_ID, OTHER_CACHE_ID]) 49 | 50 | # Delete multiple content caches 51 | ContentCache.delete_caches(connection=conn, cache_ids=[CACHE_ID, OTHER_CACHE_ID]) 52 | -------------------------------------------------------------------------------- /code_snippets/cube_cache.py: -------------------------------------------------------------------------------- 1 | """This is the demo script to show how to manage cube caches. 2 | 3 | This script will not work without replacing parameters with real values. 4 | Its basic goal is to present what can be done with this module and to 5 | ease its usage. 6 | """ 7 | 8 | from mstrio.project_objects import ( 9 | CubeCache, delete_cube_cache, delete_cube_caches, list_cube_caches, OlapCube 10 | ) 11 | from mstrio.connection import get_connection 12 | 13 | # Define a variable which can be later used in a script 14 | PROJECT_NAME = $project_name # Insert project name here 15 | 16 | conn = get_connection(workstationData, project_name=PROJECT_NAME) 17 | 18 | # Define a variable which can be later used in a script 19 | CUBE_ID = $cube_id # Insert name of cube on which you want to perform actions 20 | 21 | # get caches from an OLAP Cube 22 | oc_caches = OlapCube(conn, id=CUBE_ID).get_caches() 23 | 24 | # Define variables which can be later used in a script 25 | NODE_NAME = $node_name # Insert name of node on which you want to perform actions 26 | DB_CONNECTION_ID = $db_connection_id # insert ID of DB connection you want to include in your functions 27 | 28 | # list all caches 29 | list_cube_caches(connection=conn) 30 | # list all caches on a given node 31 | list_cube_caches(connection=conn, nodes=NODE_NAME) 32 | # list all loaded caches on a given node 33 | list_cube_caches(connection=conn, nodes=NODE_NAME, loaded=True) 34 | # list all cache on a given node for given cube 35 | list_cube_caches(connection=conn, nodes=NODE_NAME, cube_id=CUBE_ID) 36 | # list all cache on a given node for given database connection 37 | list_cube_caches(connection=conn, nodes=NODE_NAME, db_connection_id=DB_CONNECTION_ID) 38 | 39 | # Define a variable which can be later used in a script 40 | CUBE_CACHE_ID = $cube_cache_id # Insert ID of cube cache that you want to perform your actions upon 41 | 42 | # get a single cube cache by its id 43 | cube_cache_ = CubeCache(connection=conn, cache_id=CUBE_CACHE_ID) 44 | 45 | # unload a cube cache 46 | cube_cache_.unload() 47 | # load a cube cache 48 | cube_cache_.load() 49 | # deactivate a cube cache 50 | cube_cache_.deactivate() 51 | # activate a cube cache 52 | cube_cache_.activate() 53 | 54 | # refresh cube cache 55 | cube_cache_.fetch() 56 | 57 | # get state of cube cache 58 | cc_state = cube_cache_.state 59 | 60 | # get properties of cube cache 61 | cc_properties = cube_cache_.list_properties() 62 | 63 | # Delete a single cube cache (there are two ways). When `force` argument is set 64 | # to `False` (default value) then deletion must be approved. 65 | cube_cache_.delete(force=True) 66 | delete_cube_cache(connection=conn, id=cube_cache_.id, force=True) 67 | 68 | # delete all cube caches (the same rule with `force` as above) 69 | delete_cube_caches(connection=conn, force=True) 70 | # delete all cube caches on a given node (the same rule with `force` as above) 71 | delete_cube_caches(connection=conn, nodes=NODE_NAME, force=True) 72 | # delete all loaded cube caches on a given node (the same rule with `force` as 73 | # above) 74 | delete_cube_caches(connection=conn, nodes=NODE_NAME, loaded=True, force=True) 75 | # delete all cube caches on a given node for a given cube (the same rule with 76 | # `force` as above) 77 | delete_cube_caches(connection=conn, nodes=NODE_NAME, cube_id=CUBE_ID, force=True) 78 | # delete all cube caches on a given node for a given database connection (the 79 | # same rule with `force` as above) 80 | delete_cube_caches(connection=conn, nodes=NODE_NAME, db_connection_id=DB_CONNECTION_ID, force=True) 81 | -------------------------------------------------------------------------------- /code_snippets/cube_report.py: -------------------------------------------------------------------------------- 1 | """This is the demo script to show how to import data from OlapCubes and 2 | Reports. It is possible to select attributes, metrics and attribute forms which 3 | is presented. 4 | 5 | This script will not work without replacing parameters with real values. 6 | Its basic goal is to present what can be done with this module and to 7 | ease its usage. 8 | """ 9 | 10 | from mstrio.project_objects import OlapCube, Report 11 | import pandas as pd 12 | import datetime as dt 13 | 14 | # get connection to an environment 15 | from mstrio.connection import get_connection 16 | 17 | # Define a variable which can be later used in a script 18 | PROJECT_NAME = $project_name # Insert project name here 19 | 20 | conn = get_connection(workstationData, project_name=PROJECT_NAME) 21 | 22 | # Define variables which can be later used in a script 23 | CUBE_ID = $cube_id 24 | REPORT_ID = $report_id 25 | 26 | # get cube based on its id and store it in data frame 27 | my_cube = OlapCube(connection=conn, id=CUBE_ID) 28 | my_cube_df = my_cube.to_dataframe() 29 | 30 | # if any of your columns should have type 'Time' please set it manually 31 | # Column name should be set to the name of column that should have type 'Time'. 32 | # Please repeat this line for all of the columns that should have type 'Time'. 33 | COLUMN_NAME = $column_name 34 | my_cube_df[COLUMN_NAME] = pd.to_datetime(my_cube_df[COLUMN_NAME], format='%H:%M:%S').dt.time 35 | # If the values in column name are in HH:MM format and not in HH:MM:SS 36 | # you can format it as below 37 | my_cube_df[COLUMN_NAME] = pd.to_datetime(my_cube_df[COLUMN_NAME], format='%H:%M').dt.time 38 | 39 | # get report based on its id and store it in data frame 40 | my_report = Report(connection=conn, id=REPORT_ID, parallel=False) 41 | my_report_df = my_report.to_dataframe() 42 | 43 | # get list of ids of metrics, attributes or attribute elements available within 44 | # Cube or Report 45 | my_cube.metrics 46 | my_cube.attributes 47 | my_cube.attr_elements 48 | 49 | # Define variables which can be later used in a script 50 | ATTRIBUTES_LIST = $attributes_list # Insert list of attributes that you want to include in your functions 51 | METRICS_LIST = $metrics_list # insert list of metrics to be used in your functions 52 | ATTRIBUTES_ELEMENTS_LIST = $attributes_elements_list # insert list of attributes to be used in your functions 53 | 54 | # by default all elements are shown in the data frame. To choose elements you 55 | # have to pass proper IDs to function 'apply_filters()' which is available for 56 | # Cube and Report 57 | my_cube.apply_filters( 58 | attributes=ATTRIBUTES_LIST, 59 | metrics=METRICS_LIST, 60 | attr_elements=ATTRIBUTES_ELEMENTS_LIST, 61 | ) 62 | # check selected elements which will be placed into a dataframe 63 | my_cube.selected_attributes 64 | my_cube.selected_metrics 65 | my_cube.selected_attr_elements 66 | 67 | my_cube_applied_filters_df = my_cube.to_dataframe() 68 | 69 | # to exclude specific attribute elements, pass the `operator="NotIn"` to 70 | # `apply_filters()` method 71 | my_cube.apply_filters( 72 | attributes=ATTRIBUTES_LIST, 73 | metrics=METRICS_LIST, 74 | attr_elements=ATTRIBUTES_ELEMENTS_LIST, 75 | operator="NotIn", 76 | ) 77 | 78 | my_cube_excluded_elements_df = my_cube.to_dataframe() 79 | -------------------------------------------------------------------------------- /code_snippets/dynamic_recipient_list.py: -------------------------------------------------------------------------------- 1 | """This is the demo script to show how to manage dynamic recipient lists. 2 | This script will not work without replacing parameters with real values. 3 | Its basic goal is to present what can be done with this module and to ease 4 | its usage. 5 | """ 6 | 7 | from mstrio.connection import get_connection 8 | from mstrio.distribution_services.subscription import DynamicRecipientList, list_dynamic_recipient_lists 9 | 10 | # Define a variable which can be later used in a script 11 | PROJECT_NAME = $project_name # Insert name of project here 12 | 13 | conn = get_connection(workstationData, PROJECT_NAME) 14 | 15 | # Define variables which can be later used in a script 16 | # DRL stands for Dynamic Recipient List 17 | DRL_NAME = $drl_name 18 | SOURCE_REPORT_ID = $source_report_id 19 | ATTRIBUTE_ID = $attribute_id 20 | ATTRIBUTE_FORM_ID = $attribute_form_id 21 | DESCRIPTION = $description 22 | EXAMPLE_MAPPING_FIELD = DynamicRecipientList.MappingField( 23 | attribute_id=ATTRIBUTE_ID, 24 | attribute_form_id=ATTRIBUTE_FORM_ID) 25 | 26 | # Create a DRL 27 | drl3 = DynamicRecipientList.create(connection=conn, name = DRL_NAME, description=DESCRIPTION, 28 | source_report_id=SOURCE_REPORT_ID, 29 | physical_address=EXAMPLE_MAPPING_FIELD, 30 | linked_user=EXAMPLE_MAPPING_FIELD, 31 | device=EXAMPLE_MAPPING_FIELD) 32 | 33 | # List dynamic recipient lists with different conditions 34 | list_of_all_drl = list_dynamic_recipient_lists(connection=conn) 35 | print(list_of_all_drl) 36 | list_of_all_drl_as_dicts = list_dynamic_recipient_lists(connection=conn, to_dictionary=True) 37 | print(list_of_all_drl_as_dicts) 38 | list_of_all_drl_by_name = list_dynamic_recipient_lists(connection=conn, name=DRL_NAME) 39 | print(list_of_all_drl_by_name) 40 | 41 | # Define variables which can be later used in a script 42 | DRL_ID = $drl_id # ID of the DRL we want to perform actions on 43 | 44 | # Get a DRL by it's id or name 45 | drl = DynamicRecipientList(connection=conn, id=DRL_ID) 46 | drl2 = DynamicRecipientList(connection=conn, name=DRL_NAME) 47 | 48 | # List properties of a DRL 49 | print(drl.list_properties()) 50 | 51 | # Alter a DRL 52 | drl2.alter(name = DRL_NAME, description=DESCRIPTION, 53 | source_report_id=SOURCE_REPORT_ID, 54 | physical_address=EXAMPLE_MAPPING_FIELD, 55 | linked_user=EXAMPLE_MAPPING_FIELD, 56 | device=EXAMPLE_MAPPING_FIELD) 57 | 58 | # Delete a DRL without prompt 59 | drl.delete(force=True) 60 | -------------------------------------------------------------------------------- /code_snippets/events.py: -------------------------------------------------------------------------------- 1 | """This is the demo script to show how to manage events. 2 | This script will not work without replacing parameters with real values. 3 | Its basic goal is to present what can be done with this module and to 4 | ease its usage. 5 | """ 6 | 7 | from mstrio.distribution_services import Event, list_events 8 | from mstrio.connection import get_connection 9 | 10 | # Define a variable which can be later used in a script 11 | PROJECT_NAME = $project_name # Project to connect to 12 | 13 | conn = get_connection(workstationData, project_name=PROJECT_NAME) 14 | 15 | # List all events 16 | all_events = list_events(conn) 17 | 18 | # Define variables which can be later used in a script 19 | EVENT_ID = $event_id # id for Event object 20 | EVENT_NAME = $event_name # name for Event object 21 | 22 | # Get event by id 23 | sample_event = Event(conn, id=EVENT_ID) 24 | 25 | # Get event by name 26 | sample_event = Event(conn, name=EVENT_NAME) 27 | 28 | # list the Event's properties 29 | sample_event.list_properties() 30 | 31 | # Define a variable which can be later used in a script 32 | EVENT_DESCRIPTION = $event_description 33 | 34 | # Create event with description 35 | db_load_event = Event.create(conn, name=EVENT_NAME, description=EVENT_DESCRIPTION) 36 | 37 | # Trigger the event 38 | db_load_event.trigger() 39 | 40 | # Rename the event via alter 41 | db_load_event.alter(name=EVENT_NAME, description=EVENT_DESCRIPTION) 42 | 43 | # Delete the event 44 | # Please note that when argument `force` is set to `False` (default value), 45 | # deletion must be confirmed by selecting the appropriate prompt value. 46 | # Do also note that the Event object stored in the `db_load_event` variable is 47 | # still valid after the rename above, as it internally references the event 48 | # by ID, not name. 49 | db_load_event.delete(force=True) 50 | -------------------------------------------------------------------------------- /code_snippets/fences.py: -------------------------------------------------------------------------------- 1 | """This is the demo script to show how to manage fences. 2 | Its basic goal is to present what can be done with this module and to ease 3 | its usage. 4 | """ 5 | 6 | from mstrio.connection import get_connection 7 | from mstrio.server.fence import Fence, FenceType, list_fences 8 | from mstrio.server.project import Project 9 | from mstrio.users_and_groups import User, UserGroup 10 | 11 | # Define a variable which can be later used in a script 12 | PROJECT_ID = $project_id 13 | 14 | conn = get_connection(workstationData, project_id=PROJECT_ID) 15 | 16 | # List fences 17 | # Note: No Fences exist in a default environment 18 | fences = list_fences(connection=conn) 19 | fences_as_dicts = list_fences(connection=conn, to_dictionary=True) 20 | 21 | # Define a variable which can be later used in a script 22 | RANK = $rank 23 | FENCE_NAME = $fence_name 24 | NODE_NAME = $node_name 25 | USER_ID = $user_id 26 | USER_GROUP_ID = $user_group_id 27 | 28 | # Create a Fence 29 | fence = Fence.create( 30 | connection=conn, 31 | rank=RANK, 32 | name=FENCE_NAME, 33 | type=FenceType.USER_FENCE, 34 | nodes=[NODE_NAME], 35 | users=[User(connection=conn, id=USER_ID)], 36 | user_groups=[UserGroup(connection=conn, id=USER_GROUP_ID)], 37 | projects=[Project(connection=conn, id=PROJECT_ID)], 38 | ) 39 | 40 | # Get a Fence by it's id or name 41 | fence = Fence(connection=conn, id=fence.id) 42 | fence = Fence(connection=conn, name=FENCE_NAME) 43 | 44 | # Define variables which can be later used in a script 45 | RANK_2 = $rank_2 46 | USER_ID_2 = $user_id_2 47 | USER_GROUP_ID_2 = $user_group_id_2 48 | PROJECT_ID_2 = $project_id_2 49 | 50 | # Alter a Fence 51 | fence.alter( 52 | rank=RANK_2, 53 | users=[User(connection=conn, id=USER_ID_2)], 54 | user_groups=[UserGroup(connection=conn, id=USER_GROUP_ID_2)], 55 | projects=[Project(connection=conn, id=PROJECT_ID_2)], 56 | ) 57 | 58 | # Delete a Fence 59 | fence.delete(force=True) 60 | -------------------------------------------------------------------------------- /code_snippets/languages.py: -------------------------------------------------------------------------------- 1 | """This is the demo script to show how to manage languages. This script will 2 | not work without replacing parameters with real values. Its basic goal is to 3 | present what can be done with this module and to ease its usage. 4 | """ 5 | 6 | from mstrio.connection import get_connection 7 | from mstrio.server.language import list_interface_languages, list_languages, Language 8 | 9 | # Define a variable which can be later used in a script 10 | PROJECT_NAME = $project_name # Project to connect to 11 | 12 | # Create connection based on workstation data 13 | CONN = get_connection(workstationData, project_name=PROJECT_NAME) 14 | 15 | # List languages 16 | language_list = list_languages(connection=CONN) 17 | 18 | # List interface languages 19 | interface_language_list = list_interface_languages(connection=CONN) 20 | 21 | # Define variables which can be later used in a script 22 | LANGUAGE_NAME = $language_name 23 | LANGUAGE_ID = $language_id 24 | LANGUAGE_NEW_NAME = $language_new_name 25 | BASE_LANG = $base_lang 26 | INTERFACE_LANG_ID = $interface_lang_id 27 | MINUTES15 = $minutes15 28 | MINUTES30 = $minutes30 29 | HOUR = $hour 30 | DAY = $day 31 | HOUR_OF_DAY = $hour_of_day 32 | WEEK = $week 33 | MONTH = $month 34 | QUARTER = $quarter 35 | YEAR = $year 36 | 37 | # Create a language 38 | # For base_language you can either specify: 39 | # - Language class object 40 | # - specific Language ID 41 | # - specific Language lcid 42 | lang = Language.create( 43 | connection=CONN, 44 | name=LANGUAGE_NAME, 45 | base_language=BASE_LANG, 46 | interface_language_id=INTERFACE_LANG_ID, 47 | formatting_settings=Language.TimeInterval( 48 | minutes15=MINUTES15, 49 | minutes30=MINUTES30, 50 | hour=HOUR, 51 | day=DAY, 52 | hour_of_day=HOUR_OF_DAY, 53 | week=WEEK, 54 | month=MONTH, 55 | quarter=QUARTER, 56 | year=YEAR 57 | ) 58 | ) 59 | 60 | # Initialise a language 61 | # By ID 62 | lang2 = Language(connection=CONN, id=LANGUAGE_ID) 63 | # By name 64 | lang2 = Language(connection=CONN, name=LANGUAGE_NAME) 65 | 66 | # Alter a language 67 | lang.alter( 68 | name = LANGUAGE_NEW_NAME, 69 | formatting_settings=Language.TimeInterval( 70 | minutes15=MINUTES15, 71 | minutes30=MINUTES30, 72 | hour=HOUR, 73 | day=DAY, 74 | hour_of_day=HOUR_OF_DAY, 75 | week=WEEK, 76 | month=MONTH, 77 | quarter=QUARTER, 78 | year=YEAR 79 | ) 80 | ) 81 | 82 | # Delete a language 83 | lang.delete(force=True) 84 | -------------------------------------------------------------------------------- /code_snippets/olap_cube_refresh_status.py: -------------------------------------------------------------------------------- 1 | """This is the demo script meant to show how to control the refresh status of 2 | OLAP cube. 3 | 4 | This script will not work without replacing parameters with real values. 5 | Its basic goal is to present what can be done with this module and to 6 | ease its usage. 7 | """ 8 | 9 | from pprint import pprint 10 | from time import sleep 11 | 12 | from mstrio.connection import get_connection 13 | from mstrio.helpers import IServerError 14 | from mstrio.project_objects import OlapCube, list_olap_cubes 15 | from mstrio.server import Job, JobStatus 16 | 17 | 18 | def inform_when_job_done(job: Job, interval: int = 10): 19 | """Helper function to check the status of the job every `interval` seconds 20 | until it is completed. It will print the error message if the job is 21 | completed with error. This works only when called in short time max 20 22 | minutes after triggering the refresh.""" 23 | stable_states = [JobStatus.COMPLETED, JobStatus.ERROR, JobStatus.STOPPED] 24 | try: 25 | while job.status not in stable_states: 26 | sleep(interval) 27 | job.fetch() 28 | print(f"Job is finished with error: {job.error_message}") 29 | except IServerError: 30 | print("Job is finished with success.") 31 | 32 | # Define a variable which can be later used in a script 33 | PROJECT_NAME = $project_name # Insert name of project here 34 | 35 | conn = get_connection(workstationData, project_name=PROJECT_NAME) 36 | 37 | 38 | # List available OLAP cubes 39 | cubes = list_olap_cubes(connection=conn) 40 | # Print the list of available OLAP cubes 41 | pprint(cubes) 42 | 43 | # To get existing OLAP cube you need to know its ID 44 | SELECTED_CUBE_ID = $selected_cube_id # Insert ID of cube on which you want to perform actions 45 | selected_cube = OlapCube(connection=conn, id=SELECTED_CUBE_ID) 46 | # Print the last update time of the selected OLAP cube 47 | before_last_update_time = selected_cube.last_update_time 48 | print(before_last_update_time) 49 | # Print the status of the selected OLAP cube 50 | before_status = selected_cube.status 51 | print(selected_cube.show_status()) 52 | # Refresh the selected OLAP cube 53 | job = selected_cube.refresh() 54 | 55 | # Now request for refresh was sent to the I-Server. It may take some time to 56 | # complete the refresh. You can check the status of the job to see if it is 57 | # completed or see if last update time has changed. 58 | 59 | # Job can be taken from the job object or initialized with the job ID 60 | JOB_ID = job.id 61 | my_job = Job(connection=conn, id=JOB_ID) 62 | 63 | # Here we will check the status of the job every 10 seconds until it is 64 | # completed. You can adjust the time interval to your needs. 65 | 66 | inform_when_job_done(job=my_job, interval=10) 67 | 68 | # Another way to check job status is by calling refresh_status method on the job 69 | # If the job can not be found on server when refreshing, the status will be 70 | # updated to JobStatus.COMPLETED 71 | my_job.refresh_status() 72 | 73 | # Now you can check the last update time of the selected OLAP cube to see if 74 | # the refresh was successful. 75 | selected_cube.fetch() 76 | after_last_update_time = selected_cube.last_update_time 77 | print(after_last_update_time) 78 | # Print the status of the selected OLAP cube 79 | after_status = selected_cube.status 80 | print(selected_cube.show_status()) 81 | 82 | # Other way to check if refresh is done is comparing last update time before 83 | # and after refresh. If they are different, the refresh was successful. 84 | if before_last_update_time != after_last_update_time: 85 | print("Refresh was successful.") 86 | -------------------------------------------------------------------------------- /code_snippets/report_cache.py: -------------------------------------------------------------------------------- 1 | """This is the demo script to show how to manage report caches. 2 | 3 | This script will not work without replacing parameters with real values. 4 | Its basic goal is to present what can be done with this module and to 5 | ease its usage. 6 | """ 7 | 8 | from mstrio.project_objects import ContentCache, Report 9 | from mstrio.connection import get_connection 10 | 11 | # Define a variable which can be later used in a script 12 | PROJECT_NAME = $project_name # Insert project ID here 13 | 14 | conn = get_connection(workstationData, project_name=PROJECT_NAME) 15 | 16 | # Get the project ID 17 | PROJECT_ID = conn.project_id 18 | 19 | # List all report caches for a given project 20 | # This can be done in two ways 21 | # Listing it through the Report class 22 | Report.list_caches(connection=conn, project_id=PROJECT_ID) 23 | 24 | # Listing it through the ContentCache class 25 | # We have to specify the type of the cache we want to list in this case 26 | ContentCache.list_caches(connection=conn, project_id=PROJECT_ID, type='report') 27 | 28 | # Define a variable which can be later used in a script 29 | REPORT_CACHE_ID = $cache_id # Insert ID of cache that you want to perform your actions upon 30 | 31 | # Initializing the cache 32 | REPORT_CACHE = ContentCache(connection=conn, id=REPORT_CACHE_ID) 33 | 34 | # Listing properties of a cache 35 | REPORT_CACHE.list_properties() 36 | 37 | # Deleting a specific cache 38 | REPORT_CACHE.delete(force=True) 39 | 40 | # Deleting all caches from a specific warehouse table 41 | WAREHOUSE_TABLE = $warehouse_table # Insert warehouse table name here 42 | cache_ids = [cache.id for cache in Report.list_caches(connection=conn, project_id=PROJECT_ID, wh_tables=WAREHOUSE_TABLE)] 43 | ContentCache.delete_caches(connection=conn, cache_ids=cache_ids, force=True) 44 | 45 | # Loading and unloading all caches in a project 46 | cache_ids = [cache.id for cache in Report.list_caches(connection=conn, project_id=PROJECT_ID)] 47 | ContentCache.load_caches(connection=conn, cache_ids=cache_ids) 48 | ContentCache.unload_caches(connection=conn, cache_ids=cache_ids) 49 | 50 | # Deleting invalid caches from a project 51 | cache_ids = [cache.id for cache in Report.list_caches(connection=conn, project_id=PROJECT_ID, status='invalid')] 52 | ContentCache.delete_caches(connection=conn, cache_ids=cache_ids, force=True) 53 | 54 | # Deleting invalid caches from multiple projects 55 | PROJECT_ID_2 = $project_id_2 # Insert project ID here 56 | PROJECTS = [PROJECT_ID, PROJECT_ID_2] 57 | for project in PROJECTS: 58 | cache_ids = [cache.id for cache in Report.list_caches(connection=conn, project_id=project, status='invalid')] 59 | ContentCache.delete_caches(connection=conn, cache_ids=cache_ids, force=True) 60 | -------------------------------------------------------------------------------- /code_snippets/schema_mgmt.py: -------------------------------------------------------------------------------- 1 | """This is the demo script to show how administrator can manage schema. 2 | 3 | This script will not work without replacing parameters with real values. 4 | Its basic goal is to present what can be done with this module and to 5 | ease its usage. 6 | """ 7 | 8 | from mstrio.helpers import IServerError 9 | from mstrio.modeling.schema import SchemaManagement, SchemaLockType, SchemaUpdateType 10 | 11 | from mstrio.connection import get_connection 12 | 13 | # Define a variable which can be later used in a script 14 | PROJECT_NAME = $project_name # Project to connect to 15 | 16 | conn = get_connection(workstationData, project_name=PROJECT_NAME) 17 | 18 | # create an object to manage the schema 19 | schema_mgmt = SchemaManagement(conn) 20 | 21 | # get lock type of the schema 22 | lock_t = schema_mgmt.lock_type 23 | 24 | # unlock schema and get its status 25 | schema_mgmt.unlock() 26 | lock_st = schema_mgmt.get_lock_status() 27 | 28 | # Lock schema and get its status. Schema can be locked also with type 29 | # `ABSOLUTE_CONSTITUENT` or `EXCLUSIVE_CONSTITUENT`. 30 | # Available values for SchemaLockType enum are in modeling/schema/schema_management.py 31 | schema_mgmt.lock(SchemaLockType.ABSOLUTE_INDIVIDUAL) 32 | lock_st = schema_mgmt.get_lock_status() 33 | 34 | # Reload schema. It is allowed not to provide an update type. Except for 35 | # types below `TABLE_KEY` or `LOGICAL_SIZE` are also available. 36 | # When schema is reloaded asynchronously then task is returned. 37 | # Available values for SchemaLockType enum are in modeling/schema/schema_management.py 38 | task = schema_mgmt.reload( 39 | update_types=[SchemaUpdateType.CLEAR_ELEMENT_CACHE, SchemaUpdateType.ENTRY_LEVEL] 40 | ) 41 | 42 | # get list of tasks which are stored within schema management object (tasks 43 | # are saved when `reload` is performed asynchronously) 44 | tasks = schema_mgmt.tasks 45 | 46 | # get all details about the first task which is stored within schema management 47 | # object 48 | task = schema_mgmt.get_task(task_index=0) 49 | 50 | # get all details about the last task which is stored within schema management 51 | # object 52 | task_st = schema_mgmt.get_task(task_index=-1) 53 | 54 | # Reload schema without asynchronous response. This will guarantee that the 55 | # schema reload task is completed before the next operation is performed. 56 | # If the task fails, an exception will be raised. This does not return any 57 | # intermediate task details. 58 | try: 59 | schema_mgmt.reload(respond_async=False) 60 | except IServerError as e: 61 | print(f"Error occurred: {e}") 62 | 63 | # unlock schema and get its status 64 | schema_mgmt.unlock() 65 | lock_st = schema_mgmt.get_lock_status() 66 | -------------------------------------------------------------------------------- /code_snippets/script_usage_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "applications.py": "Standard", 3 | "attributes.py": "Standard", 4 | "bots.py": "Standard", 5 | "connect.py": "Standard", 6 | "contact_group_mgmt.py": "Standard", 7 | "contacts.py": "Standard", 8 | "content_groups.py": "Standard", 9 | "create_super_cube.py": "Standard", 10 | "cube_cache.py": "Standard", 11 | "cube_report.py": "Standard", 12 | "datasource_mgmt.py": "Standard", 13 | "device_mgmt.py": "Standard", 14 | "dynamic_recipient_list.py": "Standard", 15 | "events.py": "Standard", 16 | "fact.py": "Standard", 17 | "fences.py": "Standard", 18 | "filter.py": "Standard", 19 | "incremental_refresh_report.py": "Standard", 20 | "intelligent_cube.py": "Standard", 21 | "job_monitor.py": "Standard", 22 | "languages.py": "Standard", 23 | "metrics.py": "Standard", 24 | "migration.py": "Standard", 25 | "object_mgmt.py": "Standard", 26 | "olap_cube_refresh_status.py": "Standard", 27 | "report_cache.py": "Standard", 28 | "project_mgmt.py": "Standard", 29 | "schedules.py": "Standard", 30 | "schema_mgmt.py": "Standard", 31 | "security_filters.py": "Standard", 32 | "security_roles_and_privileges.py": "Standard", 33 | "server_mgmt.py": "Standard", 34 | "shortcuts.py": "Standard", 35 | "subscription_mgmt.py": "Standard", 36 | "table_mgmt.py": "Standard", 37 | "transformation.py": "Standard", 38 | "translations.py": "Standard", 39 | "transmitter_mgmt.py": "Standard", 40 | "user_hierarchy_mgmt.py": "Standard", 41 | "user_library.py": "Standard", 42 | "user_mgmt.py": "Standard", 43 | "content_cache.py": "Standard", 44 | "document.py": "Standard", 45 | "dashboard.py": "Standard", 46 | "reports.py": "Standard", 47 | "vldb.py": "Standard", 48 | "project_languages_mgmt.py": "Standard", 49 | "license_mgmt.py": "Standard" 50 | } -------------------------------------------------------------------------------- /code_snippets/security_roles_and_privileges.py: -------------------------------------------------------------------------------- 1 | # Privileges 2 | from mstrio.access_and_security.privilege import Privilege 3 | from mstrio.users_and_groups import list_user_groups, list_users, User, UserGroup 4 | from mstrio.access_and_security.security_role import list_security_roles, SecurityRole 5 | from mstrio.connection import get_connection 6 | 7 | # Define a variable which can be later used in a script 8 | PROJECT_NAME = $project_name # Insert name of project here 9 | 10 | conn = get_connection(workstationData, project_name=PROJECT_NAME) 11 | 12 | # Define variables which can be later used in a script 13 | PRIVILEGE_NAME = $privilege_name # Insert name of edited privilege here 14 | PRIVILEGE_ID = $privilege_id # Insert ID of edited privilege here 15 | 16 | # get Privilege object by name 17 | priv = Privilege(conn, name=PRIVILEGE_NAME) 18 | # get Privilege object by id 19 | priv = Privilege(conn, id=PRIVILEGE_ID) 20 | 21 | # List Privileges and return objects or display in DataFrame 22 | Privilege.list_privileges(conn, to_dataframe=True, is_project_level_privilege='True') 23 | priv = Privilege.list_privileges(conn, id=[PRIVILEGE_ID]) 24 | for p in priv: 25 | print(p.id) 26 | priv[0].list_properties() 27 | 28 | # Define variables which can be later used in a script 29 | SECURITY_ROLE_NAME = $security_role_name # Insert name of newly created or accessed security role 30 | SECURITY_ROLE_DESCRIPTION = $security_role_description # Insert description of newly created or accessed security role 31 | 32 | # SecurityRoles 33 | # Create new SecurityRole 34 | new_role = SecurityRole.create( 35 | conn, 36 | name=SECURITY_ROLE_NAME, 37 | description=SECURITY_ROLE_DESCRIPTION, 38 | privileges=[PRIVILEGE_ID, PRIVILEGE_NAME] 39 | ) 40 | 41 | # List SecurityRoles and store the Objects 42 | all_roles = list_security_roles(conn) 43 | list_security_roles(conn, to_dataframe=True) 44 | 45 | # Create SecurityRole object by name or ID 46 | role = SecurityRole(conn, id=all_roles[0].id) 47 | SecurityRole(conn, id=all_roles[0].id) 48 | role = SecurityRole(conn, name=all_roles[0].name) 49 | SecurityRole(conn, name=all_roles[0].name) 50 | SecurityRole(connection=conn, name=SECURITY_ROLE_NAME) 51 | 52 | # List SecurityRole members 53 | role.list_members(project_name=PROJECT_NAME) 54 | 55 | # Define variables which can be later used in a script 56 | USERNAME = $username # Insert name of user to be assigned or revoked security role 57 | USER_GROUP_NAME = $user_group_name # Insert name of user group to be assigned or revoked security role 58 | 59 | # Grant/Revoke Security Role to users/usergroups 60 | user = User(conn, name=USERNAME) 61 | users = list_users(conn) 62 | group = UserGroup(conn, name=USER_GROUP_NAME) 63 | groups = list_user_groups(conn) 64 | 65 | # Grant/Revoke for Users 66 | role.grant_to(members=user, project=PROJECT_NAME) 67 | role.revoke_from(members=user, project=PROJECT_NAME) 68 | role.grant_to(members=users, project=PROJECT_NAME) 69 | role.revoke_from(members=users, project=PROJECT_NAME) 70 | 71 | # Grant/Revoke for UserGroups 72 | role.grant_to(members=group, project=PROJECT_NAME) 73 | role.revoke_from(members=group, project=PROJECT_NAME) 74 | 75 | # List Privileges 76 | role.list_privileges() 77 | role.list_privileges(to_dataframe=True) 78 | 79 | # Grant/ Revoke privileges to Security Role 80 | role.grant_privilege(privilege=[PRIVILEGE_ID]) 81 | role.revoke_privilege([PRIVILEGE_ID]) 82 | privs = list(role.list_privileges().keys()) 83 | role.revoke_all_privileges() 84 | role.grant_privilege(privilege=privs) 85 | -------------------------------------------------------------------------------- /code_snippets/shortcuts.py: -------------------------------------------------------------------------------- 1 | """This is the demo script to show how administrator can manage shortcuts. 2 | 3 | This script will not work without replacing parameters with real values. 4 | Its basic goal is to present what can be done with this module and to 5 | ease its usage. 6 | """ 7 | 8 | from mstrio.connection import get_connection 9 | from mstrio.modeling.metric import Metric 10 | from mstrio.object_management.shortcut import list_shortcuts 11 | 12 | 13 | # Create connection based on workstation data 14 | PROJECT_NAME = $project_name # Project to connect to 15 | conn = get_connection(workstationData, project_name=PROJECT_NAME) 16 | 17 | # Create a shortcut for an existing object, e.g. a metric 18 | METRIC_ID = $metric_id 19 | metric_object = Metric(conn, id=METRIC_ID) 20 | 21 | TARGET_FOLDER_ID = $target_folder_id 22 | shortcut = metric_object.create_shortcut(target_folder_id=TARGET_FOLDER_ID) 23 | 24 | # List shortcuts 25 | shortcuts = list_shortcuts(conn) 26 | for sc in shortcuts: 27 | print(sc) 28 | 29 | # list properties for a shortcut 30 | properties = shortcut.list_properties() 31 | for prop_name, prop_value in properties.items(): 32 | print(f"{prop_name}: {prop_value}") 33 | 34 | # alter properties of a shortcut 35 | SHORTCUT_NEW_NAME = $shortcut_new_name 36 | shortcut.alter(name=SHORTCUT_NEW_NAME) 37 | 38 | # delete a shortcut 39 | shortcut.delete(force=True) 40 | -------------------------------------------------------------------------------- /code_snippets/user_library.py: -------------------------------------------------------------------------------- 1 | """This is the demo script to show how administrator can manage documents and 2 | dashboards in users' libraries. 3 | 4 | This script will not work without replacing parameters with real values. 5 | Its basic goal is to present what can be done with this module and to 6 | ease its usage. 7 | """ 8 | 9 | from mstrio.project_objects import Dashboard, Document, list_documents,list_dashboards 10 | from mstrio.users_and_groups import list_user_groups, list_users, User, UserGroup 11 | 12 | from mstrio.connection import get_connection 13 | 14 | # Define a variable which can be later used in a script 15 | PROJECT_NAME = $project_name # Project to connect to 16 | 17 | conn = get_connection(workstationData, project_name=PROJECT_NAME) 18 | 19 | # list all dashboards and documents within a project to which we have connection 20 | dashboards = list_dashboards(connection=conn) 21 | docs = list_documents(connection=conn) 22 | 23 | # Define variables which can be later used in a script 24 | DOCUMENT_NAME = $document_name 25 | DASHBOARD_NAME = $dashboard_name 26 | 27 | # get document and dashboard from by name or id and publish them to a library of 28 | # an authenticated user 29 | doc = Document(connection=conn, name=DOCUMENT_NAME) 30 | dashboard = Dashboard(connection=conn, name=DASHBOARD_NAME) 31 | doc.publish() 32 | dashboard.publish() 33 | 34 | # Define variables which can be later used in a script 35 | USER_ID_1 = $user_id_1 36 | USER_ID_2 = $user_id_2 37 | 38 | # list all users and get 2 of them 39 | users = list_users(connection=conn) 40 | user_1 = User(connection=conn, id=USER_ID_1) 41 | user_2 = User(connection=conn, id=USER_ID_2) 42 | 43 | # share one dashboard/document to a given user(s) by passing user object(s) 44 | # or id(s) 45 | dashboard.publish(recipients=user_1) 46 | dashboard.publish(recipients=[USER_ID_1, USER_ID_2]) 47 | # analogously we can take away dashboard(s)/document(s) from the library of the 48 | # given user(s) 49 | dashboard.unpublish(recipients=[user_1, user_2]) 50 | dashboard.unpublish(recipients=USER_ID_1) 51 | 52 | # Define a variable which can be later used in a script 53 | USER_GROUP_NAME = $user_group_name 54 | 55 | # list all user groups and get one of them 56 | user_groups_ = list_user_groups(connection=conn) 57 | user_group_ = UserGroup(connection=conn, name=USER_GROUP_NAME) 58 | 59 | # add users to given user group 60 | user_group_.add_users(users=[user_1, user_2]) 61 | 62 | # Define variables which can be later used in a script 63 | DOCUMENT_ID_1 = $document_id_1 64 | DOCUMENT_ID_2 = $document_id_2 65 | DOCUMENT_ID_3 = $document_id_3 66 | 67 | # get documents with given ids to give to the user group and users which belong 68 | # to it 69 | docs_to_publish = list_documents( 70 | connection=conn, 71 | id=[DOCUMENT_ID_1, DOCUMENT_ID_2, DOCUMENT_ID_3], 72 | ) 73 | for doc in docs_to_publish: 74 | doc.publish(recipients=user_group_) 75 | -------------------------------------------------------------------------------- /deprecation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroStrategy/mstrio-py/da214217478831a20fb9f20f6f148d4a1ec80ab7/deprecation.png -------------------------------------------------------------------------------- /docs/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Disclaimer! 2 | 3 | __Before creating a pull request, consider the following:__ 4 | 5 | > `mstrio-py` is a "source-available" application. Therefore this public repository is a code storage for its availability, not a place for contributions. All development happens internally. If you have any suggestions, enhancement requests or you want to raise a defect, please __contact MicroStrategy Support__ or create [a GitHub Issue using one of the templates](https://github.com/MicroStrategy/mstrio-py/issues/new/choose). 6 | > 7 | > If you still want to create a PR here to suggest direct code changes, please __create a GitHub Issue__ as well and mention the PR number in its content. 8 | -------------------------------------------------------------------------------- /mstrio/__init__.py: -------------------------------------------------------------------------------- 1 | """mstrio: Simple and Secure Access to Strategy One Data 2 | 3 | Mstrio provides a high-level interface for Python and is designed 4 | to give data scientists, developers, and administrators simple and secure 5 | access to their Strategy One environment. 6 | 7 | It wraps Strategy One REST APIs into simple workflows, allowing users 8 | to fetch data from cubes and reports, create new datasets, add new data 9 | to existing datasets, and manage Users/User Groups, Servers, Projects, 10 | and more. Since it enforces Strategy One’s user and object security model, 11 | you don’t need to worry about setting up separate security rules. 12 | 13 | With mstrio-py for data science, it’s easy to integrate cross-departmental, 14 | trustworthy business data in machine learning workflows and enable 15 | decision-makers to take action on predictive insights in Strategy One Reports, 16 | Dashboards, HyperIntelligence Cards, and customized, embedded analytical 17 | applications. 18 | 19 | With mstrio-py for system administration, it’s easy to minimize costs by 20 | automating critical, time-consuming administrative tasks, even enabling 21 | administrators to leverage the power of Python to address complex 22 | administrative workflows for maintaining a Strategy One environment. 23 | """ 24 | 25 | __title__ = "mstrio-py" 26 | __version__ = "11.5.5.101" # NOSONAR 27 | __license__ = "Apache License 2.0" 28 | __description__ = "Python interface for the Strategy One REST API" 29 | __author__ = "Strategy One" 30 | __author_email__ = "bkaczynski@strategy.com" 31 | 32 | from .utils.dev_helpers import what_can_i_do_with # noqa: F401 33 | -------------------------------------------------------------------------------- /mstrio/access_and_security/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .privilege import Privilege, PrivilegeList 3 | from .privilege_mode import PrivilegeMode 4 | from .security_role import SecurityRole, list_security_roles 5 | -------------------------------------------------------------------------------- /mstrio/access_and_security/privilege_mode.py: -------------------------------------------------------------------------------- 1 | from enum import auto 2 | 3 | from mstrio.utils.enum_helper import AutoUpperName 4 | 5 | 6 | class PrivilegeMode(AutoUpperName): 7 | ALL = auto() 8 | INHERITED = auto() 9 | GRANTED = auto() 10 | -------------------------------------------------------------------------------- /mstrio/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroStrategy/mstrio-py/da214217478831a20fb9f20f6f148d4a1ec80ab7/mstrio/api/__init__.py -------------------------------------------------------------------------------- /mstrio/api/change_journal.py: -------------------------------------------------------------------------------- 1 | from requests import Response 2 | 3 | from mstrio.connection import Connection 4 | from mstrio.utils.error_handlers import ErrorHandler 5 | 6 | 7 | @ErrorHandler(err_msg="Error creating a Change Journal search instance.") 8 | def create_change_journal_search_instance( 9 | connection: Connection, body: dict, fields: str | None = None 10 | ) -> Response: 11 | """Create a Change Journal search instance. 12 | 13 | Args: 14 | connection (Connection): Strategy One connection object returned by 15 | `connection.Connection()` 16 | body (dict): JSON-formatted body of the search instance 17 | fields (str, optional): Comma separated top-level field whitelist. This 18 | allows client to selectively retrieve part of the response model 19 | 20 | Returns: 21 | HTTP response object returned by the Strategy One REST server. 22 | """ 23 | return connection.post( 24 | endpoint='/api/changeJournal', json=body, params={'fields': fields} 25 | ) 26 | 27 | 28 | @ErrorHandler(err_msg="Error getting Change Journal search results") 29 | def get_change_journal_search_results( 30 | connection: Connection, 31 | search_id: str, 32 | offset: int = 0, 33 | limit: int = -1, 34 | fields: str | None = None, 35 | ) -> Response: 36 | """Get Change Journal search results. 37 | 38 | Args: 39 | connection (Connection): Strategy One connection object returned by 40 | `connection.Connection()` 41 | search_id (str): ID of the search instance 42 | offset (int, optional): Starting point within the collection of 43 | returned search results 44 | limit (int, optional): Maximum number of items returned for a single 45 | search request 46 | fields (str, optional): Comma separated top-level field whitelist. This 47 | allows client to selectively retrieve part of the response model 48 | 49 | Returns: 50 | HTTP response object returned by the Strategy One REST server. 51 | """ 52 | return connection.get( 53 | endpoint=f'/api/changeJournal/{search_id}', 54 | params={'limit': limit, 'offset': offset, 'fields': fields}, 55 | ) 56 | -------------------------------------------------------------------------------- /mstrio/api/changesets.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from mstrio.utils.error_handlers import ErrorHandler 4 | 5 | if TYPE_CHECKING: 6 | from mstrio.connection import Connection 7 | 8 | 9 | @ErrorHandler(err_msg="Error creating a new changeset.") 10 | def create_changeset( 11 | connection: 'Connection', 12 | project_id: str = None, 13 | schema_edit: bool = False, 14 | error_msg: str = None, 15 | ): 16 | """Create a new changeset for modelling manipulations.""" 17 | return connection.post( 18 | endpoint='/api/model/changesets', 19 | headers={'X-MSTR-ProjectID': project_id}, 20 | params={'schemaEdit': str(schema_edit).lower()}, 21 | ) 22 | 23 | 24 | @ErrorHandler(err_msg="Error committing changeset {id} changes to the metadata.") 25 | def commit_changeset_changes( 26 | connection: 'Connection', id: str, error_msg: str = None, throw_error=True 27 | ): 28 | """Commit the changeset changes to metadata.""" 29 | return connection.post( 30 | endpoint=f'/api/model/changesets/{id}/commit', 31 | headers={'X-MSTR-MSChanget': id}, 32 | params={'changesetId': id}, 33 | ) 34 | 35 | 36 | @ErrorHandler(err_msg="Error deleting the changeset with ID {id}") 37 | def delete_changeset(connection: 'Connection', id: str, error_msg: str = None): 38 | """Delete the changeset.""" 39 | return connection.delete( 40 | endpoint=f'/api/model/changesets/{id}', 41 | headers={'X-MSTR-MSChanget': id}, 42 | params={'changesetId': id}, 43 | ) 44 | -------------------------------------------------------------------------------- /mstrio/api/drivers.py: -------------------------------------------------------------------------------- 1 | from mstrio.connection import Connection 2 | from mstrio.utils.error_handlers import ErrorHandler 3 | 4 | 5 | @ErrorHandler(err_msg="Error getting Drivers.") 6 | def get_drivers( 7 | connection: Connection, 8 | error_msg: str | None = None, 9 | ): 10 | """Get information for all drivers. 11 | 12 | Args: 13 | connection: Strategy One REST API connection object 14 | error_msg (string, optional): Custom Error Message for Error Handling 15 | 16 | Returns: 17 | Complete HTTP response object. Expected status is 200. 18 | """ 19 | 20 | return connection.get( 21 | endpoint='/api/drivers', 22 | ) 23 | 24 | 25 | @ErrorHandler(err_msg="Error getting Driver with ID {id}") 26 | def get_driver(connection: Connection, id: str, error_msg: str | None = None): 27 | """Get driver by a specific ID. 28 | 29 | Args: 30 | connection: Strategy One REST API connection object 31 | id: ID of the driver 32 | error_msg (string, optional): Custom Error Message for Error Handling 33 | 34 | Returns: 35 | Complete HTTP response object. Expected status is 200. 36 | """ 37 | return connection.get( 38 | endpoint=f'/api/drivers/{id}', 39 | ) 40 | 41 | 42 | @ErrorHandler(err_msg="Error updating Driver with ID {id}") 43 | def update_driver( 44 | connection: Connection, 45 | id: str, 46 | body: dict, 47 | error_msg: str | None = None, 48 | ): 49 | """Update a driver. 50 | 51 | Args: 52 | connection: Strategy One REST API connection object 53 | id: ID of the driver 54 | body: Driver update info. 55 | error_msg (string, optional): Custom Error Message for Error Handling 56 | 57 | Returns: 58 | Complete HTTP response object. Expected status is 200. 59 | """ 60 | return connection.patch( 61 | endpoint=f'/api/drivers/{id}', 62 | json=body, 63 | ) 64 | -------------------------------------------------------------------------------- /mstrio/api/emails.py: -------------------------------------------------------------------------------- 1 | from mstrio.connection import Connection 2 | from mstrio.utils.error_handlers import ErrorHandler 3 | 4 | 5 | @ErrorHandler(err_msg='Error sending an email') 6 | def send_email(connection: 'Connection', body: dict, error_msg: str | None = None): 7 | """Send an email to specified recipients. 8 | 9 | Args: 10 | connection (Connection): Strategy One connection object returned by 11 | `connection.Connection()` 12 | body (json): JSON-formatted data used to send an email 13 | error_msg (string, optional): Custom Error Message for Error Handling 14 | 15 | Returns: 16 | HTTP response object returned by the Strategy One REST server.""" 17 | return connection.post(endpoint='/api/emails', json=body) 18 | -------------------------------------------------------------------------------- /mstrio/api/facts.py: -------------------------------------------------------------------------------- 1 | from mstrio.connection import Connection 2 | from mstrio.utils.api_helpers import changeset_manager, unpack_information 3 | from mstrio.utils.error_handlers import ErrorHandler 4 | 5 | 6 | @unpack_information 7 | @ErrorHandler(err_msg="Error reading fact with ID: {id}.") 8 | def read_fact( 9 | connection: 'Connection', 10 | id: str, 11 | project_id: str = None, 12 | changeset_id: str = None, 13 | show_expression_as: str | None = None, 14 | show_potential_tables: bool = False, 15 | show_fields: str | None = None, 16 | ): 17 | if project_id is None: 18 | connection._validate_project_selected() 19 | project_id = connection.project_id 20 | spt = ( 21 | str(show_potential_tables).lower() 22 | if show_potential_tables is not None 23 | else None 24 | ) 25 | return connection.get( 26 | endpoint=f'/api/model/facts/{id}', 27 | headers={ 28 | 'X-MSTR-ProjectID': project_id, 29 | 'X-MSTR-MS-Changeset': changeset_id, 30 | }, 31 | params={ 32 | 'showExpressionAs': show_expression_as, 33 | 'showPotentialTables': spt, 34 | 'showFields': show_fields, 35 | }, 36 | ) 37 | 38 | 39 | @unpack_information 40 | @ErrorHandler(err_msg="Error creating a fact.") 41 | def create_fact( 42 | connection: 'Connection', 43 | body: dict, 44 | show_expression_as: str | None = None, 45 | show_potential_tables: bool = False, 46 | ): 47 | spt = ( 48 | str(show_potential_tables).lower() 49 | if show_potential_tables is not None 50 | else None 51 | ) 52 | with changeset_manager(connection) as changeset_id: 53 | return connection.post( 54 | endpoint='/api/model/facts', 55 | headers={'X-MSTR-MS-Changeset': changeset_id}, 56 | params={ 57 | 'showExpressionAs': show_expression_as, 58 | 'showPotentialTables': spt, 59 | }, 60 | json=body, 61 | ) 62 | 63 | 64 | @unpack_information 65 | @ErrorHandler(err_msg="Error updating fact with ID: {id}.") 66 | def update_fact( 67 | connection: 'Connection', 68 | id: str, 69 | body: dict, 70 | show_expression_as: str | None = None, 71 | show_potential_tables: bool = False, 72 | ): 73 | spt = ( 74 | str(show_potential_tables).lower() 75 | if show_potential_tables is not None 76 | else None 77 | ) 78 | with changeset_manager(connection) as changeset_id: 79 | return connection.put( 80 | endpoint=f'/api/model/facts/{id}', 81 | headers={'X-MSTR-MS-Changeset': changeset_id}, 82 | params={ 83 | 'showExpressionAs': show_expression_as, 84 | 'showPotentialTables': spt, 85 | }, 86 | json=body, 87 | ) 88 | -------------------------------------------------------------------------------- /mstrio/api/gateways.py: -------------------------------------------------------------------------------- 1 | from mstrio.connection import Connection 2 | from mstrio.utils.error_handlers import ErrorHandler 3 | 4 | 5 | @ErrorHandler(err_msg="Error getting Gateways.") 6 | def get_gateways( 7 | connection: Connection, 8 | error_msg: str | None = None, 9 | ): 10 | """Get information for all gateways. 11 | 12 | Args: 13 | connection: Strategy One REST API connection object 14 | error_msg (string, optional): Custom Error Message for Error Handling 15 | 16 | Returns: 17 | Complete HTTP response object. Expected status is 200. 18 | """ 19 | 20 | return connection.get( 21 | endpoint='/api/gateways', 22 | ) 23 | 24 | 25 | @ErrorHandler(err_msg='Error getting Gateway with ID {id}') 26 | def get_gateway(connection: Connection, id: str, error_msg: str | None = None): 27 | """Get gateway by a specific ID. 28 | 29 | Args: 30 | connection: Strategy One REST API connection object 31 | id: ID of the gateway 32 | error_msg (string, optional): Custom Error Message for Error Handling 33 | 34 | Returns: 35 | Complete HTTP response object. Expected status is 200. 36 | """ 37 | return connection.get( 38 | endpoint=f'/api/gateways/{id}', 39 | ) 40 | -------------------------------------------------------------------------------- /mstrio/api/hierarchies.py: -------------------------------------------------------------------------------- 1 | from mstrio.connection import Connection 2 | from mstrio.utils.api_helpers import changeset_manager 3 | from mstrio.utils.error_handlers import ErrorHandler 4 | 5 | 6 | @ErrorHandler(err_msg="Error getting attribute {id} relationship.") 7 | def get_attribute_relationships(connection: Connection, id: str, project_id: str): 8 | """Get relationship(s) of an attribute 9 | 10 | Args: 11 | connection: Strategy One REST API connection object 12 | id: ID of an attribute 13 | project_id: ID of a project 14 | 15 | Return: 16 | HTTP response object. Expected status: 200 17 | """ 18 | with changeset_manager(connection) as changeset_id: 19 | return connection.get( 20 | endpoint=f'/api/model/systemHierarchy/attributes/{id}/relationships', 21 | headers={ 22 | 'X-MSTR-MS-Changeset': changeset_id, 23 | 'X-MSTR-ProjectID': project_id, 24 | }, 25 | ) 26 | 27 | 28 | @ErrorHandler(err_msg='Error updating attribute {id} relationship.') 29 | def update_attribute_relationships( 30 | connection: Connection, 31 | id: str, 32 | body: dict, 33 | ): 34 | """Update relationship(s) of an attribute 35 | 36 | Args: 37 | connection: Strategy One REST API connection object 38 | id: ID of an attribute 39 | body: JSON-formatted definition of the attribute relationships 40 | 41 | Return: 42 | HTTP response object. Expected status: 200 43 | """ 44 | with changeset_manager(connection) as changeset_id: 45 | return connection.put( 46 | endpoint=f'/api/model/systemHierarchy/attributes/{id}/relationships', 47 | headers={'X-MSTR-MS-Changeset': changeset_id}, 48 | json=body, 49 | ) 50 | -------------------------------------------------------------------------------- /mstrio/api/hooks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from json.decoder import JSONDecodeError 4 | 5 | from mstrio.utils.helper import exception_handler 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | def print_url( 11 | response, *args, **kwargs # NOSONAR required for hook to session objects 12 | ): 13 | """Response hook to print url for debugging.""" 14 | logger.debug(response.url) 15 | 16 | 17 | def save_response( 18 | response, *args, **kwargs # NOSONAR required for hook to session objects 19 | ): 20 | """Response hook to save REST API responses to files structured by the API 21 | family.""" 22 | import json 23 | from pathlib import Path 24 | 25 | if response.status_code != 204: 26 | # Generate file name 27 | base_path = Path(__file__).parents[2] / 'tests/resources/auto-api-responses/' 28 | url = response.url.rsplit('api/', 1)[1] 29 | temp_path = url.split('/') 30 | file_name = '-'.join(temp_path[1:]) if len(temp_path) > 1 else temp_path[0] 31 | file_name = f'{file_name}-{response.request.method}' 32 | file_path = base_path if len(temp_path) == 1 else base_path / temp_path[0] 33 | path = str(file_path / file_name) 34 | 35 | # Create target directory & all intermediate directories if don't exists 36 | if not os.path.exists(str(file_path)): 37 | os.makedirs(file_path) 38 | print("Directory ", file_path, " created ") 39 | else: 40 | print("Directory ", file_path, " already exists") 41 | 42 | # Dump the response to JSON and Pickle 43 | # with open(path + '.pkl', 'wb') as f: 44 | # pickle.dump(response, f) 45 | with open(path + '.json', 'w') as f: 46 | try: 47 | json.dump(response.json(), f) 48 | except JSONDecodeError: 49 | exception_handler( 50 | "Could not decode response. Skipping creating JSON file.", Warning 51 | ) 52 | -------------------------------------------------------------------------------- /mstrio/api/library.py: -------------------------------------------------------------------------------- 1 | from mstrio.utils.error_handlers import ErrorHandler 2 | 3 | 4 | @ErrorHandler(err_msg="Error getting document with ID {id}") 5 | def get_document(connection, id, error_msg=None): 6 | """Get information for a document with document Id. 7 | 8 | Args: 9 | connection: Strategy One REST API connection object 10 | id (string): Document ID 11 | error_msg (string, optional): Custom Error Message for Error Handling 12 | 13 | Returns: 14 | Complete HTTP response object. 15 | """ 16 | endpoint = f'/api/library/{id}' 17 | return connection.get(endpoint=endpoint) 18 | 19 | 20 | @ErrorHandler(err_msg="Error unpublishing document with ID {id}") 21 | def unpublish_document(connection, id, error_msg=None): 22 | """Unpublish a previously published document. This makes the document no 23 | longer available in the library of each user it was originally published 24 | to. 25 | 26 | Args: 27 | connection: Strategy One REST API connection object 28 | id (string): Document ID 29 | error_msg (string, optional): Custom Error Message for Error Handling 30 | 31 | Returns: 32 | Complete HTTP response object. 33 | """ 34 | endpoint = f'/api/library/{id}' 35 | return connection.delete(endpoint=endpoint) 36 | 37 | 38 | @ErrorHandler(err_msg="Error unpublishing document with ID {document_id}") 39 | def unpublish_document_for_user(connection, document_id, user_id, error_msg=None): 40 | """Unpublish a previously published document. This makes the document no 41 | longer available in the library of each user specified in `user_id` 42 | 43 | Args: 44 | connection: Strategy One REST API connection object 45 | document_id (string): Document ID 46 | user_id (string): user ID 47 | error_msg (string, optional): Custom Error Message for Error Handling 48 | 49 | Returns: 50 | Complete HTTP response object. 51 | """ 52 | connection._validate_project_selected() 53 | endpoint = f'/api/library/{document_id}/recipients/{user_id}' 54 | return connection.delete(endpoint=endpoint) 55 | 56 | 57 | @ErrorHandler(err_msg="Error getting library.") 58 | def get_library(connection, error_msg=None): 59 | """Get the library for the authenticated user. 60 | 61 | Args: 62 | connection: Strategy One REST API connection object 63 | error_msg (string, optional): Custom Error Message for Error Handling 64 | 65 | Returns: 66 | Complete HTTP response object. 67 | """ 68 | endpoint = '/api/library' 69 | return connection.get(endpoint=endpoint, headers={'X-MSTR-ProjectID': None}) 70 | 71 | 72 | @ErrorHandler(err_msg="Error publishing document.") 73 | def publish_document(connection, body, error_msg=None): 74 | """Publish a document to users or user groups in a specific project. 75 | 76 | Args: 77 | connection: Strategy One REST API connection object 78 | body: JSON-formatted definition of the dataset. Generated by 79 | `utils.formjson()`. 80 | error_msg (string, optional): Custom Error Message for Error Handling 81 | 82 | Returns: 83 | Complete HTTP response object. 84 | """ 85 | connection._validate_project_selected() 86 | endpoint = '/api/library' 87 | return connection.post(endpoint=endpoint, json=body) 88 | -------------------------------------------------------------------------------- /mstrio/api/misc.py: -------------------------------------------------------------------------------- 1 | from requests import Response 2 | from requests.exceptions import SSLError 3 | 4 | from mstrio.utils.helper import response_handler 5 | 6 | 7 | def server_status(connection) -> 'Response | None': 8 | """ 9 | Args: 10 | connection: Strategy One REST API connection object 11 | Returns: 12 | Complete HTTP response object 13 | """ 14 | 15 | try: 16 | response = connection.get(skip_expiration_check=True, endpoint='/api/status') 17 | except SSLError as exc: 18 | raise SSLError( 19 | "SSL certificate error.\nPlease double check that the link you " 20 | "are using comes from a trusted source. If you trust the URL " 21 | "provided please specify parameter 'ssl_verify' to 'False' in the " 22 | "'Connection' class.\n\nCheck readme for more details." 23 | ) from exc 24 | 25 | if not response.ok and response.status_code != 401: 26 | response_handler(response, "Failed to check server status") 27 | elif response.status_code == 401: 28 | return None 29 | return response 30 | -------------------------------------------------------------------------------- /mstrio/api/registrations.py: -------------------------------------------------------------------------------- 1 | from mstrio.utils.error_handlers import ErrorHandler 2 | 3 | 4 | @ErrorHandler( 5 | err_msg="Error obtaining the list of registered nodes from the Strategy One " 6 | "deployment." 7 | ) 8 | def get_nodes(connection, error_msg=None): 9 | """Obtain the list of registered nodes from the Strategy One deployment. 10 | 11 | Args: 12 | connection(object): Strategy One connection object returned by 13 | 'connection.Connection(). 14 | error_msg (string, optional): Custom Error Message for Error Handling 15 | """ 16 | return connection.get(endpoint='/api/registrations/nodes') 17 | 18 | 19 | @ErrorHandler( 20 | err_msg="Error obtaining the list of registered services available from " 21 | "the Strategy One deployment" 22 | ) 23 | def get_services(connection, error_msg=None): 24 | """Obtain the list of registered services available from the Strategy One 25 | deployment. 26 | 27 | Args: 28 | connection(object): Strategy One connection object returned by 29 | 'connection.Connection(). 30 | error_msg (string, optional): Custom Error Message for Error Handling 31 | """ 32 | return connection.get(endpoint='/api/registrations/services') 33 | 34 | 35 | @ErrorHandler( 36 | err_msg="Error obtaining the metadata information for the registered services" 37 | " available from the Strategy One deployment." 38 | ) 39 | def get_services_metadata(connection, error_msg=None): 40 | """Obtain the metadata information for the registered services available 41 | from the Strategy One deployment. 42 | 43 | Args: 44 | connection(object): Strategy One connection object returned by 45 | "connection.Connection(). 46 | error_msg (string, optional): Custom Error Message for Error Handling 47 | """ 48 | return connection.get(endpoint='/api/registrations/services/metadata') 49 | 50 | 51 | @ErrorHandler(err_msg="Error to start/stop service") 52 | def start_stop_service( 53 | connection, login, password, name, id, address, action='START', error_msg=None 54 | ): 55 | """Start or stop registered service. 56 | 57 | Args: 58 | connection(object): Strategy One connection object returned by 59 | 'connection.Connection() 60 | login (string): login for SSH operation 61 | password (string): password for SSH operation 62 | name(string): name of the service 63 | id(string): name of the service 64 | action(string): one of "START" or "STOP" 65 | error_msg (string, optional): Custom Error Message for Error Handling 66 | Returns: 67 | Complete HTTP response object. 68 | """ 69 | 70 | body = { 71 | "name": name, 72 | "id": id, 73 | "action": action, 74 | "address": address, 75 | "login": login, 76 | "password": password, 77 | } 78 | endpoint = '/api/registrations/services/control' 79 | return connection.post(endpoint=endpoint, json=body) 80 | -------------------------------------------------------------------------------- /mstrio/api/runtimes.py: -------------------------------------------------------------------------------- 1 | from requests import Response 2 | 3 | from mstrio.connection import Connection 4 | from mstrio.utils.error_handlers import ErrorHandler 5 | 6 | 7 | @ErrorHandler(err_msg="Error getting Runtimes.") 8 | def list_runtimes( 9 | connection: Connection, fields: str | None = None, error_msg: str | None = None 10 | ) -> Response: 11 | """Get list of runtimes. 12 | 13 | Args: 14 | connection (Connection): Strategy One connection object returned by 15 | `connection.Connection()` 16 | fields (str, optional): Comma separated top-level field whitelist. This 17 | allows client to selectively retrieve part of the response model, 18 | defaults to None 19 | error_msg (str, optional): Custom Error Message for Error Handling 20 | 21 | Returns: 22 | HTTP response object returned by the Strategy One REST server. 23 | """ 24 | 25 | return connection.get(endpoint='/api/runtimes', params={'fields': fields}) 26 | 27 | 28 | @ErrorHandler(err_msg="Error creating Runtime.") 29 | def create_runtime( 30 | connection: Connection, 31 | body: dict, 32 | fields: str | None = None, 33 | error_msg: str | None = None, 34 | ) -> Response: 35 | """Create a runtime. 36 | 37 | Args: 38 | connection (Connection): Strategy One connection object returned by 39 | `connection.Connection()` 40 | body (dict): JSON-formatted body of the new runtime 41 | fields (str, optional): Comma separated top-level field whitelist. This 42 | allows client to selectively retrieve part of the response model, 43 | defaults to None 44 | error_msg (str, optional): Custom Error Message for Error Handling 45 | 46 | Returns: 47 | HTTP response object returned by the Strategy One REST server. 48 | """ 49 | 50 | return connection.post( 51 | endpoint='/api/runtimes', json=body, params={'fields': fields} 52 | ) 53 | 54 | 55 | @ErrorHandler(err_msg="Error deleting Runtime with ID: {id}") 56 | def delete_runtime( 57 | connection: Connection, id: str, error_msg: str | None = None 58 | ) -> Response: 59 | """Delete a runtime. 60 | 61 | Args: 62 | connection (Connection): Strategy One connection object returned by 63 | `connection.Connection()` 64 | id (str): ID of the runtime to be deleted 65 | error_msg (str, optional): Custom Error Message for Error Handling 66 | 67 | Returns: 68 | HTTP response object returned by the Strategy One REST server. 69 | """ 70 | 71 | return connection.delete(endpoint=f'/api/runtimes/{id}') 72 | -------------------------------------------------------------------------------- /mstrio/api/schema.py: -------------------------------------------------------------------------------- 1 | from mstrio.connection import Connection 2 | from mstrio.utils.error_handlers import ErrorHandler 3 | 4 | 5 | @ErrorHandler(err_msg="Error reading lock status of the schema.") 6 | def read_lock_status(connection: 'Connection', project_id: str | None = None): 7 | """Read lock status of the schema.""" 8 | project_id = project_id if project_id is not None else connection.project_id 9 | return connection.get( 10 | endpoint='/api/model/schema/lock', 11 | headers={'X-MSTR-ProjectID': project_id}, 12 | ) 13 | 14 | 15 | @ErrorHandler(err_msg="Error placing the lock of type `{lock_type}` on the schema.") 16 | def lock_schema( 17 | connection: 'Connection', 18 | lock_type: str, 19 | project_id: str | None = None, 20 | throw_error: bool = True, 21 | ): 22 | """Places a lock on the schema.""" 23 | project_id = project_id if project_id is not None else connection.project_id 24 | return connection.post( 25 | endpoint='/api/model/schema/lock', 26 | headers={'X-MSTR-ProjectID': project_id}, 27 | json={'lockType': lock_type}, 28 | ) 29 | 30 | 31 | @ErrorHandler(err_msg="Error unlocking the schema.") 32 | def unlock_schema( 33 | connection: 'Connection', 34 | lock_type: str | None = None, 35 | project_id: str | None = None, 36 | throw_error: bool = True, 37 | ): 38 | """Unlocks the schema.""" 39 | project_id = project_id if project_id is not None else connection.project_id 40 | return connection.delete( 41 | endpoint='/api/model/schema/lock', 42 | headers={'X-MSTR-ProjectID': project_id}, 43 | params={'lockType': lock_type}, 44 | ) 45 | 46 | 47 | @ErrorHandler(err_msg="Error reloading the schema.") 48 | def reload_schema( 49 | connection: 'Connection', 50 | project_id: str | None = None, 51 | update_types: list[str] | None = None, 52 | prefer_async: bool = False, 53 | ): 54 | """Reloads (updates) the schema.""" 55 | project_id = project_id if project_id is not None else connection.project_id 56 | update_types = update_types if update_types else [] 57 | prefer_async = 'respond-async' if prefer_async else None 58 | return connection.post( 59 | endpoint='/api/model/schema/reload', 60 | headers={ 61 | 'X-MSTR-ProjectID': project_id, 62 | 'Prefer': prefer_async, 63 | }, 64 | json={'updateTypes': update_types}, 65 | ) 66 | 67 | 68 | @ErrorHandler(err_msg="Error reading status of the task with ID: {task_id}.") 69 | def read_task_status( 70 | connection: 'Connection', task_id: str, project_id: str | None = None 71 | ): 72 | """Read the status of the task.""" 73 | project_id = project_id if project_id is not None else connection.project_id 74 | return connection.get( 75 | endpoint=f'/api/model/tasks/{task_id}', 76 | headers={'X-MSTR-ProjectID': project_id}, 77 | ) 78 | -------------------------------------------------------------------------------- /mstrio/api/scripts.py: -------------------------------------------------------------------------------- 1 | from requests import Response 2 | 3 | from mstrio.connection import Connection 4 | from mstrio.object_management import Object, full_search 5 | from mstrio.utils.error_handlers import ErrorHandler 6 | 7 | 8 | def list_scripts( 9 | connection: Connection, project_id: str, to_dictionary=True 10 | ) -> list[dict] | list[Object]: 11 | """Get a list of scripts. 12 | 13 | Args: 14 | connection (Connection): Strategy One connection object returned by 15 | `connection.Connection()` 16 | project_id (str): ID of the project 17 | to_dictionary (bool, optional): If True returns dict (default), 18 | otherwise returns Object. 19 | 20 | Returns: 21 | List of scripts. 22 | """ 23 | return full_search( 24 | connection, project=project_id, object_types=76, to_dictionary=to_dictionary 25 | ) 26 | 27 | 28 | @ErrorHandler(err_msg="Error creating Script.") 29 | def create_script( 30 | connection: Connection, project_id: str, body: dict, error_msg: str | None = None 31 | ) -> Response: 32 | """Create a script. 33 | 34 | Args: 35 | connection (Connection): Strategy One connection object returned by 36 | `connection.Connection()` 37 | body (dict): JSON-formatted body of the new script 38 | project_id (str): ID of the project 39 | error_msg (str, optional): Custom Error Message for Error Handling 40 | 41 | Returns: 42 | HTTP response object returned by the Strategy One REST server. 43 | """ 44 | return connection.post( 45 | endpoint='/api/scripts', 46 | headers={'X-MSTR-ProjectID': project_id}, 47 | json=body, 48 | ) 49 | 50 | 51 | @ErrorHandler(err_msg="Error deleting Script with ID: {id}") 52 | def delete_script( 53 | connection: Connection, id: str, project_id: str, error_msg: str | None = None 54 | ) -> Response: 55 | """Delete a script. 56 | 57 | Args: 58 | connection (Connection): Strategy One connection object returned by 59 | `connection.Connection()` 60 | id (str): ID of the script to be deleted 61 | project_id (str): ID of the project 62 | error_msg (str, optional): Custom Error Message for Error Handling 63 | 64 | Returns: 65 | HTTP response object returned by the Strategy One REST server. 66 | """ 67 | return connection.delete( 68 | endpoint=f'/api/scripts/{id}', headers={'X-MSTR-ProjectID': project_id} 69 | ) 70 | -------------------------------------------------------------------------------- /mstrio/api/transmitters.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from mstrio.utils.error_handlers import ErrorHandler 4 | 5 | if TYPE_CHECKING: 6 | from mstrio.connection import Connection 7 | 8 | 9 | @ErrorHandler(err_msg="Error listing Transmitters.") 10 | def get_transmitters(connection: 'Connection', error_msg: str | None = None): 11 | """Get a list of all transmitters that the authenticated user has access 12 | to. 13 | 14 | Args: 15 | connection: Strategy One REST API connection object 16 | error_msg (string, optional): Custom Error Message for Error Handling 17 | 18 | Returns: 19 | Complete HTTP response object. Expected status is 200. 20 | """ 21 | return connection.get(endpoint='/api/transmitters') 22 | 23 | 24 | @ErrorHandler(err_msg="Error creating Transmitter.") 25 | def create_transmitter(connection: 'Connection', body, error_msg: str | None = None): 26 | """Create a new transmitter. 27 | 28 | Args: 29 | connection: Strategy One REST API connection object 30 | body: Transmitter creation body 31 | error_msg (string, optional): Custom Error Message for Error Handling 32 | 33 | Returns: 34 | Complete HTTP response object. Expected status is 201. 35 | """ 36 | return connection.post(endpoint='/api/transmitters', json=body) 37 | 38 | 39 | @ErrorHandler(err_msg="Error getting Transmitter with ID {id}") 40 | def get_transmitter(connection: 'Connection', id: str, error_msg: str | None = None): 41 | """Get transmitter by a specific id. 42 | 43 | Args: 44 | connection: Strategy One REST API connection object 45 | id: ID of the transmitter 46 | error_msg (string, optional): Custom Error Message for Error Handling 47 | 48 | Returns: 49 | Complete HTTP response object. Expected status is 200. 50 | """ 51 | return connection.get(endpoint=f'/api/transmitters/{id}') 52 | 53 | 54 | @ErrorHandler(err_msg="Error updating Transmitter with ID {id}") 55 | def update_transmitter( 56 | connection: 'Connection', id: str, body: dict, error_msg: str | None = None 57 | ): 58 | """Update a transmitter. 59 | 60 | Args: 61 | connection: Strategy One REST API connection object 62 | id: ID of the transmitter 63 | body: Transmitter update info. 64 | error_msg (string, optional): Custom Error Message for Error Handling 65 | 66 | Returns: 67 | Complete HTTP response object. Expected status is 200. 68 | """ 69 | return connection.put(endpoint=f'/api/transmitters/{id}', json=body) 70 | 71 | 72 | @ErrorHandler(err_msg="Error deleting Transmitter with ID {id}") 73 | def delete_transmitter(connection: 'Connection', id: str, error_msg: str | None = None): 74 | """Delete a transmitter. 75 | 76 | Args: 77 | connection: Strategy One REST API connection object 78 | id: ID of the transmitter 79 | error_msg (string, optional): Custom Error Message for Error Handling 80 | 81 | Returns: 82 | Complete HTTP response object. Expected status is 204. 83 | """ 84 | return connection.delete(endpoint=f'/api/transmitters/{id}') 85 | -------------------------------------------------------------------------------- /mstrio/config.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | import warnings 4 | 5 | from pandas import options 6 | 7 | verbose = True # Controls the amount of feedback from the I-Server 8 | fetch_on_init = True # Controls if object will fetch basic data from server on init 9 | progress_bar = ( 10 | True # Controls whether progress bar will be shown during long fetch operations 11 | ) 12 | debug = False # Lets the program run in debugging mode 13 | # Sets number of rows displayed for pandas DataFrame 14 | options.display.max_rows = max(250, options.display.max_rows) 15 | options.display.max_colwidth = max(100, options.display.max_colwidth) 16 | # Warning settings: "error", "ignore", "always", "default", "module", "once" 17 | print_warnings = 'always' 18 | module_path = 'mstrio.*' 19 | save_responses = False # Used to save REST API responses for mocking 20 | wip_warnings_enabled = ( 21 | True # Controls whether warnings/errors about WIP functionality are emitted 22 | ) 23 | 24 | 25 | def custom_formatwarning(msg, category, *args, **kwargs): 26 | # ignore everything except the message 27 | return str(category.__name__) + ': ' + str(msg) + '\n' 28 | 29 | 30 | warnings.formatwarning = custom_formatwarning 31 | warnings.filterwarnings(action=print_warnings, module=module_path) 32 | warnings.filterwarnings( 33 | action=print_warnings, category=DeprecationWarning, module=module_path 34 | ) 35 | warnings.filterwarnings(action='default', category=UserWarning, module=module_path) 36 | 37 | 38 | def get_logging_level() -> int: 39 | """Calculate and return logging level 40 | to configure logger. 41 | """ 42 | return logging.DEBUG if debug else logging.INFO 43 | 44 | 45 | logger_stream_handler = logging.StreamHandler(stream=sys.stdout) 46 | 47 | # warns issued by the warnings module will be redirected to the logging.warning 48 | logging.captureWarnings(True) 49 | 50 | # root logger for mstrio, all loggers in submodules will have keys of the form 51 | # "mstrio.sub1.sub2" and have this one as its ancestor 52 | logger = logging.getLogger("mstrio") 53 | logger.propagate = False 54 | warnings_logger = logging.getLogger("py.warnings") 55 | 56 | logger.addHandler(logger_stream_handler) 57 | logger.setLevel(get_logging_level()) 58 | 59 | 60 | def toggle_debug_mode() -> None: 61 | """Toggle debug mode between INFO and DEBUG. 62 | It will change root logger's logging level. 63 | """ 64 | global debug 65 | debug = not debug 66 | logger.setLevel(get_logging_level()) 67 | -------------------------------------------------------------------------------- /mstrio/datasources/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | # isort: off 4 | from .dbms import Dbms, list_available_dbms 5 | 6 | # isort: on 7 | from .database_connections import DatabaseConnections 8 | from .datasource_connection import ( 9 | CharEncoding, 10 | DatasourceConnection, 11 | DriverType, 12 | ExecutionMode, 13 | list_datasource_connections, 14 | ) 15 | from .datasource_instance import ( 16 | DatasourceInstance, 17 | DatasourceType, 18 | list_connected_datasource_instances, 19 | list_datasource_instances, 20 | ) 21 | from .datasource_login import DatasourceLogin, list_datasource_logins 22 | from .datasource_map import DatasourceMap, list_datasource_mappings 23 | from .driver import Driver, list_drivers 24 | from .embedded_connection import EmbeddedConnection 25 | from .gateway import Gateway, list_gateways 26 | from .helpers import DBType, GatewayType 27 | -------------------------------------------------------------------------------- /mstrio/distribution_services/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .device import * 3 | from .event import Event, list_events 4 | from .schedule import * 5 | from .subscription import * 6 | from .transmitter import * 7 | -------------------------------------------------------------------------------- /mstrio/distribution_services/device/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .device import Device, DeviceType, list_devices 3 | from .device_properties import * 4 | -------------------------------------------------------------------------------- /mstrio/distribution_services/email.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from mstrio import config 4 | from mstrio.api import emails 5 | from mstrio.connection import Connection 6 | from mstrio.users_and_groups import User 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | def send_email( 12 | connection: 'Connection', 13 | users: list[str | User], 14 | subject: str, 15 | content: str, 16 | is_html: bool | None = None, 17 | ) -> None: 18 | """Send an email to specified recipients. 19 | 20 | Args: 21 | connection (Connection): Strategy One connection object returned by 22 | `connection.Connection()` 23 | users(list[str | User]): List of user IDs or User objects to send 24 | the email to 25 | subject (str): Subject of the email 26 | content (str): Content of the email 27 | is_html (bool, optional): Whether the email content is HTML-formatted 28 | """ 29 | if not subject or not content: 30 | raise ValueError("Both `subject` and `content` must be provided.") 31 | 32 | body = { 33 | 'notificationType': 'USER_CREATION', 34 | 'userIds': [ 35 | user_id.id if isinstance(user_id, User) else user_id for user_id in users 36 | ], 37 | 'subject': subject, 38 | 'content': content, 39 | 'isHTML': is_html, 40 | } 41 | 42 | emails.send_email(connection=connection, body=body) 43 | if config.verbose: 44 | logger.info(f"Email sent with the subject: {subject}") 45 | -------------------------------------------------------------------------------- /mstrio/distribution_services/schedule/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | # isort: off 4 | from .schedule_time import ScheduleEnums, ScheduleTime 5 | 6 | # isort: on 7 | from .schedule import Schedule, list_schedules 8 | -------------------------------------------------------------------------------- /mstrio/distribution_services/subscription/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .base_subscription import Subscription 3 | from .cache_update_subscription import CacheUpdateSubscription 4 | from .content import Content 5 | from .delivery import ( 6 | CacheType, 7 | ClientType, 8 | Delivery, 9 | Orientation, 10 | SendContentAs, 11 | ShortcutCacheFormat, 12 | ZipSettings, 13 | ) 14 | from .dynamic_recipient_list import DynamicRecipientList, list_dynamic_recipient_lists 15 | from .email_subscription import EmailSubscription 16 | from .file_subscription import FileSubscription 17 | from .ftp_subscription import FTPSubscription 18 | from .history_list_subscription import HistoryListSubscription 19 | from .mobile_subscription import MobileSubscription 20 | from .subscription_manager import SubscriptionManager, list_subscriptions 21 | -------------------------------------------------------------------------------- /mstrio/distribution_services/subscription/common.py: -------------------------------------------------------------------------------- 1 | from enum import auto 2 | 3 | from mstrio.utils.enum_helper import AutoName 4 | 5 | 6 | class RefreshPolicy(AutoName): 7 | ADD = auto() 8 | DELETE = auto() 9 | UPDATE = auto() 10 | UPSERT = auto() 11 | REPLACE = auto() 12 | -------------------------------------------------------------------------------- /mstrio/distribution_services/transmitter/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .transmitter import * 3 | -------------------------------------------------------------------------------- /mstrio/modeling/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | # isort: off 4 | from .filter import * 5 | from .schema import * 6 | from .expression import * 7 | from .metric import * 8 | from .security_filter import * 9 | 10 | # isort: on 11 | -------------------------------------------------------------------------------- /mstrio/modeling/expression/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .dynamic_date_time import ( 3 | AdjustmentMonthlyByDay, 4 | AdjustmentMonthlyByDayOfWeek, 5 | AdjustmentMonthlyByReverseCount, 6 | AdjustmentNone, 7 | AdjustmentQuarterlyByDay, 8 | AdjustmentQuarterlyByDayOfWeek, 9 | AdjustmentQuarterlyByReverseCount, 10 | AdjustmentWeeklyByDayOfWeek, 11 | AdjustmentYearlyByDate, 12 | AdjustmentYearlyByDayOfWeek, 13 | DateMode, 14 | DayOfWeek, 15 | DynamicDateTimeStructure, 16 | DynamicDateTimeType, 17 | DynamicVersatileDate, 18 | HourMode, 19 | MinuteAndSecondMode, 20 | StaticVersatileDate, 21 | VersatileTime, 22 | ) 23 | from .enums import * 24 | from .expression import Expression, Token, list_functions 25 | from .expression_nodes import ( 26 | AttributeFormPredicate, 27 | BandingCountPredicate, 28 | BandingDistinctPredicate, 29 | BandingPointsPredicate, 30 | BandingSizePredicate, 31 | ColumnReference, 32 | Constant, 33 | CustomExpressionPredicate, 34 | DynamicDateTime, 35 | ElementListPredicate, 36 | ExpressionFormShortcut, 37 | ExpressionRelationship, 38 | FilterQualificationPredicate, 39 | JointElementListPredicate, 40 | MetricPredicate, 41 | ObjectReference, 42 | Operator, 43 | PromptPredicate, 44 | ReportQualificationPredicate, 45 | SetFromRelationshipPredicate, 46 | ) 47 | from .fact_expression import FactExpression 48 | from .parameters import ( 49 | AttributeElement, 50 | ConstantArrayParameter, 51 | ConstantParameter, 52 | DynamicDateTimeParameter, 53 | ExpressionParameter, 54 | FunctionProperty, 55 | ObjectReferenceParameter, 56 | PromptParameter, 57 | Variant, 58 | VariantType, 59 | ) 60 | -------------------------------------------------------------------------------- /mstrio/modeling/filter/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .filter import * 3 | -------------------------------------------------------------------------------- /mstrio/modeling/metric/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | # isort: off 3 | from .dimensionality import Dimensionality, DimensionalityUnit 4 | from .metric_format import FormatProperty, MetricFormat 5 | from .metric import ( 6 | DefaultSubtotals, 7 | FormatProperty, 8 | list_metrics, 9 | Metric, 10 | MetricFormat, 11 | Threshold, 12 | ) 13 | 14 | # isort: on 15 | -------------------------------------------------------------------------------- /mstrio/modeling/schema/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .attribute import * 3 | from .fact import * 4 | from .helpers import * 5 | from .schema_management import ( 6 | SchemaLockStatus, 7 | SchemaLockType, 8 | SchemaManagement, 9 | SchemaTask, 10 | SchemaTaskStatus, 11 | SchemaUpdateType, 12 | ) 13 | from .table import * 14 | from .transformation import * 15 | from .user_hierarchy import * 16 | -------------------------------------------------------------------------------- /mstrio/modeling/schema/attribute/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .attribute_form import AttributeForm 3 | from .relationship import Relationship, RelationshipType 4 | 5 | # isort: split 6 | 7 | # This import has to be at the bottom due to circular import errors 8 | from .attribute import Attribute, list_attributes 9 | -------------------------------------------------------------------------------- /mstrio/modeling/schema/fact/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .fact import Fact, list_facts 3 | -------------------------------------------------------------------------------- /mstrio/modeling/schema/table/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .logical_table import * 3 | from .physical_table import * 4 | from .warehouse_table import * 5 | -------------------------------------------------------------------------------- /mstrio/modeling/schema/transformation/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .transformation import * 3 | -------------------------------------------------------------------------------- /mstrio/modeling/schema/user_hierarchy/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .user_hierarchy import * 3 | -------------------------------------------------------------------------------- /mstrio/modeling/security_filter/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .security_filter import * 3 | -------------------------------------------------------------------------------- /mstrio/object_management/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .object import Object, list_objects 3 | from .predefined_folders import PredefinedFolders 4 | 5 | # isort: off 6 | from .folder import ( 7 | Folder, 8 | get_my_personal_objects_contents, 9 | get_predefined_folder_contents, 10 | list_folders, 11 | ) 12 | 13 | # isort: on 14 | from .search_enums import ( 15 | CertifiedStatus, 16 | SearchDomain, 17 | SearchPattern, 18 | SearchResultsFormat, 19 | ) 20 | from .search_operations import ( 21 | CertifiedStatus, 22 | QuickSearchData, 23 | SearchDomain, 24 | SearchObject, 25 | SearchPattern, 26 | SearchResultsFormat, 27 | find_objects_with_id, 28 | full_search, 29 | get_search_results, 30 | get_search_suggestions, 31 | quick_search, 32 | quick_search_by_id, 33 | quick_search_from_object, 34 | start_full_search, 35 | ) 36 | from .shortcut import list_shortcuts, Shortcut, ShortcutInfoFlags, get_shortcuts 37 | from .translation import Translation, list_translations 38 | -------------------------------------------------------------------------------- /mstrio/object_management/migration/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | # isort: off 3 | from .package import ( 4 | Action, 5 | PackageConfig, 6 | PackageContentInfo, 7 | PackageSettings, 8 | MigrationPurpose, 9 | PackageType, 10 | PackageStatus, 11 | ImportStatus, 12 | ) 13 | from .migration import Migration, list_migrations, list_migration_possible_content 14 | 15 | # isort: on 16 | -------------------------------------------------------------------------------- /mstrio/object_management/predefined_folders.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class PredefinedFolders(Enum): 5 | """Enumeration constants used to specify names of pre-defined folders. 6 | Values are specified according to `EnumDSSXMLFolderNames`.""" 7 | 8 | AUTO_STYLES = 57 9 | BLACK_LISTED = 1000 10 | CONFIGURE_DB_ROLES = 73 11 | CONFIGURE_MONITORS = 58 12 | CONFIGURE_SERVER_DEFINITIONS = 59 13 | DB_CONNECTIONS = 81 14 | DB_LOGINS = 82 15 | DBMS = 76 16 | DEVICES = 88 17 | EVENTS = 72 18 | LINKS = 83 19 | LOCALES = 74 20 | MY_DASHBOARDS = 96 21 | MY_SHARED_DASHBOARDS = 97 22 | PALETTES = 94 23 | PROFILE_ANSWERS = 21 24 | PROFILE_FAVORITES = 22 25 | PROFILE_OBJECTS = 19 26 | PROFILE_OTHER = 23 27 | PROFILE_REPORTS = 20 28 | PROFILE_SEGMENTS = 92 29 | PROJECTS = 77 30 | PROPERTY_SETS = 75 31 | PUBLIC_CONSOLIDATIONS = 2 32 | PUBLIC_CUSTOM_GROUPS = 3 33 | PUBLIC_FILTERS = 4 34 | PUBLIC_METRICS = 5 35 | PUBLIC_OBJECTS = 1 36 | PUBLIC_PROMPTS = 6 37 | PUBLIC_REPORTS = 7 38 | PUBLIC_SEARCHES = 8 39 | PUBLIC_TEMPLATES = 9 40 | ROOT = 39 41 | SCHEDULE_OBJECTS = 84 42 | SCHEDULE_TRIGGERS = 85 43 | SCHEMA_ARITHMETIC_OPERATORS = 49 44 | SCHEMA_ATTRIBUTE_FORMS = 25 45 | SCHEMA_ATTRIBUTES = 26 46 | SCHEMA_BASIC_FUNCTIONS = 41 47 | SCHEMA_COLUMNS = 27 48 | SCHEMA_COMPARISON_FOR_RANK_OPERATORS = 51 49 | SCHEMA_COMPARISON_OPERATORS = 50 50 | SCHEMA_DATA_EXPLORER = 28 51 | SCHEMA_DATE_AND_TIME_FUNCTIONS = 42 52 | SCHEMA_FACTS = 29 53 | SCHEMA_FINANCIAL_FUNCTIONS = 54 54 | SCHEMA_FUNCTIONS = 30 55 | SCHEMA_FUNCTIONS_NESTED = 40 56 | SCHEMA_HIERARCHIES = 31 57 | SCHEMA_INTERNAL_FUNCTIONS = 43 58 | SCHEMA_LOGICAL_OPERATORS = 52 59 | SCHEMA_MATH_FUNCTIONS = 55 60 | SCHEMA_NULL_ZERO_FUNCTIONS = 44 61 | SCHEMA_OBJECTS = 24 62 | SCHEMA_OLAP_FUNCTIONS = 45 63 | SCHEMA_OPERATORS = 48 64 | SCHEMA_PARTITION_FILTERS = 32 65 | SCHEMA_PARTITION_MAPPINGS = 33 66 | SCHEMA_PLUG_IN_PACKAGES = 53 67 | SCHEMA_RANK_AND_N_TILE_FUNCTIONS = 46 68 | SCHEMA_STATISTICAL_FUNCTIONS = 56 69 | SCHEMA_STRING_FUNCTIONS = 47 70 | SCHEMA_SUBTOTALS = 34 71 | SCHEMA_TABLES = 35 72 | SCHEMA_TRANSFORMATION_ATTRIBUTES = 37 73 | SCHEMA_TRANSFORMATIONS = 38 74 | SCHEMA_WAREHOUSE_TABLES = 36 75 | SECURITY_ROLES = 80 76 | SYSTEM_DIMENSION = 91 77 | SYSTEM_DRILL_MAP = 68 78 | SYSTEM_DUMMY_PARTITION_TABLES = 70 79 | SYSTEM_LINKS = 62 80 | SYSTEM_MD_SECURITY_FILTERS = 69 81 | SYSTEM_OBJECTS = 61 82 | SYSTEM_PARSER_FOLDER = 64 83 | SYSTEM_PROPERTY_SETS = 63 84 | SYSTEM_SCHEMA_FOLDER = 65 85 | SYSTEM_SYSTEM_HIERARCHY = 67 86 | SYSTEM_SYSTEM_PROMPTS = 71 87 | SYSTEM_WAREHOUSE_CATALOG = 66 88 | TABLE_SOURCES = 86 89 | TEMPLATE_ANALYSIS = 93 90 | TEMPLATE_CONSOLIDATIONS = 11 91 | TEMPLATE_CUSTOM_GROUPS = 12 92 | TEMPLATE_DASHBOARDS = 90 93 | TEMPLATE_DOCUMENTS = 60 94 | TEMPLATE_FILTERS = 13 95 | TEMPLATE_METRICS = 14 96 | TEMPLATE_OBJECTS = 10 97 | TEMPLATE_PROMPTS = 15 98 | TEMPLATE_REPORTS = 16 99 | TEMPLATE_SEARCHES = 17 100 | TEMPLATE_TEMPLATES = 18 101 | THEMES = 95 102 | TRANSMITTERS = 89 103 | USER_GROUPS = 79 104 | USERS = 78 105 | VERSION_UPDATE_HISTORY = 87 106 | -------------------------------------------------------------------------------- /mstrio/object_management/search_enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, IntEnum 2 | 3 | 4 | class CertifiedStatus(Enum): 5 | """Enumeration that represents what can be passed in the certified_status 6 | attribute of the IServer quick search command.""" 7 | 8 | ALL = 'ALL' 9 | CERTIFIED_ONLY = 'CERTIFIED_ONLY' 10 | NOT_CERTIFIED_ONLY = 'NOT_CERTIFIED_ONLY' 11 | OFF = 'OFF' 12 | 13 | 14 | class SearchPattern(IntEnum): 15 | """Enumeration constants used to specify searchType used to control BI 16 | Search. More details can be found in EnumDSSXMLSearchTypes in a browser.""" 17 | 18 | CONTAINS_ANY_WORD = 0 19 | BEGIN_WITH = 1 20 | EXACTLY = 2 21 | BEGIN_WITH_PHRASE = 3 22 | CONTAINS = 4 23 | END_WITH = 5 24 | 25 | 26 | class SearchDomain(IntEnum): 27 | """Enumeration constants used to specify the search domains. More details 28 | can be found in EnumDSSXMLSearchDomain in a browser.""" 29 | 30 | LOCAL = 1 31 | PROJECT = 2 32 | REPOSITORY = 3 33 | CONFIGURATION = 4 34 | 35 | 36 | class SearchResultsFormat(Enum): 37 | """Enumeration constants used to specify the format to return 38 | from search functions.""" 39 | 40 | LIST = 'LIST' 41 | TREE = 'TREE' 42 | -------------------------------------------------------------------------------- /mstrio/project_objects/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .bots import Bot, list_bots 3 | from .content_cache import ContentCache 4 | from .content_group import ContentGroup, list_content_groups 5 | from .datasets import * 6 | 7 | 8 | from .document import Document, list_documents, list_documents_across_projects 9 | from .dashboard import ( 10 | ChapterPage, 11 | Dashboard, 12 | DashboardChapter, 13 | PageSelector, 14 | PageVisualization, 15 | VisualizationSelector, 16 | list_dashboards, 17 | list_dashboards_across_projects, 18 | ) 19 | from .incremental_refresh_report import ( 20 | IncrementalRefreshReport, 21 | list_incremental_refresh_reports, 22 | ) 23 | from .library import Library 24 | from .prompt import Prompt 25 | from .report import Report, list_reports 26 | -------------------------------------------------------------------------------- /mstrio/project_objects/datasets/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .cube import list_all_cubes, load_cube 3 | from .cube_cache import ( 4 | CubeCache, 5 | delete_cube_cache, 6 | delete_cube_caches, 7 | list_cube_caches, 8 | ) 9 | from .olap_cube import OlapCube, list_olap_cubes 10 | from .super_cube import ( 11 | SuperCube, 12 | SuperCubeAttribute, 13 | SuperCubeAttributeForm, 14 | SuperCubeFormExpression, 15 | list_super_cubes, 16 | ) 17 | -------------------------------------------------------------------------------- /mstrio/project_objects/incremental_refresh_report/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .incremental_refresh_report import * 3 | -------------------------------------------------------------------------------- /mstrio/project_objects/library.py: -------------------------------------------------------------------------------- 1 | from mstrio.api import library 2 | from mstrio.connection import Connection 3 | from mstrio.project_objects.dashboard import Dashboard, list_dashboards 4 | from mstrio.project_objects.document import Document, list_documents 5 | from mstrio.utils.helper import get_valid_project_id 6 | 7 | 8 | class Library: 9 | def __init__( 10 | self, 11 | connection: Connection, 12 | project_id: str | None = None, 13 | project_name: str | None = None, 14 | ): 15 | self.connection = connection 16 | ids = self.__get_library_ids() 17 | self.user_id = connection.user_id 18 | 19 | try: 20 | project_id = get_valid_project_id( 21 | connection=connection, 22 | project_id=project_id, 23 | project_name=project_name, 24 | with_fallback=not project_name, 25 | ) 26 | except ValueError: 27 | self._documents = None 28 | self._dashboards = None 29 | self._contents = None 30 | return 31 | 32 | self._documents = list_documents(self.connection, project_id=project_id, id=ids) 33 | self._dashboards = list_dashboards( 34 | self.connection, project_id=project_id, id=ids 35 | ) 36 | self._contents = self._documents + self._dashboards 37 | 38 | def __get_library_ids(self): 39 | response = library.get_library(self.connection) 40 | body = response.json() 41 | ids = [doc_body['target']['id'] for doc_body in body if 'target' in doc_body] 42 | return ids 43 | 44 | @property 45 | def dashboards(self): 46 | if self.connection.project_id is not None: 47 | ids = self.__get_library_ids() 48 | self._dashboards = list_dashboards(self.connection, id=ids) 49 | return self._dashboards 50 | 51 | @property 52 | def documents(self): 53 | if self.connection.project_id is not None: 54 | ids = self.__get_library_ids() 55 | self._documents = list_documents(self.connection, id=ids) 56 | return self._documents 57 | 58 | @property 59 | def contents(self): 60 | if self.connection.project_id is not None: 61 | self._contents = self.dashboards + self.documents 62 | return self._contents 63 | 64 | def publish(self, contents: "list | Dashboard | Document | str"): 65 | """Publishes dashboard or document to the authenticated user's library. 66 | 67 | contents: dashboards or documents to be published, can be 68 | Dashboard/Document class object or ID 69 | """ 70 | if not isinstance(contents, list): 71 | contents = [contents] 72 | for doc in contents: 73 | doc_id = doc.id if isinstance(doc, Document) else doc 74 | body = {'id': doc_id, 'recipients': [{'id': self.user_id}]} 75 | library.publish_document(self.connection, body=body) 76 | 77 | def unpublish(self, contents: "list | Dashboard | Document | str"): 78 | """Publishes dashboard or document to the authenticated user's library. 79 | 80 | contents: dashboards or documents to be published, can be 81 | Dashboard/Document class object or ID 82 | """ 83 | if not isinstance(contents, list): 84 | contents = [contents] 85 | for doc in contents: 86 | doc_id = doc.id if isinstance(doc, Document) else doc 87 | library.unpublish_document_for_user( 88 | self.connection, 89 | user_id=self.user_id, 90 | document_id=doc_id, 91 | ) 92 | -------------------------------------------------------------------------------- /mstrio/project_objects/prompt.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Any 3 | 4 | from mstrio.utils.helper import Dictable 5 | 6 | 7 | @dataclass 8 | class Prompt(Dictable): 9 | """A Strategy One class representing a prompt. 10 | 11 | Attributes: 12 | type (str): Type of the prompt 13 | Possible values are: 14 | - UNSUPPORTED 15 | - VALUE 16 | - ELEMENTS 17 | - EXPRESSION 18 | - OBJECTS 19 | - LEVEL 20 | answers (Any | list[Any]): Singular answer or list of 21 | answers to the prompt. 22 | key (str, optional): Unique key of the prompt. 23 | id (str, optional): ID of the prompt. 24 | name (str, optional): Name of the prompt. 25 | use_default (bool, optional): Whether to use default value. 26 | If True, provided answer will be ignored. Defaults to False. 27 | 28 | Note that only one of the key, id, or name needs to be provided. 29 | It is recommended to always provide the key as it's always unique 30 | as opposed to ID or name that can be shared among prompts. 31 | """ 32 | 33 | type: str 34 | answers: Any | list[Any] 35 | key: str | None = None 36 | id: str | None = None 37 | name: str | None = None 38 | use_default: bool = False 39 | -------------------------------------------------------------------------------- /mstrio/server/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .cluster import Cluster, GroupBy, ServiceAction 3 | from .environment import Environment 4 | from .language import Language, list_interface_languages, list_languages 5 | from .license import ( 6 | ActivationInfo, 7 | ContactInformation, 8 | InstallationUse, 9 | License, 10 | MachineInfo, 11 | PrivilegeInfo, 12 | Product, 13 | Record, 14 | UserLicense, 15 | ) 16 | from .node import Node 17 | from .project import ( 18 | IdleMode, 19 | Project, 20 | ProjectSettings, 21 | ProjectStatus, 22 | compare_project_settings, 23 | list_projects, 24 | ) 25 | from .server import ServerSettings 26 | 27 | # isort: off 28 | from .job_monitor import ( 29 | Job, 30 | JobStatus, 31 | JobType, 32 | kill_all_jobs, 33 | kill_jobs, 34 | list_jobs, 35 | ObjectType, 36 | PUName, 37 | SubscriptionType, 38 | ) 39 | 40 | # isort: on 41 | -------------------------------------------------------------------------------- /mstrio/server/node.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import TYPE_CHECKING 3 | 4 | from mstrio.server.project import Project 5 | from mstrio.utils.helper import Dictable 6 | 7 | if TYPE_CHECKING: 8 | from mstrio.connection import Connection 9 | 10 | 11 | @dataclass 12 | class Node(Dictable): 13 | _FROM_DICT_MAP = {'projects': [Project.from_dict]} 14 | 15 | name: str | None = None 16 | address: str | None = None 17 | service_control: bool | None = None 18 | port: int | None = None 19 | status: str | None = None 20 | load: int | None = None 21 | projects: list[Project] | None = None 22 | default: bool | None = None 23 | 24 | @classmethod 25 | def from_dict(cls, source: dict, connection: 'Connection | None' = None): 26 | if not source.get('name'): 27 | source['name'] = source['node'] 28 | if source.get('ipAddress'): 29 | source['address'] = source['ipAddress'] 30 | return super().from_dict(source, connection=connection) 31 | 32 | 33 | @dataclass 34 | class Service(Dictable): 35 | id: str 36 | service: str 37 | port: int 38 | status: str 39 | tags: dict | None = None 40 | output: str | None = None 41 | 42 | 43 | @dataclass 44 | class ServiceWithNode(Dictable): 45 | node: str 46 | address: str 47 | id: str 48 | service_control: bool 49 | port: int 50 | status: str 51 | service_address: str 52 | tags: dict | None = None 53 | output: str | None = None 54 | -------------------------------------------------------------------------------- /mstrio/server/setting_types.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | _COMMON_ENUM_FIELDS = { 4 | 'SUBSCRIPTION_RECIPIENT_NAME': 'recipient_name', 5 | 'SUBSCRIPTION_OWNER_NAME': 'owner_name', 6 | 'SUBSCRIPTION_REPORT_OR_DOCUMENT_NAME': 'report_document_name', 7 | 'SUBSCRIPTION_PROJECT_NAME': 'project_name', 8 | 'SUBSCRIPTION_DELIVERY_METHOD': 'delivery_method', 9 | 'SUBSCRIPTION_SCHEDULE': 'schedule', 10 | 'SUBSCRIPTION_NAME': 'subscription_name', 11 | 'DELIVERY_STATUS': 'delivery_status', 12 | 'DELIVERY_DATE': 'date', 13 | 'DELIVERY_TIME': 'time', 14 | 'DELIVERY_ERROR_MESSAGE': 'error_message', 15 | } 16 | 17 | FailedEmailDelivery = Enum( 18 | 'FailedEmailDelivery', 19 | {**_COMMON_ENUM_FIELDS, 'DELIVERY_EMAIL_ADDRESS': 'email_address'}, 20 | ) 21 | 22 | FailedFileDelivery = Enum( 23 | 'FailedFileDelivery', 24 | { 25 | **_COMMON_ENUM_FIELDS, 26 | 'FILE_LOCATION': 'file_location', 27 | 'LINK_TO_FILE': 'link_to_file', 28 | }, 29 | ) 30 | 31 | FailedFTPDelivery = Enum( 32 | 'FailedFTPDelivery', 33 | { 34 | **_COMMON_ENUM_FIELDS, 35 | 'FILE_LOCATION': 'file_location', 36 | 'LINK_TO_FILE': 'link_to_file', 37 | }, 38 | ) 39 | 40 | FailedPrinterDelivery = Enum( 41 | 'FailedPrinterDelivery', 42 | { 43 | **_COMMON_ENUM_FIELDS, 44 | 'PRINTER_NAME': 'printer_name', 45 | }, 46 | ) 47 | 48 | FailedHistoryListDelivery = Enum( 49 | 'FailedHistoryListDelivery', 50 | { 51 | **_COMMON_ENUM_FIELDS, 52 | 'LINK_TO_HISTORY_LIST': 'link_to_history_list', 53 | }, 54 | ) 55 | 56 | FailedMobileDelivery = Enum('FailedMobileDelivery', _COMMON_ENUM_FIELDS) 57 | 58 | FailedCacheCreation = Enum('FailedCacheCreation', _COMMON_ENUM_FIELDS) 59 | 60 | 61 | class CompressionLevel(Enum): 62 | """Level of File | Email | FTP delivery compression""" 63 | 64 | OFF = 0 65 | LOW = 3 66 | MEDIUM = 6 67 | HIGH = 9 68 | 69 | 70 | class CacheEncryptionLevel(Enum): 71 | """Cache encryption level on disk""" 72 | 73 | NONE = 0 74 | LOW = 1 75 | HIGH = 2 76 | 77 | 78 | class ShowBaseViewInLibrary(Enum): 79 | """Always open dashboards/documents with the last saved view 80 | in Library""" 81 | 82 | DEFAULT = 'use_inherited_value' 83 | YES = 'yes' 84 | NO = 'no' 85 | 86 | 87 | class SmartMemoryUsageForIntelligenceServer(Enum): 88 | """Smart Memory Usage for Intelligence Server""" 89 | 90 | USE_INHERITED_VALUE = -1 91 | APPLY_BEST_STRATEGY = 0 92 | TURN_OFF_THE_CAPABILITY_WITHOUT_EXCEPTIONS = 1 93 | DISABLE_THE_CAPABILITY = 2 94 | ENABLE_THE_CAPABILITY = 3 95 | 96 | 97 | class OrderMultiSourceDBI(Enum): 98 | """Rules to order multi-source Database Instances""" 99 | 100 | MULTISOURCE_DEFAULT_ORDERING = 0 101 | PROJECT_LEVEL_DATABASE_ORDERING = 1 102 | 103 | 104 | class DisplayEmptyReportMessageInRWD(Enum): 105 | """Display mode for empty Grid/Graphs in documents""" 106 | 107 | DISPLAY_MESSAGE_IN_DOCUMENT_GRIDS = -1 108 | HIDE_DOCUMENT_GRID = 0 109 | 110 | 111 | class MergeSecurityFilters(Enum): 112 | """Rules to merge multiple Security Filters across user groups and users""" 113 | 114 | UNION = 0 115 | INTERSECT = 3 116 | -------------------------------------------------------------------------------- /mstrio/server/storage.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import Enum 3 | from logging import getLogger 4 | 5 | from mstrio.utils.helper import Dictable 6 | 7 | logger = getLogger(__name__) 8 | 9 | 10 | class StorageType(Enum): 11 | """Enumeration of Storage Service types.""" 12 | 13 | UNSET = "unset" 14 | UNKNOWN = "unknown" 15 | FILE_SYSTEM = "file_system" 16 | S3 = "S3" 17 | AZURE = "Azure" 18 | GCS = "GCS" 19 | 20 | 21 | @dataclass 22 | class StorageService(Dictable): 23 | """StorageService configuration of environment. 24 | Attributes: 25 | type (StorageType): Type of storage (e.g. file system, S3) 26 | alias (str, optional): Alias of the storage configuration, 27 | location (str, optional): Storage location, e.g. 28 | bucket name for S3, absolute path of folder for File System 29 | s3_region (str, optional): S3 bucket region 30 | aws_access_id (str, optional): Access ID for AWS S3 31 | aws_secret_key (str, optional): Access key for AWS S3 32 | azure_storage_account_name (str, optional): Account name for Azure 33 | azure_secret_key (str, optional): Access key for Azure 34 | configured (bool): whether Storage Service is configured 35 | """ 36 | 37 | _FROM_DICT_MAP = { 38 | 'type': StorageType, 39 | } 40 | 41 | type: StorageType = StorageType.UNSET 42 | alias: str | None = None 43 | location: str | None = None 44 | s3_region: str | None = None 45 | aws_access_id: str | None = None 46 | aws_secret_key: str | None = None 47 | azure_storage_account_name: str | None = None 48 | azure_secret_key: str | None = None 49 | gcs_service_account_key: str | None = None 50 | configured: bool = False 51 | 52 | # Give a warning when changing type 53 | # This is equivalent to the warning present in Workstation 54 | def __setattr__(self, name, value): 55 | if ( 56 | name == 'type' 57 | and hasattr(self, 'type') 58 | and value != self.type 59 | and self.type != StorageType.UNSET 60 | ): 61 | logger.warning( 62 | "Changing the storage service type. The existing packages " 63 | "in the previous location will not be available." 64 | ) 65 | super().__setattr__(name, value) 66 | -------------------------------------------------------------------------------- /mstrio/users_and_groups/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from typing import Union 3 | 4 | from .user import User, create_users_from_csv, list_users 5 | from .user_connections import UserConnections 6 | from .user_group import UserGroup, list_user_groups 7 | 8 | # isort: split 9 | 10 | # those import has to be below import of `User` to avoid circular imports 11 | from .contact import Contact, ContactAddress, ContactDeliveryType, list_contacts 12 | from .contact_group import ( 13 | ContactGroup, 14 | ContactGroupMember, 15 | ContactGroupMemberType, 16 | list_contact_groups, 17 | ) 18 | 19 | UserOrGroup = Union[str, User, UserGroup] 20 | -------------------------------------------------------------------------------- /mstrio/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroStrategy/mstrio-py/da214217478831a20fb9f20f6f148d4a1ec80ab7/mstrio/utils/__init__.py -------------------------------------------------------------------------------- /mstrio/utils/certified_info.py: -------------------------------------------------------------------------------- 1 | from mstrio.connection import Connection 2 | from mstrio.users_and_groups.user import User 3 | from mstrio.utils.entity import auto_match_args_entity 4 | from mstrio.utils.helper import Dictable 5 | 6 | 7 | class CertifiedInfo(Dictable): 8 | """Certification status, time of certification and information 9 | about the certifier (currently only for document and report). 10 | 11 | Attributes: 12 | connection: Strategy One connection object returned by 13 | `connection.Connection()`. 14 | certified: Specifies whether the object is trusted, 15 | as determined by the standards set by the certifier 16 | date_certified: time when the object was certified, 17 | "yyyy-MM-dd HH:mm:ss" in UTC 18 | certifier: information about the entity certifying 19 | the object, User object 20 | """ 21 | 22 | def __init__( 23 | self, 24 | connection: Connection, 25 | certified: bool, 26 | date_certified: str | None = None, 27 | certifier: dict | None = None, 28 | ): 29 | self._connection = connection 30 | self._certified = certified 31 | self._date_certified = date_certified 32 | self._certifier = ( 33 | User.from_dict(certifier, self._connection) if certifier else None 34 | ) 35 | 36 | def __str__(self): 37 | if not self.certified: 38 | return 'Object is not certified.' 39 | return f"Object certified on {self.date_certified} by {self._certifier}" 40 | 41 | def __repr__(self): 42 | param_value_dict = auto_match_args_entity( 43 | self.__init__, self, exclude=['self'], include_defaults=False 44 | ) 45 | params_list = [] 46 | for param, value in param_value_dict.items(): 47 | if param == "connection" and isinstance(value, Connection): 48 | params_list.append("connection") 49 | else: 50 | params_list.append(f"{param}={repr(value)}") 51 | formatted_params = ", ".join(params_list) 52 | return f"{self.__class__.__name__}({formatted_params})" 53 | 54 | @property 55 | def connection(self) -> Connection: 56 | return self._connection 57 | 58 | @property 59 | def certified(self) -> bool | None: 60 | return self._certified 61 | 62 | @property 63 | def date_certified(self) -> str | None: 64 | return self._date_certified 65 | 66 | @property 67 | def certifier(self) -> User | None: 68 | return self._certifier 69 | -------------------------------------------------------------------------------- /mstrio/utils/collections.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, TypeVar 2 | 3 | T = TypeVar('T') 4 | 5 | 6 | def remove_duplicate_objects(objects: list[T], getter: Callable[[T], Any]) -> list[T]: 7 | """ 8 | Remove duplicate objects from a list of objects 9 | based on the value returned by the getter function. 10 | This function assumes that getter will always return 11 | a comparable value and will not fail. 12 | 13 | Args: 14 | objects (list[T]): List of objects to filter. 15 | getter (Callable[[T], Any]): Function to get the value from the object 16 | by which the objects should be compared. 17 | 18 | Returns: 19 | list[T]: List of unique objects. 20 | """ 21 | seen = set() 22 | unique_objects = [] 23 | 24 | for obj in objects: 25 | value = getter(obj) 26 | if value not in seen: 27 | seen.add(value) 28 | unique_objects.append(obj) 29 | 30 | return unique_objects 31 | -------------------------------------------------------------------------------- /mstrio/utils/datasources.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def alter_conn_resp(response): 5 | response_json = response.json() 6 | response_json = alter_conn_json(response_json) 7 | response.encoding, response._content = 'utf-8', json.dumps(response_json).encode( 8 | 'utf-8' 9 | ) 10 | return response 11 | 12 | 13 | def alter_conn_resp_embedded(response): 14 | response_json = response.json()['database']['embeddedConnection'] 15 | response.encoding, response._content = 'utf-8', json.dumps(response_json).encode( 16 | 'utf-8' 17 | ) 18 | return response 19 | 20 | 21 | def alter_conn_list_resp(response): 22 | response_json = response.json() 23 | for conn in response_json["connections"]: 24 | alter_conn_json(conn) 25 | response.encoding, response._content = 'utf-8', json.dumps(response_json).encode( 26 | 'utf-8' 27 | ) 28 | return response 29 | 30 | 31 | def alter_conn_json(response_json): 32 | response_json['datasource_login'] = response_json["database"]["login"] 33 | response_json['database_type'] = response_json["database"]["type"] 34 | response_json['database_version'] = response_json["database"]["version"] 35 | del response_json["database"] 36 | return response_json 37 | 38 | 39 | def alter_instance_resp(response): 40 | response_json = response.json() 41 | response_json = alter_instance_json(response_json) 42 | response.encoding, response._content = 'utf-8', json.dumps(response_json).encode( 43 | 'utf-8' 44 | ) 45 | return response 46 | 47 | 48 | def alter_instance_list_resp(response): 49 | response_json = response.json() 50 | for ds in response_json["datasources"]: 51 | alter_instance_json(ds) 52 | response.encoding, response._content = 'utf-8', json.dumps(response_json).encode( 53 | 'utf-8' 54 | ) 55 | return response 56 | 57 | 58 | def alter_instance_json(response_json): 59 | response_json['datasource_connection'] = response_json["database"].get("connection") 60 | if response_json["database"].get("connection", {}).get('isEmbedded'): 61 | response_json['datasource_connection'] |= response_json["database"].get( 62 | "embeddedConnection", {} 63 | ) 64 | response_json['datasource_connection']['datasource_id'] = response_json["id"] 65 | response_json['database_type'] = response_json["database"].get("type") 66 | response_json['database_version'] = response_json["database"].get("version") 67 | response_json['primary_datasource'] = response_json["database"].get( 68 | "primaryDatasource" 69 | ) 70 | response_json['data_mart_datasource'] = response_json["database"].get( 71 | "dataMartDatasource" 72 | ) 73 | del response_json["database"] 74 | return response_json 75 | 76 | 77 | def alter_patch_req_body(op_dict, initial_path, altered_path): 78 | if op_dict["path"] == initial_path: 79 | op_dict["path"] = altered_path 80 | op_dict["value"] = ( 81 | op_dict["value"] 82 | if isinstance(op_dict["value"], str) 83 | else op_dict["value"].get("id") 84 | ) 85 | return op_dict 86 | 87 | 88 | def get_objects_id(obj, obj_class): 89 | if isinstance(obj, str): 90 | return obj 91 | elif isinstance(obj, obj_class): 92 | return obj.id 93 | return None 94 | -------------------------------------------------------------------------------- /mstrio/utils/encoder.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | from base64 import b64encode 3 | 4 | from pandas import DataFrame 5 | 6 | 7 | class Encoder: 8 | """Internal method for converting a Pandas DataFrame to Strategy One 9 | compliant base64 encoded JSON. 10 | 11 | When creating a data set, Strategy One APIs require the tabular data to 12 | have been transformed first into JSON and then into a base 64 encoded string 13 | before it is transmitted to the Intelligence Server via the REST API to 14 | create the data set. This class uses Pandas to handle transforming 15 | the DataFrame into a JSON representation of the data. For single-table data 16 | sets, Strategy One APIs require the JSON data to be formatted using 17 | the 'records' orientation from Pandas. Conversely, multi-table data sets 18 | require the JSON data to have a 'values' orientation. Based on the data set 19 | type, the correct encoding strategy is applied and the data is then encoded. 20 | 21 | Attributes: 22 | data_frame: Pandas DataFrame to be encoded. 23 | b64_data: DataFrame converted into a Base-64 encoded JSON string. 24 | orientation: For single-table data sets, "single"; for multi-table 25 | data sets, "multi". 26 | """ 27 | 28 | def __init__(self, data_frame: DataFrame, dataset_type: str): 29 | """Inits Encoder with given data_frame and type. 30 | 31 | Args: 32 | data_frame (DataFrame): Pandas DataFrame to be converted. 33 | dataset_type (str): Dataset type. One of `single` or `multi` to 34 | correspond with single-table or multi-table sources. 35 | """ 36 | self.data_frame: DataFrame = data_frame 37 | self._b64_data: str | None = None 38 | self.orientation: str | None = None 39 | # Mapping used when converting DataFrame rows 40 | # into proper JSON orientation needed for data uploads. 41 | _table_type_orient_map: dict = {'single': 'records', 'multi': 'values'} 42 | 43 | # Sets the proper orientation 44 | if dataset_type not in _table_type_orient_map: 45 | allowed_types = ', '.join(_table_type_orient_map) 46 | raise ValueError(f"Table type should be one of {allowed_types}") 47 | 48 | self.orientation = _table_type_orient_map[dataset_type] 49 | 50 | def encode(self) -> None: 51 | """Encode data in base 64.""" 52 | self.data_frame = self.data_frame.apply( 53 | lambda x: x.astype('str') if isinstance(x.iloc[0], dt.date) else x 54 | ) 55 | 56 | json_data = self.data_frame.to_json(orient=self.orientation, date_format='iso') 57 | self._b64_data = b64encode(json_data.encode('utf-8')).decode('utf-8') 58 | 59 | @property 60 | def b64_data(self) -> str: 61 | if not self._b64_data: 62 | self.encode() 63 | return self._b64_data 64 | -------------------------------------------------------------------------------- /mstrio/utils/enum_helper.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class AutoName(Enum): 5 | def _generate_next_value_(self, start, count, last_values): 6 | return self.lower() 7 | 8 | @classmethod 9 | def has_value(cls, value): 10 | return value in {item.value for item in cls} 11 | 12 | def __repr__(self) -> str: 13 | return self.__str__() 14 | 15 | 16 | class AutoUpperName(Enum): 17 | def _generate_next_value_(self, start, count, last_values): 18 | return self 19 | 20 | 21 | class AutoCapitalizedName(Enum): 22 | def _generate_next_value_(self, start, count, last_values): 23 | return self.capitalize() 24 | 25 | 26 | def __get_enum_helper( 27 | obj, enum: type[Enum] = Enum, get_value: bool = False 28 | ) -> str | int | type[Enum] | None: 29 | """Helper function for `get_enum` and `get_enum_val`.""" 30 | if obj is None: 31 | return obj 32 | 33 | if isinstance(obj, enum): 34 | return obj.value if get_value else obj 35 | 36 | if isinstance(obj, (str, int)): 37 | validate_enum_value(obj, enum) 38 | return obj if get_value else enum(obj) 39 | 40 | raise TypeError(f"Incorrect type. Value should be of type: {enum}.") 41 | 42 | 43 | def get_enum(obj, enum: type[Enum] = Enum) -> type[Enum] | None: 44 | """Safely get enum from enum or str.""" 45 | return __get_enum_helper(obj, enum) 46 | 47 | 48 | def get_enum_val(obj, enum: type[Enum] = Enum) -> str | int | None: 49 | """Safely extract value from enum or str.""" 50 | return __get_enum_helper(obj, enum, True) 51 | 52 | 53 | def validate_enum_value( 54 | obj: str | int, enum: type[Enum] | tuple[type[Enum]] | list[type[Enum]] 55 | ) -> None: 56 | """Validate provided value. If not correct, 57 | error message with possible options will be displayed. 58 | """ 59 | from mstrio.utils.helper import exception_handler 60 | 61 | possible_values = [ 62 | e.value 63 | for item in enum 64 | for e in (item if isinstance(enum, (tuple, list)) else [item]) 65 | ] 66 | err_msg = f"Incorrect enum value '{obj}'. Possible values are {possible_values}" 67 | if obj not in possible_values: 68 | exception_handler(err_msg, exception_type=ValueError) 69 | -------------------------------------------------------------------------------- /mstrio/utils/enums.py: -------------------------------------------------------------------------------- 1 | from enum import auto 2 | from mstrio.utils.enum_helper import AutoName 3 | 4 | 5 | class DaysOfWeek(AutoName): 6 | MONDAY = auto() 7 | TUESDAY = auto() 8 | WEDNESDAY = auto() 9 | THURSDAY = auto() 10 | FRIDAY = auto() 11 | SATURDAY = auto() 12 | SUNDAY = auto() 13 | NONE = None 14 | -------------------------------------------------------------------------------- /mstrio/utils/formjson.py: -------------------------------------------------------------------------------- 1 | def _map_data_type(datatype): 2 | if datatype == 'object': 3 | return "STRING" 4 | elif datatype in ['int64', 'int32']: 5 | return "INTEGER" 6 | elif datatype in ['float64', 'float32']: 7 | return "DOUBLE" 8 | elif datatype == 'bool': 9 | return "BOOL" 10 | elif datatype == 'datetime64[ns]': 11 | return 'DATETIME' 12 | 13 | 14 | def formjson(df, table_name, as_metrics=None, as_attributes=None): 15 | def _form_column_headers(_col_names, _col_types): 16 | return [{'name': n, 'dataType': t} for n, t in zip(_col_names, _col_types)] 17 | 18 | def _form_attribute_list(_attributes): 19 | return [ 20 | { 21 | 'name': n, 22 | 'attributeForms': [ 23 | { 24 | 'category': 'ID', 25 | 'expressions': [{'formula': table_name + "." + n}], 26 | } 27 | ], 28 | } 29 | for n in _attributes 30 | ] 31 | 32 | def _form_metric_list(_metrics): 33 | return [ 34 | { 35 | 'name': n, 36 | 'dataType': 'number', 37 | 'expressions': [{'formula': table_name + "." + n}], 38 | } 39 | for n in _metrics 40 | ] 41 | 42 | col_names = list(df.columns) 43 | col_types = list(map(_map_data_type, list(df.dtypes.values))) 44 | 45 | # Adjust attributes/metrics mapping if new mappings were provided in 46 | # as_metrics and as_attributes 47 | attributes = [] 48 | metrics = [] 49 | for _name, _type in zip(col_names, col_types): 50 | if _type in ['DOUBLE', 'INTEGER']: # metric 51 | if as_attributes is not None and _name in as_attributes: 52 | attributes.append(_name) 53 | else: 54 | metrics.append(_name) 55 | else: # attribute 56 | if as_metrics is not None and _name in as_metrics: 57 | metrics.append(_name) 58 | else: 59 | attributes.append(_name) 60 | 61 | column_headers = _form_column_headers(_col_names=col_names, _col_types=col_types) 62 | attribute_list = _form_attribute_list(_attributes=attributes) 63 | metric_list = _form_metric_list(_metrics=metrics) 64 | 65 | return column_headers, attribute_list, metric_list 66 | -------------------------------------------------------------------------------- /mstrio/utils/monitors.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Callable 2 | 3 | from mstrio.api import monitors 4 | from mstrio.utils.helper import _prepare_objects, auto_match_args, response_handler 5 | from mstrio.utils.sessions import FuturesSessionWithRenewal 6 | 7 | 8 | def all_nodes_async( 9 | connection, 10 | async_api: Callable, 11 | filters: dict, 12 | error_msg: str, 13 | unpack_value: str | None = None, 14 | limit: int | None = None, 15 | **kwargs, 16 | ): 17 | """Return list of objects fetched async using wrappers in monitors.py""" 18 | 19 | node = kwargs.get('node_name') 20 | if not node: 21 | nodes_response = monitors.get_node_info(connection).json() 22 | all_nodes = nodes_response['nodes'] 23 | node_names = [node['name'] for node in all_nodes if node['status'] == 'running'] 24 | else: 25 | from mstrio.server.node import Node 26 | 27 | node = node.name if isinstance(node, Node) else node 28 | if isinstance(node, str): 29 | node_names = [node] 30 | 31 | if kwargs.get('error_msg'): 32 | error_msg = kwargs.get('error_msg') 33 | 34 | with FuturesSessionWithRenewal(connection=connection, max_workers=8) as session: 35 | # Extract parameters of the api wrapper and set them using kwargs 36 | param_value_dict = auto_match_args( 37 | async_api, 38 | kwargs, 39 | exclude=[ 40 | 'connection', 41 | 'limit', 42 | 'offset', 43 | 'future_session', 44 | 'error_msg', 45 | 'node_name', 46 | ], 47 | ) 48 | futures = [ 49 | async_api( 50 | future_session=session, 51 | node_name=n, 52 | **param_value_dict, 53 | ) 54 | for n in node_names 55 | ] 56 | objects = [] 57 | for f in futures: 58 | response = f.result() 59 | if not response.ok: 60 | response_handler(response, error_msg, throw_error=False) 61 | else: 62 | obj = _prepare_objects(response.json(), filters, unpack_value) 63 | objects.extend(obj) 64 | if limit: 65 | objects = objects[:limit] 66 | return objects 67 | -------------------------------------------------------------------------------- /mstrio/utils/related_subscription_mixin.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from mstrio.api import subscriptions 4 | from mstrio.utils.helper import get_valid_project_id 5 | from mstrio.utils.version_helper import method_version_handler 6 | 7 | if TYPE_CHECKING: 8 | from mstrio.distribution_services import Event, Schedule, Subscription # noqa: F401 9 | from mstrio.project_objects import Dashboard, Report # noqa: F401 10 | from mstrio.users_and_groups import User # noqa: F401 11 | 12 | RelatedSubscriptionTypes = 'User | Dashboard | Report | Schedule | Event' 13 | 14 | 15 | class RelatedSubscriptionMixin: 16 | """RelatedSubscriptionMixin class adds listing support for supported 17 | objects.""" 18 | 19 | @method_version_handler('11.4.0600') 20 | def list_related_subscriptions( 21 | self: RelatedSubscriptionTypes, to_dictionary: bool = False 22 | ) -> list['Subscription'] | list[dict]: 23 | """List all subscriptions that are dependent on the object. 24 | 25 | Args: 26 | to_dictionary (bool, optional): If True returns a list of 27 | subscription dicts, otherwise (default) returns a list of 28 | subscription objects 29 | """ 30 | object_type = self.__class__.__name__.lower() 31 | 32 | if object_type in ['user', 'event', 'schedule']: 33 | project_id = None 34 | else: 35 | project_id = self.project_id or get_valid_project_id( 36 | self.connection, with_fallback=True 37 | ) 38 | 39 | objects = ( 40 | subscriptions.get_dependent_subscriptions( 41 | self.connection, self.id, object_type, project_id 42 | ) 43 | .json() 44 | .get('subscriptions', []) 45 | ) 46 | 47 | if to_dictionary: 48 | return objects 49 | 50 | from mstrio.distribution_services.subscription.subscription_manager import ( 51 | dispatch_from_dict, 52 | ) 53 | 54 | return [ 55 | dispatch_from_dict( 56 | source=obj, 57 | connection=self.connection, 58 | project_id=project_id or obj.get('project', {}).get('id'), 59 | ) 60 | for obj in objects 61 | ] 62 | -------------------------------------------------------------------------------- /mstrio/utils/resources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroStrategy/mstrio-py/da214217478831a20fb9f20f6f148d4a1ec80ab7/mstrio/utils/resources/__init__.py -------------------------------------------------------------------------------- /mstrio/utils/response_processors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroStrategy/mstrio-py/da214217478831a20fb9f20f6f148d4a1ec80ab7/mstrio/utils/response_processors/__init__.py -------------------------------------------------------------------------------- /mstrio/utils/response_processors/browsing.py: -------------------------------------------------------------------------------- 1 | from mstrio.api import browsing as browsing_api 2 | from mstrio.connection import Connection 3 | 4 | 5 | def get_search_objects( 6 | connection: 'Connection', 7 | body: dict, 8 | include_ancestors: bool = False, 9 | show_navigation_path: bool = False, 10 | fields: str | None = None, 11 | ): 12 | return ( 13 | browsing_api.get_search_objects( 14 | connection=connection, 15 | body=body, 16 | include_ancestors=include_ancestors, 17 | show_navigation_path=show_navigation_path, 18 | fields=fields, 19 | ) 20 | .json() 21 | .get('result') 22 | ) 23 | -------------------------------------------------------------------------------- /mstrio/utils/response_processors/calendars.py: -------------------------------------------------------------------------------- 1 | import mstrio.api.calendars as calendars_api 2 | from mstrio.connection import Connection 3 | from mstrio.utils import helper 4 | 5 | 6 | def _wrangle_year(source: dict, *keys) -> int | None: 7 | """Safely extract year from a nested dictionary, i.e. 8 | source[key1][key2]...[keyN]""" 9 | acc = source 10 | for key in keys: 11 | acc = acc.get(key, {}) 12 | return int(acc) if acc else None 13 | 14 | 15 | def _wrangle_entry(source: dict) -> dict: 16 | data = source.copy() 17 | if base_cal := data.get('baseCalendar'): 18 | data['baseCalendar'] = base_cal['objectId'] 19 | 20 | data['calendarBeginStatic'] = _wrangle_year(data, 'calendarBegin', 'staticYear') 21 | data['calendarBeginOffset'] = _wrangle_year( 22 | data, 'calendarBegin', 'dynamicYearOffset' 23 | ) 24 | data['calendarEndStatic'] = _wrangle_year(data, 'calendarEnd', 'staticYear') 25 | data['calendarEndOffset'] = _wrangle_year(data, 'calendarEnd', 'dynamicYearOffset') 26 | del data['calendarBegin'] 27 | del data['calendarEnd'] 28 | 29 | data['weekStartDay'] = data['weekStartDay'].lower() 30 | data = helper.camel_to_snake(data) 31 | 32 | return data 33 | 34 | 35 | def get_calendar(connection: Connection, id: str) -> dict: 36 | data = calendars_api.get_calendar(connection, id).json() 37 | return _wrangle_entry(data) 38 | 39 | 40 | def list_calendars( 41 | connection: Connection, 42 | subtype: str | None = None, 43 | limit: int | None = None, 44 | offset: int | None = None, 45 | ) -> list[dict]: 46 | data = calendars_api.list_calendars( 47 | connection=connection, 48 | subtype=subtype, 49 | offset=offset, 50 | limit=limit, 51 | ).json()['calendars'] 52 | return [_wrangle_entry(entry) for entry in data] 53 | 54 | 55 | def create_calendar( 56 | connection: Connection, 57 | body: dict, 58 | ): 59 | data = calendars_api.create_calendar(connection, body=body).json() 60 | return _wrangle_entry(data) 61 | 62 | 63 | def update_calendar( 64 | connection: Connection, 65 | id: str, 66 | body: dict, 67 | ): 68 | data = calendars_api.update_calendar(connection, id, body=body).json() 69 | return _wrangle_entry(data) 70 | -------------------------------------------------------------------------------- /mstrio/utils/response_processors/datasources.py: -------------------------------------------------------------------------------- 1 | from mstrio.api import datasources as datasources_api 2 | from mstrio.connection import Connection 3 | from mstrio.utils import helper 4 | 5 | 6 | def get_mappings( 7 | connection: Connection, 8 | project_id: str | None = None, 9 | limit: int | None = None, 10 | default_connection_map: bool = False, 11 | filters: str = None, 12 | ): 13 | helper.validate_param_value('limit', limit, int, min_val=1, special_values=[None]) 14 | response = datasources_api.get_datasource_mappings( 15 | connection=connection, 16 | default_connection_map=default_connection_map, 17 | project_id=project_id, 18 | ) 19 | if response.ok: 20 | response = response.json() 21 | for mapping in response['mappings']: 22 | if 'locale' not in mapping: 23 | mapping['locale'] = {'name': '', 'id': ''} 24 | mappings = helper._prepare_objects(response, filters, 'mappings', project_id) 25 | if limit: 26 | mappings = mappings[:limit] 27 | return mappings 28 | else: 29 | return [] 30 | 31 | 32 | def update_project_datasources(connection: Connection, id: str, body: dict): 33 | return ( 34 | datasources_api.update_project_datasources( 35 | connection=connection, id=id, body=body 36 | ) 37 | .json() 38 | .get('datasources') 39 | ) 40 | 41 | 42 | def execute_query(connection: Connection, body: dict, id: str, project_id: str): 43 | return datasources_api.execute_query( 44 | connection=connection, id=id, project_id=project_id, body=body 45 | ).json() 46 | 47 | 48 | def get_query_results(connection: Connection, id: str): 49 | return datasources_api.get_query_results(connection=connection, id=id).json() 50 | -------------------------------------------------------------------------------- /mstrio/utils/response_processors/drivers.py: -------------------------------------------------------------------------------- 1 | from mstrio.api import drivers as drivers_api 2 | from mstrio.connection import Connection 3 | from mstrio.utils.helper import rename_dict_keys 4 | 5 | REST_ATTRIBUTES_MAP = {'enabled': 'isEnabled'} 6 | 7 | 8 | def get_all(connection: Connection) -> list[dict]: 9 | """Get information for all drivers. 10 | 11 | Args: 12 | connection: Strategy One REST API connection object 13 | 14 | Returns: 15 | list of dict representing driver objects 16 | """ 17 | drivers = drivers_api.get_drivers(connection).json()['drivers'].values() 18 | return [rename_dict_keys(item, REST_ATTRIBUTES_MAP) for item in drivers] 19 | 20 | 21 | def get(connection: Connection, id: str) -> dict: 22 | """Get driver by a specific ID. 23 | 24 | Args: 25 | connection: Strategy One REST API connection object 26 | id: ID of the driver 27 | 28 | Returns: 29 | dict representing driver object 30 | """ 31 | data = drivers_api.get_driver(connection, id).json() 32 | return rename_dict_keys(data, REST_ATTRIBUTES_MAP) 33 | 34 | 35 | def update( 36 | connection: Connection, 37 | id: str, 38 | body: dict, 39 | ) -> dict: 40 | """Update a driver. 41 | 42 | Args: 43 | connection: Strategy One REST API connection object 44 | id: ID of the driver 45 | body: Driver update info. 46 | 47 | Returns: 48 | dict representing driver object 49 | """ 50 | data = drivers_api.update_driver(connection, id, body).json() 51 | return rename_dict_keys(data, REST_ATTRIBUTES_MAP) 52 | -------------------------------------------------------------------------------- /mstrio/utils/response_processors/gateways.py: -------------------------------------------------------------------------------- 1 | from mstrio.api import gateways as gateways_api 2 | from mstrio.connection import Connection 3 | from mstrio.utils.helper import rename_dict_keys 4 | 5 | REST_ATTRIBUTES_MAP = {'type': 'gatewayType', 'certifiedAsGateway': 'isCertified'} 6 | 7 | 8 | def get_all(connection: Connection) -> list[dict]: 9 | """Get information for all gateways. 10 | 11 | Args: 12 | connection: Strategy One REST API connection object 13 | 14 | Returns: 15 | list of dict representing gateway objects 16 | """ 17 | gateways = gateways_api.get_gateways(connection).json()['gateways'].values() 18 | return [rename_dict_keys(item, REST_ATTRIBUTES_MAP) for item in gateways] 19 | 20 | 21 | def get(connection: Connection, id: str) -> dict: 22 | """Get gateway by a specific ID. 23 | 24 | Args: 25 | connection: Strategy One REST API connection object 26 | id: ID of the gateway 27 | 28 | Returns: 29 | dict representing gateway object 30 | """ 31 | data = gateways_api.get_gateway(connection, id).json() 32 | return rename_dict_keys(data, REST_ATTRIBUTES_MAP) 33 | -------------------------------------------------------------------------------- /mstrio/utils/response_processors/migrations.py: -------------------------------------------------------------------------------- 1 | from mstrio.api import migration as migrations_api 2 | from mstrio.connection import Connection 3 | from mstrio.utils.helper import rename_dict_keys 4 | 5 | REST_ATTRIBUTES_MAP = { 6 | 'packageInfo': 'package_info', 7 | 'importInfo': 'import_info', 8 | } 9 | 10 | 11 | def get(connection: Connection, id: str) -> dict: 12 | """Get migration by a specific ID. 13 | 14 | Args: 15 | connection: Strategy One REST API connection object 16 | id: ID of the Migration 17 | 18 | Returns: 19 | dict representing a migration object 20 | """ 21 | data = migrations_api.get_migration( 22 | connection, migration_id=id, show_content='default' 23 | ).json() 24 | renamed_data = rename_dict_keys(data, REST_ATTRIBUTES_MAP) 25 | renamed_data['name'] = renamed_data['package_info']['name'] 26 | return renamed_data 27 | -------------------------------------------------------------------------------- /mstrio/utils/response_processors/monitors.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from mstrio.api import monitors as monitors_api 4 | 5 | if TYPE_CHECKING: 6 | from mstrio.connection import Connection 7 | 8 | 9 | def get_contents_caches_loop( 10 | connection: 'Connection', 11 | project_id: str, 12 | node: str, 13 | offset: int = 0, 14 | limit_per_request: int = 1000, 15 | status: str | None = None, 16 | content_type: str | None = None, 17 | content_format: str | None = None, 18 | size: str | None = None, 19 | owner: str | None = None, 20 | expiration: str | None = None, 21 | last_updated: str | None = None, 22 | hit_count: str | None = None, 23 | sort_by: str | None = None, 24 | fields: str | None = None, 25 | error_msg: str | None = None, 26 | ) -> list[dict]: 27 | response = monitors_api.get_contents_caches( 28 | connection, 29 | project_id, 30 | node, 31 | offset, 32 | limit_per_request, 33 | status, 34 | content_type, 35 | content_format, 36 | size, 37 | owner, 38 | expiration, 39 | last_updated, 40 | hit_count, 41 | sort_by, 42 | fields, 43 | error_msg, 44 | ) 45 | caches = response.json() 46 | total_caches = caches.get('total') 47 | all_caches = caches.get('contentCaches') 48 | offset = 0 49 | while len(all_caches) < total_caches: 50 | offset += limit_per_request 51 | response = monitors_api.get_contents_caches( 52 | connection, 53 | project_id, 54 | node, 55 | offset, 56 | limit_per_request, 57 | status, 58 | content_type, 59 | content_format, 60 | size, 61 | owner, 62 | expiration, 63 | last_updated, 64 | hit_count, 65 | sort_by, 66 | fields, 67 | error_msg, 68 | ) 69 | caches = response.json() 70 | all_caches += caches.get('contentCaches') 71 | return all_caches 72 | -------------------------------------------------------------------------------- /mstrio/utils/response_processors/projects.py: -------------------------------------------------------------------------------- 1 | from mstrio.api import projects as projects_api 2 | from mstrio.connection import Connection 3 | 4 | 5 | def _prepare_rest_output_for_project_languages(response: dict, path: str) -> dict: 6 | default_key = 'defaultLanguage' if path == 'data' else 'default' 7 | default_language_id = response.get(default_key) 8 | language = response.get('languages').get(default_language_id) 9 | response[default_key] = {default_language_id: language} 10 | return response 11 | 12 | 13 | def get_project_languages(connection: Connection, id: str, path: str): 14 | """Get languages of the project. 15 | 16 | Args: 17 | connection (Connection): Strategy One REST API connection object 18 | id (string): Project ID 19 | path (string): Path to the languages. 20 | Available values: 21 | - `data` 22 | - `metadata` 23 | 24 | Returns: 25 | List of languages in form of a single dictionary. 26 | """ 27 | return ( 28 | projects_api.get_project_languages(connection=connection, id=id) 29 | .json() 30 | .get(path) 31 | .get('languages') 32 | ) 33 | 34 | 35 | def get_default_language(connection: Connection, id: str, path: str): 36 | """Get default language of the project. 37 | 38 | Args: 39 | connection (Connection): Strategy One REST API connection object 40 | id (string): Project ID 41 | path (string): Path to the languages. 42 | Available values: 43 | - `data` 44 | - `metadata` 45 | """ 46 | return _prepare_rest_output_for_project_languages( 47 | projects_api.get_project_languages(connection=connection, id=id) 48 | .json() 49 | .get(path), 50 | path, 51 | ).get('defaultLanguage' if path == 'data' else 'default') 52 | 53 | 54 | def update_project_languages(connection: Connection, id: str, body: dict, path: str): 55 | return ( 56 | projects_api.update_project_languages(connection=connection, id=id, body=body) 57 | .json() 58 | .get(path) 59 | .get('languages') 60 | ) 61 | 62 | 63 | def update_current_mode(connection: Connection, id: str, body: dict): 64 | return ( 65 | projects_api.update_project_languages(connection=connection, id=id, body=body) 66 | .json() 67 | .get('data') 68 | .get('currentMode') 69 | ) 70 | 71 | 72 | def update_default_language(connection: Connection, id: str, body: dict): 73 | return _prepare_rest_output_for_project_languages( 74 | projects_api.update_project_languages(connection=connection, id=id, body=body) 75 | .json() 76 | .get('data'), 77 | path='data', 78 | ).get('defaultLanguage') 79 | 80 | 81 | def get_project_internalization(connection: Connection, id: str): 82 | response = projects_api.get_project_languages(connection=connection, id=id).json() 83 | 84 | for path in ['data', 'metadata']: 85 | response[path] = _prepare_rest_output_for_project_languages( 86 | response=response[path], path=path 87 | ) 88 | 89 | return response 90 | 91 | 92 | def get_project_lock(connection: Connection, id: str): 93 | return { 94 | 'lock_status': projects_api.get_project_lock( 95 | connection=connection, id=id 96 | ).json() 97 | } 98 | -------------------------------------------------------------------------------- /mstrio/utils/response_processors/subscriptions.py: -------------------------------------------------------------------------------- 1 | from mstrio.api import subscriptions as subscriptions_api 2 | from mstrio.connection import Connection 3 | from mstrio.helpers import IServerError 4 | from mstrio.utils.version_helper import is_server_min_version 5 | 6 | 7 | def get_subscription_status(connection: Connection, id: str): 8 | response = subscriptions_api.get_subscription_status( 9 | connection=connection, id=id, whitelist=[('ERR002', 500)] 10 | ) 11 | res = response.json() 12 | server_msg = res.get('message') 13 | 14 | if not server_msg: 15 | return {'status': res} 16 | 17 | if ( 18 | 'No status for the subscription' in server_msg 19 | or 'This endpoint is disabled' in server_msg 20 | ): 21 | return {'status': None} 22 | 23 | server_code = res.get('code') 24 | ticket_id = res.get('ticketId') 25 | raise IServerError( 26 | message=f"{server_msg}; code: '{server_code}', ticket_id: '{ticket_id}'", 27 | http_code=response.status_code, 28 | ) 29 | 30 | 31 | def get_subscription_last_run(connection: Connection, id: str, project_id: str): 32 | if not is_server_min_version(connection, '11.4.0600'): 33 | return None 34 | 35 | response = subscriptions_api.list_subscriptions( 36 | connection=connection, project_id=project_id, last_run=True 37 | ).json()['subscriptions'] 38 | sub = next(sub for sub in response if sub['id'] == id).get('lastRun') 39 | 40 | return {'last_run': sub} 41 | -------------------------------------------------------------------------------- /mstrio/utils/response_processors/tables.py: -------------------------------------------------------------------------------- 1 | from mstrio.api import tables as tables_api 2 | from mstrio.connection import Connection 3 | 4 | 5 | def update_structure( 6 | connection: Connection, 7 | id: str, 8 | column_merge_option: str | None = None, 9 | ignore_table_prefix: bool | None = None, 10 | ): 11 | data = tables_api.update_structure( 12 | connection, id, column_merge_option, ignore_table_prefix 13 | ).json() 14 | 15 | data.update(data.pop('information', {})) 16 | data.update({'id': data.pop('objectId', None)}) 17 | 18 | return data 19 | -------------------------------------------------------------------------------- /mstrio/utils/response_processors/usergroups.py: -------------------------------------------------------------------------------- 1 | from mstrio.api import usergroups as usergroups_api 2 | from mstrio.utils.helper import camel_to_snake 3 | from mstrio.utils.version_helper import is_server_min_version 4 | 5 | 6 | def update_user_group_info(connection, id, body): 7 | """Update user group by a specified ID. 8 | 9 | Args: 10 | connection: Strategy One REST API connection object 11 | id: ID of the user group 12 | body: dict with changes to be made 13 | 14 | Returns: 15 | dict with user group information 16 | """ 17 | # We do not want information about members from patch as it is insufficient 18 | # to differentiate between users and user groups 19 | response = usergroups_api.update_user_group_info( 20 | connection=connection, id=id, body=body 21 | ) 22 | result = response.json() 23 | if result.get('members'): 24 | result.pop('members') 25 | 26 | return result 27 | 28 | 29 | def get_settings( 30 | connection, id, include_access=False, offset=0, limit=-1, error_msg=None 31 | ): 32 | response = usergroups_api.get_settings( 33 | connection=connection, 34 | id=id, 35 | include_access=include_access, 36 | offset=offset, 37 | limit=limit, 38 | error_msg=error_msg, 39 | ).json() 40 | 41 | if is_server_min_version(connection, '11.4.1200'): 42 | list_of_settings = [ 43 | {key: value} for key, value in camel_to_snake(response).items() 44 | ] 45 | response = {'settings': list_of_settings} 46 | 47 | return response 48 | -------------------------------------------------------------------------------- /mstrio/utils/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroStrategy/mstrio-py/da214217478831a20fb9f20f6f148d4a1ec80ab7/mstrio/utils/settings/__init__.py -------------------------------------------------------------------------------- /mstrio/utils/settings/settings_helper.py: -------------------------------------------------------------------------------- 1 | def convert_settings_to_byte(settings: dict, mapping: dict) -> dict: 2 | def convert(setting, value): 3 | unit = mapping.get(setting) 4 | if unit is not None: 5 | if unit == 'B': 6 | return value * (1024**2) 7 | elif unit == 'KB': 8 | return value * 1024 9 | return value 10 | 11 | return {setting: convert(setting, value) for setting, value in settings.items()} 12 | 13 | 14 | def convert_settings_to_mega_byte(settings: dict, mapping: dict) -> dict: 15 | def convert(setting, value): 16 | unit = mapping.get(setting) 17 | if unit is not None: 18 | if unit == 'B': 19 | return value // (1024**2) 20 | elif unit == 'KB': 21 | return value // 1024 22 | return value 23 | 24 | return {setting: convert(setting, value) for setting, value in settings.items()} 25 | -------------------------------------------------------------------------------- /strategy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroStrategy/mstrio-py/da214217478831a20fb9f20f6f148d4a1ec80ab7/strategy-logo.png -------------------------------------------------------------------------------- /workflows/add_email_to_new_users.py: -------------------------------------------------------------------------------- 1 | """Add email address with a form `{username}@domain.com` to every user 2 | which is enabled but doesn't have an email address. For each successfully added 3 | email address a message will be printed. 4 | 5 | 1. Connect to the environment using data from workstation 6 | 2. Get a list of all users that are enabled 7 | 3. Add email address to every user which doesn't have one (it is created with username 8 | of the user and the domain provided in the function) 9 | """ 10 | 11 | from mstrio.connection import Connection, get_connection 12 | from mstrio.users_and_groups import list_users, User 13 | 14 | 15 | def add_email_to_new_users( 16 | connection: "Connection", domain="domain.com" 17 | ) -> list["User"]: 18 | """Add email address with a form `{username}@domain.com` 19 | to every user which is enabled but doesn't have an email address. 20 | For each successfully added email address a message will be printed. 21 | 22 | Args: 23 | connection: Strategy One connection object returned by 24 | `connection.Connection()` 25 | domain: name of the domain in the email address (it should be 26 | provided without '@' symbol). Default value is "domain.com". 27 | 28 | Returns: 29 | list of users to which email addresses where added 30 | """ 31 | # get all users that are enabled 32 | all_users = list_users(connection=connection) 33 | users_ = [u for u in all_users if u.enabled] 34 | modified_users_ = [] 35 | for user_ in users_: 36 | # add email address only for those users which don't have one 37 | if not user_.addresses: 38 | email_address = f'{user_.username}@{domain}' 39 | user_.add_address(name=user_.username, address=email_address) 40 | modified_users_.append(user_) 41 | 42 | return modified_users_ 43 | 44 | 45 | # connect to environment without providing user credentials 46 | # variable `workstationData` is stored within Workstation 47 | conn = get_connection(workstationData, 'MicroStrategy Tutorial') 48 | 49 | # execute adding email to new users 50 | # optionally, specify a domain - the default is 'domain.com' 51 | add_email_to_new_users(conn) 52 | -------------------------------------------------------------------------------- /workflows/assign_ACLs_for_objects.py: -------------------------------------------------------------------------------- 1 | """ 2 | Manage access control lists (ACLs). 3 | Assign ACLs to user groups and its children for folders. 4 | 5 | 1. Connect to the environment using data from workstation 6 | 2. Assign ACL with View aggregated rights to user group with name 'Mobile' for 7 | user 'Administrator' 8 | 3. Assign ACL with View aggregated rights to user group with name 'Mobile' for 9 | user 'Administrator' with propagation to children of this user group 10 | 4. Assign ACL with every right except Execution to folder with given id for user 11 | 'Administrator' 12 | 5. Assign ACL with every right except Execution to folder with given id for user 13 | 'Administrator' with propagation to children of this folder 14 | """ 15 | 16 | from mstrio.connection import get_connection 17 | from mstrio.object_management.folder import Folder 18 | from mstrio.helpers import AggregatedRights 19 | from mstrio.users_and_groups.user import User 20 | from mstrio.users_and_groups.user_group import list_user_groups 21 | 22 | # connect to environment without providing user credentials 23 | # variable `workstationData` is stored within Workstation 24 | conn = get_connection(workstationData, project_name='MicroStrategy Tutorial') 25 | 26 | # assign ACL AggregatedRights.VIEW to UserGroup 27 | user_group = list_user_groups(conn, name='Mobile')[0] 28 | user_group.acl_add( 29 | rights=AggregatedRights.VIEW, 30 | trustees=[User(conn, name='Administrator')], 31 | denied=False, 32 | ) 33 | 34 | # assign ACL AggregatedRights.VIEW to UserGroup and propagate it to children 35 | user_group = list_user_groups(conn, name='Mobile')[0] 36 | user_group.acl_add( 37 | rights=AggregatedRights.VIEW, 38 | trustees=[User(conn, name='Administrator')], 39 | denied=False, 40 | propagate_to_children=True, 41 | ) 42 | 43 | # assign ACLs for folder 44 | folder = Folder(conn, id='945A968E40E4EA2C9114BBBE885DBC24') 45 | folder.acl_add(rights=127, trustees=[User(conn, name='Administrator')], denied=False) 46 | 47 | # assign ACLs for folder and propagate to children 48 | folder = Folder(conn, id='945A968E40E4EA2C9114BBBE885DBC24') 49 | folder.acl_add( 50 | rights=127, 51 | trustees=[User(conn, name='Administrator')], 52 | denied=False, 53 | propagate_to_children=True, 54 | ) 55 | -------------------------------------------------------------------------------- /workflows/control_users_and_security.py: -------------------------------------------------------------------------------- 1 | """ 2 | Change password for one or more users. 3 | 4 | 1. Connect to the environment using data from workstation 5 | 2. Change password for all users which name begins with 'User_S' 6 | 3. Change password for only first user which name begins with 'User_S' 7 | 4. Change password for only first user which name begins with 'User_S' and 8 | require new password on the next login 9 | 5. Change password for all users which name begins with 'User_S' and require 10 | new password on the next login 11 | """ 12 | 13 | from mstrio.connection import get_connection 14 | from mstrio.users_and_groups.user import list_users 15 | 16 | # connect to environment without providing user credentials 17 | # variable `workstationData` is stored within Workstation 18 | conn = get_connection(workstationData, project_name='MicroStrategy Tutorial') 19 | 20 | # change password for all users 21 | for user in list_users(conn, name_begins='User_S'): 22 | user.alter(password='new_password') 23 | 24 | # change user password 25 | user = list_users(conn, name_begins='User_S')[0] 26 | user.alter(password='new_password') 27 | 28 | # change user password with option require_new_password 29 | user.alter(password='new_password', require_new_password=True) 30 | 31 | # change password for all users with option require_new_password 32 | for user in list_users(conn, name_begins='User_S'): 33 | user.alter(password='new_password', require_new_password=True) 34 | -------------------------------------------------------------------------------- /workflows/create_bursting_subscription.py: -------------------------------------------------------------------------------- 1 | """Create a bursting subscription. 2 | 1. Connect to the environment using data from workstation 3 | 2. Create a bursting configuration 4 | - 2.1 using objects 5 | - 2.2 using IDs 6 | 3. Create an email subscription with created bursting configuration 7 | """ 8 | 9 | from mstrio.connection import get_connection 10 | from mstrio.distribution_services import Content, Device, EmailSubscription, Schedule 11 | from mstrio.modeling import Attribute, AttributeForm 12 | 13 | conn = get_connection(workstationData, project_name='MicroStrategy Tutorial') 14 | 15 | 16 | # Create bursting configuration, using objects 17 | bursting = Content.Properties.Bursting( 18 | slicing_attributes=[Attribute(conn, id='8D679D4311D3E4981000E787EC6DE8A4')], 19 | address_attribute=Attribute(conn, id='8D679D4311D3E4981000E787EC6DE8A4'), 20 | device=Device(conn, id='1D2E6D168A7711D4BE8100B0D04B6F0B'), 21 | form=AttributeForm(conn, id='8A9EFEDB11D60C62C000CC8F9590594F'), 22 | ) 23 | 24 | # Alternatively, create bursting configuration using IDs. 25 | # However, the `slicing_attributes` should be provided in the format: 26 | # '~' 27 | # bursting = Content.Properties.Bursting( 28 | # slicing_attributes=['8D679D4311D3E4981000E787EC6DE8A4~Manager'], 29 | # address_attribute_id='8D679D4311D3E4981000E787EC6DE8A4', 30 | # device_id='1D2E6D168A7711D4BE8100B0D04B6F0B', 31 | # form_id='8A9EFEDB11D60C62C000CC8F9590594F', 32 | # ) 33 | 34 | # Create bursting subscription 35 | bursting_subscription = EmailSubscription.create( 36 | connection=conn, 37 | name='Bursting Subscription', 38 | project_name='MicroStrategy Tutorial', 39 | contents=[ 40 | Content( 41 | id='', # Content should have bursting enabled 42 | type=Content.Type.REPORT, 43 | personalization=Content.Properties( 44 | format_type=Content.Properties.FormatType.PDF, 45 | bursting=bursting, 46 | ), 47 | ), 48 | ], 49 | schedules=Schedule(conn, name='Monday Morning'), 50 | recipients=['54F3D26011D2896560009A8E67019608'], # Administrator 51 | email_subject='Bursting Subscription', 52 | ) 53 | -------------------------------------------------------------------------------- /workflows/create_content_group_and_application.py: -------------------------------------------------------------------------------- 1 | from mstrio.connection import get_connection 2 | from mstrio.project_objects.content_group import ContentGroup 3 | from mstrio.project_objects.applications import Application 4 | from mstrio.project_objects.dashboard import Dashboard 5 | from mstrio.users_and_groups import User, UserGroup 6 | 7 | conn = get_connection(workstationData, 'MicroStrategy Tutorial') 8 | 9 | # Create a Content Group 10 | cg = ContentGroup.create( 11 | connection=conn, 12 | name='Content Group for Application', 13 | color='#ffe4e1', 14 | opacity=100, 15 | email_enabled=True, 16 | recipients=[ 17 | User(connection=conn, id='0870757440D0FEB7CDC449AAE6C18AFF'), 18 | UserGroup(connection=conn, id='E96685CD4E60068559F7DFAC7C2AA851'), 19 | ], 20 | ) 21 | 22 | # Add some contents to the Content Group 23 | # Add Contents to a Content Group 24 | cg.update_contents( 25 | content_to_add=[ 26 | Dashboard(connection=conn, id='F6252B9211E7B348312C0080EF55DB6A'), 27 | Dashboard(connection=conn, id='27BB740E11E7A7B40A650080EF856B88'), 28 | ] 29 | ) 30 | 31 | # Create an Application 32 | # To limit the Application to only the specified group, pass the 33 | # ContentGroup ID in the content_bundle_ids of the home_library settings 34 | # And make sure to set show_all_contents to False 35 | 36 | # Only the necessary variables are defined here 37 | app = Application.create( 38 | connection=conn, 39 | name='Application Workflow Showcase', 40 | description='This is a demo application created for content group.', 41 | home_screen=Application.HomeSettings( 42 | mode=0, 43 | home_library=Application.HomeSettings.HomeLibrary( 44 | content_bundle_ids=[cg.id], 45 | show_all_contents=False, 46 | ), 47 | ), 48 | ) 49 | -------------------------------------------------------------------------------- /workflows/create_mobile_subscription.py: -------------------------------------------------------------------------------- 1 | """Create a mobile subscription for a selected dashboard and schedule. 2 | 1. Connect to the environment using data from workstation 3 | 2. List Dashboards and select one that will be used as Content for the subscription 4 | 3. List Schedules and select one that will be used for the subscription 5 | 4. Define the user that will receive the subscription 6 | 5. Create Mobile Subscription with expiration date 7 | 6. Execute created mobile subscription 8 | """ 9 | 10 | from mstrio.connection import get_connection 11 | from mstrio.distribution_services.schedule.schedule import list_schedules 12 | from mstrio.distribution_services.subscription import Content, MobileSubscription 13 | from mstrio.project_objects import list_dashboards 14 | from mstrio.users_and_groups import list_users 15 | 16 | PROJECT_NAME = 'MicroStrategy Tutorial' # Insert name of project here 17 | 18 | conn = get_connection(workstationData, PROJECT_NAME) 19 | 20 | # List Dashboards and select one that will be used as Content for the subscription 21 | dashboards = list_dashboards(conn) 22 | selected_dashboard = dashboards[0] 23 | 24 | # List Schedules and select one that will be used for the subscription 25 | # It will be 'At Close of Business (Weekday)' in our example here 26 | schedules = list_schedules(conn) 27 | selected_schedule = schedules[1] 28 | print(selected_schedule) 29 | 30 | # Define the user that will receive the subscription 31 | # It will be 'Administrator' in our example here 32 | users = list_users(conn, 'Administrator') 33 | selected_user = users[0] 34 | print(selected_user) 35 | 36 | # Create Mobile Subscription 37 | mobile_sub = MobileSubscription.create( 38 | connection=conn, 39 | name='', 40 | schedules=[selected_schedule], 41 | contents=Content( 42 | id=selected_dashboard.id, 43 | type=Content.Type.DASHBOARD, 44 | personalization=Content.Properties( 45 | format_type=Content.Properties.FormatType.HTML 46 | ), 47 | ), 48 | recipients=[selected_user.id], 49 | delivery_expiration_date='2024-12-31', 50 | delivery_expiration_timezone='Europe/London', 51 | ) 52 | 53 | # Execute the subscription 54 | mobile_sub.execute() 55 | -------------------------------------------------------------------------------- /workflows/delete_addresses_from_departed_users.py: -------------------------------------------------------------------------------- 1 | """Remove each address from every departed user (those which are disabled). 2 | For each successfully removed address a message will be printed. 3 | 4 | 1. Connect to the environment using data from workstation 5 | 2. Get all users that are disabled 6 | 3. For every retrieved user remove all its email addresses 7 | 4. Return list which contains information about removed addresses such as user's id, 8 | username and address 9 | """ 10 | 11 | from typing import List 12 | 13 | from mstrio.connection import Connection, get_connection 14 | from mstrio.users_and_groups import list_users 15 | 16 | 17 | def delete_addresses_from_departed_users(connection: "Connection") -> List[dict]: 18 | """Remove each address from every departed user (those which are disabled). 19 | For each successfully removed address a message will be printed. 20 | 21 | Args: 22 | connection: Strategy One connection object returned by 23 | `connection.Connection()` 24 | """ 25 | 26 | # get all users that are disabled 27 | all_users = list_users(connection=connection) 28 | users_ = [u for u in all_users if not u.enabled] 29 | removed_addresses = [] 30 | for user_ in users_: 31 | # remove all email addresses from the given user 32 | if user_.addresses: 33 | for address in user_.addresses: 34 | user_.remove_address(id=address['id']) 35 | 36 | removed_addresses.append( 37 | { 38 | 'user_id': user_.id, 39 | 'username': user_.username, 40 | 'address': address, 41 | } 42 | ) 43 | return removed_addresses 44 | 45 | 46 | # connect to environment without providing user credentials 47 | # variable `workstationData` is stored within Workstation 48 | conn = get_connection(workstationData, 'MicroStrategy Tutorial') 49 | 50 | # execute deletion of addresses from departed users 51 | delete_addresses_from_departed_users(conn) 52 | -------------------------------------------------------------------------------- /workflows/delete_inactive_cube_caches.py: -------------------------------------------------------------------------------- 1 | """Delete inactive caches which were not used for 30 days. IDs of deleted caches 2 | will be printed. 3 | 4 | 1. Connect to the environment using data from workstation 5 | 2. Validate if the project is selected 6 | 3. List all cube caches for the provided nodes (if nodes are not specified, then 7 | all nodes' names are loaded from the cluster) 8 | 4. Delete inactive caches which are older than specified number of days 9 | (by default 30) 10 | 5. Return every deleted cache object in a list 11 | 6. Print ids of all deleted caches 12 | """ 13 | 14 | from datetime import datetime, timezone 15 | from typing import List, Union 16 | 17 | from mstrio.connection import Connection, get_connection 18 | from mstrio.project_objects import CubeCache, list_cube_caches 19 | 20 | 21 | def _get_datetime(date): 22 | """String to date time conversion""" 23 | return datetime.strptime(date, '%Y-%m-%dT%H:%M:%S.000%z') 24 | 25 | 26 | def delete_inactive_caches( 27 | connection: "Connection", days_diff: str = 30, nodes: Union[str, List[str]] = None 28 | ) -> List["CubeCache"]: 29 | """Delete inactive caches which have `hit_count` equals 0 and their 30 | `last_time_updated` was earlier than `days_diff` before. 31 | 32 | Args: 33 | connection: Strategy One connection object returned by 34 | `connection.Connection()` 35 | days_diff (int, optional): number of days to determine whether to delete 36 | cache when its `hit_count` equals 0. Default value is 30. 37 | nodes (list of strings or string, optional): names of nodes from which 38 | caches will be deleted. By default, it equals `None` and in that 39 | case all nodes' names are loaded from the cluster. 40 | 41 | Return: 42 | list with cache objects which were deleted 43 | """ 44 | connection._validate_project_selected() 45 | caches = list_cube_caches(connection, nodes) 46 | # delete caches which fulfill requirements to be treated as inactive 47 | deleted_caches = [] 48 | for cache in caches: 49 | today = datetime.now(timezone.utc) 50 | if ( 51 | cache.hit_count == 0 52 | and (today - _get_datetime(cache.last_update_time)).days > days_diff 53 | ): 54 | cache.delete(force=True) 55 | deleted_caches.append(cache) 56 | 57 | return deleted_caches 58 | 59 | 60 | # connect to environment without providing user credentials 61 | # variable `workstationData` is stored within Workstation 62 | conn = get_connection(workstationData, 'MicroStrategy Tutorial') 63 | 64 | # execute deletion of inactive cube caches 65 | deleted_caches = delete_inactive_caches(conn) 66 | 67 | print('IDs of deleted caches') 68 | for deleted_cache in deleted_caches: 69 | print(deleted_cache.id) 70 | -------------------------------------------------------------------------------- /workflows/delete_subscriptions_of_departed_users.py: -------------------------------------------------------------------------------- 1 | """Delete all subscription in all projects which owners are departed users. 2 | For which project message about successful (or not) deletion will be printed. 3 | 4 | 1. Connect to the environment using data from workstation 5 | 2. Get all projects that the authenticated user has access to 6 | 3. Get all disabled users 7 | 4. For each project for each user get the list of subscriptions and delete them 8 | 5. For each deletion process message is displayed informing about the result 9 | """ 10 | 11 | from mstrio.api.projects import get_projects 12 | from mstrio.connection import Connection, get_connection 13 | from mstrio.distribution_services import SubscriptionManager 14 | from mstrio.users_and_groups import list_users 15 | 16 | 17 | def delete_subscriptions_of_departed_users(connection: "Connection") -> None: 18 | """Delete all subscription in all projects which owners are departed users. 19 | 20 | Args: 21 | Args: 22 | connection: Strategy One connection object returned by 23 | `connection.Connection()` 24 | """ 25 | 26 | # get all projects that the authenticated user has access to 27 | response = get_projects(connection, whitelist=[('ERR014', 403)]) 28 | projects_ = response.json() if response.ok else [] 29 | # get all disabled users 30 | all_users = list_users(connection=connection) 31 | disabled_users = [u for u in all_users if not u.enabled] 32 | 33 | for project_ in projects_: 34 | project_id = project_['id'] 35 | sub_manager = SubscriptionManager(connection=connection, project_id=project_id) 36 | for user_ in disabled_users: 37 | subs = sub_manager.list_subscriptions(owner={'id': user_.id}) 38 | msg = f"subscriptions of user with ID: {user_.id}" 39 | msg += f" in project {project_['name']} with ID: {project_['id']}" 40 | # call of the function below returns True if all passed 41 | # subscriptions were deleted 42 | if sub_manager.delete(subscriptions=subs, force=True): 43 | print("All " + msg + " were deleted.", flush=True) 44 | else: 45 | print( 46 | "Not all " + msg + " were deleted or there was no subscriptions.", 47 | flush=True, 48 | ) 49 | 50 | 51 | # connect to environment without providing user credentials 52 | # variable `workstationData` is stored within Workstation 53 | conn = get_connection(workstationData, 'MicroStrategy Tutorial') 54 | 55 | # execute deletion of addresses from departed users 56 | delete_subscriptions_of_departed_users(conn) 57 | -------------------------------------------------------------------------------- /workflows/disable_all_active_bots.py: -------------------------------------------------------------------------------- 1 | """ 2 | Disable all active bots in a project. 3 | 4 | 1. Provide name of the project to connect to 5 | 2. Connect to the environment using data from workstation 6 | 3. Get a list of all bots in the project 7 | 4. Disable every active bot on the list 8 | """ 9 | 10 | from mstrio.connection import get_connection 11 | from mstrio.project_objects.bots import list_bots 12 | 13 | # Define project to connect to 14 | PROJECT_NAME = 'MicroStrategy Tutorial' 15 | 16 | # Connect to environment without providing user credentials 17 | # Variable `workstationData` is stored within Workstation 18 | conn = get_connection(workstationData, project_name=PROJECT_NAME) 19 | 20 | # Get a list of bots in the project 21 | bots = list_bots(connection=conn) 22 | 23 | # Disable every active bot 24 | for bot in bots: 25 | if bot.status == 'enabled': 26 | bot.disable() 27 | -------------------------------------------------------------------------------- /workflows/duplicate_user_subscriptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Duplicate user subscriptions across all projects. 3 | 4 | 1. Connect to the environment using data from workstation 5 | 2. Get user with the given id 6 | 2. Get all projects that the authenticated user has access to 7 | 4. For each project get all subscriptions of the user which was earlier 8 | retrieved 9 | 5. Duplicate each received subscription and change its name by adding " Copy" at 10 | the end and specify new owner by providing some other id 11 | """ 12 | 13 | from mstrio.api.projects import get_projects 14 | from mstrio.api.subscriptions import create_subscription 15 | from mstrio.connection import Connection, get_connection 16 | from mstrio.distribution_services import list_subscriptions 17 | from mstrio.users_and_groups import User 18 | 19 | 20 | def duplicate_user_subscriptions(connection: Connection, owner: str | User) -> None: 21 | """Duplicate user subscriptions across all projects. 22 | 23 | Args: 24 | connection (Connection): Strategy One connection object returned by 25 | `connection.Connection()` 26 | owner (str, User): The owner of the subscriptions to duplicate. 27 | """ 28 | 29 | # get all projects that the authenticated user has access to 30 | response = get_projects(connection, whitelist=[('ERR014', 403)]) 31 | projects = response.json() if response.ok else [] 32 | 33 | owner_id = owner.id if isinstance(owner, User) else owner 34 | 35 | for project in projects: 36 | project_id = project['id'] 37 | subscriptions = list_subscriptions( 38 | connection=connection, 39 | project_id=project_id, 40 | owner={'id': owner_id}, 41 | ) 42 | for subscription in subscriptions: 43 | subscription_dict = subscription.to_dict() 44 | # Update subscription name and owner 45 | subscription_dict.update( 46 | name=f'{subscription_dict["name"]} Copy', 47 | owner={'id': '51F189429A439FAA734550A753978285'}, 48 | ) 49 | create_subscription( 50 | connection, project_id=project_id, body=subscription_dict 51 | ) 52 | 53 | 54 | # connect to environment without providing user credentials 55 | # variable `workstationData` is stored within Workstation 56 | conn = get_connection(workstationData, 'MicroStrategy Tutorial') 57 | 58 | user = User(connection=conn, id='7FC05A65473CE2FD845CE6A1D3F13233') 59 | duplicate_user_subscriptions(conn, user) 60 | -------------------------------------------------------------------------------- /workflows/export_selected_information_to_csv.py: -------------------------------------------------------------------------------- 1 | """ 2 | Export selected information about users and user groups to a CSV file. 3 | 4 | 1. Provide name of the project to connect to. 5 | 2. Connect to the environment using data from workstation. 6 | 3. Define filename where exported data will be saved. 7 | 4. Open the file and write the header row. 8 | 5. List all user groups and write their information to the file. 9 | 6. List all users and write their information to the file. 10 | """ 11 | 12 | from mstrio.connection import get_connection 13 | from mstrio.users_and_groups import ( 14 | list_user_groups, 15 | list_users, 16 | ) 17 | import csv 18 | 19 | # Define project to connect with 20 | PROJECT_NAME = 'MicroStrategy Tutorial' # Insert name of project here 21 | 22 | conn = get_connection(workstationData, PROJECT_NAME) 23 | 24 | # Define filename where exported data will be saved. In Workstation you need to 25 | # provide absolute path to the file remembering about double backslashes on WIN 26 | # for example: 'C:\\Users\\admin\\Documents\\data\\users_and_user_groups.csv' 27 | FILE_PATH = '' 28 | with open(FILE_PATH, mode='w', newline='') as file: 29 | file_writer = csv.writer( 30 | file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL 31 | ) 32 | file_writer.writerow( 33 | [ 34 | 'Full Name', 35 | 'User ID', 36 | 'Username (Login)', 37 | 'Enabled', 38 | 'Date Created', 39 | 'Date Modified', 40 | 'Description', 41 | ] 42 | ) 43 | 44 | user_groups = list_user_groups(connection=conn) 45 | for user_group in user_groups: 46 | file_writer.writerow( 47 | [ 48 | user_group.name, 49 | user_group.id, 50 | user_group.name, 51 | 'N/A', 52 | user_group.date_created, 53 | user_group.date_modified, 54 | user_group.description, 55 | ] 56 | ) 57 | 58 | users = list_users(connection=conn) 59 | for user in users: 60 | file_writer.writerow( 61 | [ 62 | user.full_name, 63 | user.id, 64 | user.username, 65 | user.enabled, 66 | user.date_created, 67 | user.date_modified, 68 | user.description, 69 | ] 70 | ) 71 | -------------------------------------------------------------------------------- /workflows/get_all_columns_in_table.py: -------------------------------------------------------------------------------- 1 | """List all the columns for all available tables 2 | 3 | 1. Connect to the environment using data from workstation 4 | 2a. This path is executed when the table is specified in argument of function 5 | 3a. Get whole representation of table based on its id 6 | 4a. Unpack the response and for the given table get all columns in a specified 7 | format with subtype, object id and name 8 | 2b. This path is executed when the table is not specified in argument of function 9 | 3b. Get all tables in the project specified in the connection object 10 | 4b. Get whole representation of each table based on their ids 11 | 5b. Unpack the response and for each table return all columns in a specified 12 | format with subtype, object id and name 13 | """ 14 | from typing import Optional, Union 15 | 16 | from mstrio.api import tables 17 | from mstrio.connection import Connection, get_connection 18 | from mstrio.modeling.schema.helpers import SchemaObjectReference 19 | from mstrio.utils.api_helpers import async_get 20 | from mstrio.utils.wip import module_wip, WipLevels 21 | 22 | module_wip(globals(), level=WipLevels.PREVIEW) 23 | 24 | 25 | def list_table_columns( 26 | connection: Connection, table: Optional[Union[SchemaObjectReference, str]] = None 27 | ) -> dict: 28 | """List all the columns for a given table, if tables is not specified, 29 | list all the columns for all available tables. 30 | 31 | Args: 32 | connection: Strategy One connection object returned 33 | by `connection.Connection()` 34 | table: SchemaObjectReference of a table or table id 35 | 36 | Returns: 37 | dictionary of all the columns for a given table, or for all tables 38 | if table is not specified e.g. 39 | {'TABLE_NAME':[ 40 | SchemaObjectReference(sub_type='column', object_id='1111', 41 | name='column_name'), 42 | SchemaObjectReference(sub_type='column', object_id='2222', 43 | name='other_column_name') 44 | ]} 45 | 46 | """ 47 | 48 | def unpack(table_res): 49 | columns_list_dict = table_res['physicalTable']['columns'] 50 | return [ 51 | SchemaObjectReference( 52 | col['information']['subType'], 53 | col['information']['objectId'], 54 | col['information']['name'], 55 | ) 56 | for col in columns_list_dict 57 | ] 58 | 59 | if table: 60 | table_id = table if isinstance(table, str) else table.object_id 61 | table_res = tables.get_table( 62 | connection, table_id, project_id=connection.project_id 63 | ).json() 64 | return {table_res['information']['name']: unpack(table_res)} 65 | else: 66 | table_res = tables.get_tables( 67 | connection, project_id=connection.project_id 68 | ).json() 69 | ids = [tab['information']['objectId'] for tab in table_res['tables']] 70 | tables_async = async_get(tables.get_table_async, connection, ids) 71 | return {table['information']['name']: unpack(table) for table in tables_async} 72 | 73 | 74 | # connect to environment without providing user credentials 75 | # variable `workstationData` is stored within Workstation 76 | conn = get_connection(workstationData, 'MicroStrategy Tutorial') 77 | 78 | # list all the columns for all available tables 79 | list_table_columns(conn) 80 | -------------------------------------------------------------------------------- /workflows/get_subscriptions_by_emails.py: -------------------------------------------------------------------------------- 1 | """ 2 | Get subscriptions by user emails. 3 | 4 | 1. Connect to the environment using data from workstation 5 | 2. Specify emails to search for in subscriptions 6 | 3. Get all subscriptions and iterate over them 7 | 4. Iteration over all user's recipients of the subscription 8 | 5. Try to get the addresses of the user from the response with subscriptions 9 | 6. If the user is not in the response with subscriptions get its addresses 10 | 7. Iterate over all email addresses of the user and print message with 11 | information about the subscription name and id and user email 12 | """ 13 | 14 | from mstrio.connection import Connection, get_connection 15 | from mstrio.distribution_services import list_subscriptions 16 | from mstrio.utils.response_processors import users 17 | 18 | 19 | def get_subscriptions_by_emails(connection: 'Connection', emails: list[str]) -> None: 20 | """Get subscriptions by user emails. 21 | 22 | Args: 23 | connection (Connection): Strategy One connection object returned by 24 | `connection.Connection()` 25 | emails (list[str]): List of emails to search for in subscriptions. 26 | """ 27 | user_addresses = {} 28 | for sub in list_subscriptions(connection=connection): 29 | # Iterate over all user recipients of the subscription 30 | for user_id in [x['id'] for x in sub.recipients if x['type'] == 'user']: 31 | try: 32 | # Try to get the addresses of the user from the dictionary 33 | # to reduce the number of API calls 34 | addresses = user_addresses[user_id] 35 | except KeyError: 36 | # If the user is not in the dictionary, get the addresses from the API 37 | addresses = users.get_addresses(conn, id=user_id)['addresses'] 38 | # Store the addresses in the dictionary for future use 39 | user_addresses[user_id] = addresses 40 | # Iterate over all email addresses of the user 41 | for email in [ 42 | addr['physicalAddress'] 43 | for addr in addresses 44 | if addr['deliveryType'] == 'email' and addr['physicalAddress'] in emails 45 | ]: 46 | print( 47 | f'Found in subscription: {sub.name} with ID: {sub.id}, ' 48 | f'user email: {email}' 49 | ) 50 | 51 | 52 | # variable `workstationData` is stored within Workstation 53 | conn = get_connection(workstationData, 'MicroStrategy Tutorial') 54 | 55 | emails_to_find = ['user_1@email.com', 'user_2@email.com'] 56 | 57 | get_subscriptions_by_emails(conn, emails_to_find) 58 | -------------------------------------------------------------------------------- /workflows/import_cube_data_into_dataframe.py: -------------------------------------------------------------------------------- 1 | """ 2 | Create a dataframe from a Cube. 3 | List available cubes, retrieve their attributes and metrics. Operate with given 4 | cube. 5 | 6 | 1. Connect to the environment using data from workstation 7 | 2. List available cubes (with limit of 10) 8 | 3. Get cube with given id and check its available attributes and metrics 9 | 4. Choose attributes and metrics for filtering 10 | 5. Publish cube before applying filters 11 | 6. Apply filters to the cube specifying attributes and metrics 12 | 7. Create a dataframe from the filtered cube 13 | 8. Print first few rows of newly created dataframe 14 | """ 15 | 16 | from time import sleep 17 | from mstrio.connection import get_connection 18 | from mstrio.project_objects.datasets import list_all_cubes, OlapCube 19 | 20 | # connect to environment without providing user credentials 21 | # variable `workstationData` is stored within Workstation 22 | connection = get_connection(workstationData, project_name='MicroStrategy Tutorial') 23 | 24 | # List available Cubes (limited to 10) 25 | cubes = list_all_cubes(connection, limit=10) 26 | for cube in cubes: 27 | print(cube) 28 | 29 | # Check available attributes and metrics of a Cube 30 | sample_cube_id = '42FF415D4E162846C87D4FAD8B26BF4E' 31 | sample_cube = OlapCube(connection, id=sample_cube_id) 32 | attributes = sample_cube.attributes 33 | metrics = sample_cube.metrics 34 | print(f"Attributes: {attributes}\nMetrics: {metrics}") 35 | 36 | # Choose attributes and metrics 37 | call_center_attribute = attributes[0].get('id') 38 | category_attribute = attributes[1].get('id') 39 | 40 | profit_metric = metrics[0].get('id') 41 | profit_margin_metric = metrics[1].get('id') 42 | 43 | # Cube needs to be published before we can apply filters 44 | sample_cube.publish() 45 | sleep(5) 46 | 47 | # Filter which attributes and metrics to use in a dataframe 48 | sample_cube.apply_filters( 49 | attributes=[call_center_attribute, category_attribute], 50 | metrics=[profit_metric, profit_margin_metric], 51 | ) 52 | 53 | # Create a dataframe from a Cube 54 | dataframe = sample_cube.to_dataframe() 55 | print(dataframe.head()) 56 | -------------------------------------------------------------------------------- /workflows/import_report_data_into_dataframe.py: -------------------------------------------------------------------------------- 1 | """ 2 | Create a dataframe from a Report. 3 | List available reports, retrieve their attributes and metrics. Operate with 4 | given report. 5 | 6 | 1. Connect to the environment using data from workstation 7 | 2. List available reports (with limit of 10) 8 | 3. Get report with given id and check its available attributes and metrics 9 | 4. Choose attributes and metrics for filtering 10 | 5. Apply filters to the report specifying attributes and metrics 11 | 6. Create a dataframe from the filtered report 12 | 7. Print first few rows of newly created dataframe 13 | """ 14 | 15 | from mstrio.connection import get_connection 16 | from mstrio.project_objects import Report, Prompt, list_reports 17 | 18 | # connect to environment without providing user credentials 19 | # variable `workstationData` is stored within Workstation 20 | connection = get_connection(workstationData, project_name='MicroStrategy Tutorial') 21 | 22 | # List available Reports (limited to 10) 23 | sample_reports = list_reports(connection, limit=10) 24 | for report in sample_reports: 25 | print(report) 26 | 27 | # Check available attributes and metrics of a Report 28 | sample_report_id = '759C0B5340E0AD6FAEDFF19D4DBC3488' 29 | sample_report = Report(connection, id=sample_report_id) 30 | attributes = sample_report.attributes 31 | metrics = sample_report.metrics 32 | print(f"Attributes: {attributes}\nMetrics: {metrics}") 33 | 34 | # Choose attributes and metrics 35 | category_attribute = attributes[0].get('id') 36 | subcategory_attribute = attributes[1].get('id') 37 | 38 | cost_metric = metrics[0].get('id') 39 | profit_metric = metrics[1].get('id') 40 | 41 | # Filter which attributes and metrics to use in a dataframe 42 | sample_report.apply_filters( 43 | attributes=[category_attribute, subcategory_attribute], 44 | metrics=[cost_metric, profit_metric], 45 | ) 46 | 47 | # Create a dataframe from a Report 48 | dataframe = sample_report.to_dataframe() 49 | print(dataframe.head()) 50 | 51 | # Set and view VLDB properties for the report 52 | vldb_properties = sample_report.list_vldb_settings() 53 | sample_report.alter_vldb_settings(property_set_name='VLDB Select',name='Base Table Join for Template',value=2) 54 | print(vldb_properties) 55 | 56 | # View Owner/ACL properties for the report 57 | acls = sample_report.list_acl() 58 | owner_properties = sample_report.owner.list_properties().get('name') 59 | print(f"ACLs: {acls}\nOwner_property 'name': '{owner_properties}'") 60 | 61 | 62 | # Page by operations for report 63 | page_by_report = Report(connection, '918EFA5F46E541B9F2799BA36E184811') 64 | 65 | # List available elements 66 | pg_elements = page_by_report.page_by_elements 67 | print(pg_elements) 68 | 69 | 70 | ATTR_QUARTER = '8D679D4A11D3E4981000E787EC6DE8A4' 71 | ATTR_CC = '8D679D3511D3E4981000E787EC6DE8A4' 72 | element_quarter = pg_elements[ATTR_QUARTER][0] 73 | 74 | page_by_dataframe = page_by_report.to_dataframe(page_element_id={ATTR_QUARTER: element_quarter, ATTR_CC: 'h1;'}) 75 | print(page_by_dataframe.head()) 76 | 77 | 78 | # Prompt operations for report 79 | prompt_report = Report(connection=connection, id='87FDA2E94FCD7C00B0A43AA2C52767B4') 80 | prompt = Prompt(type='VALUE', key='CA6906D3499B6AEE259BFE9C308076D7@0@10', answers=100, use_default=False) 81 | 82 | prompt_report_df = prompt_report.to_dataframe(prompt_answers=[prompt]) 83 | print("Prompt report dataframe:\n", prompt_report_df.head()) 84 | -------------------------------------------------------------------------------- /workflows/importing_usergroups_user_group_structure_from_csv.py: -------------------------------------------------------------------------------- 1 | """ 2 | Import user or user group from csv. 3 | Automate administrative tasks such as resetting password, assigning security 4 | filters. 5 | 6 | 1. Connect to the environment using data from workstation 7 | 2. Create users from CSV file 8 | 3. Get first user group from the list of user groups 9 | 4. Set view access to the user group for all users created from CSV file 10 | 5. Reset password for all newly created users and make sure they have to change 11 | it at the next login 12 | 6. Import user group structure from CSV file 13 | 7. Apply security filter with name 'security_filter' to the user group with name 14 | 'AI Users' 15 | """ 16 | 17 | import csv 18 | from mstrio.connection import get_connection 19 | from mstrio.helpers import AggregatedRights 20 | from mstrio.modeling.security_filter.security_filter import list_security_filters 21 | from mstrio.users_and_groups.user import User, create_users_from_csv 22 | from mstrio.users_and_groups.user_group import UserGroup, list_user_groups 23 | 24 | # connect to environment without providing user credentials 25 | # variable `workstationData` is stored within Workstation 26 | conn = get_connection(workstationData, project_name='MicroStrategy Tutorial') 27 | 28 | # set correct user group access in tree view for user groups 29 | # assumptions about CSV - csv is created from User.to_csv_from_list with at least 30 | # two columns: username and full_name 31 | user_list = create_users_from_csv(conn, 'users.csv') 32 | user_group = list_user_groups(conn)[0] 33 | user_group.acl_add( 34 | right=AggregatedRights.VIEW, 35 | trustees=user_list, 36 | denied=False, 37 | inheritable=True, 38 | propagate_to_children=True, 39 | ) 40 | 41 | # reset the password for those users and check-in the option 42 | # "User must change password at the next login" 43 | for user in user_list: 44 | user.alter(password='new_password', require_new_password=True) 45 | 46 | # import user group structure from CSV file 47 | # assumptions - you have a prepared list CSV of two columns: 48 | # user_name , user_group_name with headers added 49 | with open('tree_structure.csv', encoding='utf-8') as structure: 50 | user_rows = csv.DictReader(structure) 51 | for row in user_rows: 52 | user_name = row['user_name'] 53 | user_group_name = row['user_group_name'] 54 | user = User(conn, name=user_name) 55 | user_group = UserGroup(conn, name=user_group_name) 56 | user_group.add_users(user) 57 | 58 | # assigning correct security filter (the same name) for the user group 59 | user_group = UserGroup(conn, name='AI Users') 60 | security_filter = list_security_filters(conn, name='security_filter')[0] 61 | user_group.apply_security_filter(security_filter) 62 | -------------------------------------------------------------------------------- /workflows/list_active_user_privileges.py: -------------------------------------------------------------------------------- 1 | """List user privileges for all active users. 2 | 3 | 1. Connect to the environment using data from workstation 4 | 2. Get all active users 5 | 3. For each of retrieved users prepare dictionary with user's id, name, 6 | username and list of privileges and save it to table 7 | 4. For each of retrieved users print user's name, username and each of its 8 | privileges' name 9 | """ 10 | 11 | from typing import List 12 | 13 | from mstrio.connection import Connection, get_connection 14 | from mstrio.users_and_groups import list_users 15 | 16 | 17 | def list_active_user_privileges(connection: "Connection") -> List[dict]: 18 | """List user privileges for all active users. 19 | 20 | Args: 21 | connection: Strategy One connection object returned by 22 | `connection.Connection()` 23 | 24 | Returns: 25 | list of dicts where each of them is in given form: 26 | { 27 | 'id' - id of user 28 | 'name' - name of user 29 | 'username' - username of user 30 | 'privileges' - list of privileges of user 31 | } 32 | """ 33 | all_users = list_users(connection=connection) 34 | active_users = [u for u in all_users if u.enabled] 35 | privileges_list = [] 36 | for usr in active_users: 37 | p = { 38 | 'id': usr.id, 39 | 'name': usr.name, 40 | 'username': usr.username, 41 | 'privileges': usr.privileges, 42 | } 43 | print(f"{p['name']} ({p['username']}) ", flush=True) 44 | for prvlg in p['privileges']: 45 | print("\t" + prvlg['privilege']['name'], flush=True) 46 | privileges_list.append(p) 47 | return privileges_list 48 | 49 | 50 | # connect to environment without providing user credentials 51 | # variable `workstationData` is stored within Workstation 52 | conn = get_connection(workstationData, 'MicroStrategy Tutorial') 53 | 54 | # list privileges for all enabled users 55 | list_active_user_privileges(conn) 56 | -------------------------------------------------------------------------------- /workflows/list_empty_user_groups.py: -------------------------------------------------------------------------------- 1 | """List names and IDs of empty user groups. 2 | 3 | 1. Connect to the environment using data from workstation 4 | 2. Get all user groups 5 | 3. Filter out user groups that have no members 6 | 4. Print names and ids of filtered user groups 7 | """ 8 | 9 | from typing import List 10 | 11 | from mstrio.connection import Connection, get_connection 12 | from mstrio.users_and_groups import list_user_groups, UserGroup 13 | 14 | 15 | def list_empty_user_groups(connection: "Connection") -> List["UserGroup"]: 16 | """List empty user groups. 17 | 18 | Args: 19 | connection: Strategy One connection object returned by 20 | `connection.Connection()` 21 | """ 22 | all_user_groups = list_user_groups(connection=connection) 23 | return [ 24 | user_group_ for user_group_ in all_user_groups if not user_group_.list_members() 25 | ] 26 | 27 | 28 | # connect to environment without providing user credentials 29 | # variable `workstationData` is stored within Workstation 30 | conn = get_connection(workstationData, 'MicroStrategy Tutorial') 31 | 32 | # get empty user groups 33 | empty_user_groups = list_empty_user_groups(conn) 34 | 35 | # print empty user groups 36 | print('Empty user groups:', flush=True) 37 | for ug in empty_user_groups: 38 | print(f"{ug.name} ({ug.id}) ", flush=True) 39 | -------------------------------------------------------------------------------- /workflows/list_security_roles_per_user.py: -------------------------------------------------------------------------------- 1 | """List security roles for every user in a user group. It is possible to provide 2 | either name or id of user group. Without any changes this script will be 3 | executed for user group 'System Administrators'. 4 | 5 | 1. Connect to the environment using data from workstation 6 | 2. Get user group object based on provided name or id (in this case name of user 7 | group is 'System Administrators') 8 | 3. Get list of all members of user group 9 | 4. For each member check whether it is user or user group (if additional argument 10 | is set to True, then skip user groups) 11 | 5. Prepare dictionary with type, id, name, username and list of security roles 12 | 6. For each project print its name and name of every security role which is 13 | inside the given project for the given member of "main" user group 14 | """ 15 | 16 | from typing import List 17 | 18 | from mstrio.connection import Connection, get_connection 19 | from mstrio.users_and_groups import UserGroup 20 | 21 | 22 | def list_security_roles_per_user( 23 | connection: "Connection", 24 | user_group_name: str = None, 25 | user_group_id: str = None, 26 | include_user_groups: bool = False, 27 | ) -> List[dict]: 28 | """List security roles for every user in a user group. 29 | It is possible to provide either name or id of user group. 30 | 31 | Args: 32 | connection: Strategy One connection object returned by 33 | `connection.Connection()` 34 | user_group_name (str): name of the user group 35 | user_group_id (str): id of the user group 36 | include_user_groups (bool): if True also user groups, which are inside 37 | provided user group, will be included in the result 38 | 39 | Returns: 40 | list of dicts where each of them is in given form: 41 | { 42 | 'type' - type of object (it can be `user` or `user group`) 43 | 'id' - id of object 44 | 'name' - name of object 45 | 'username' - username of user (for user group it is None) 46 | 'security_roles' - list of security roles which object has 47 | } 48 | """ 49 | 50 | user_group_ = UserGroup( 51 | connection=connection, name=user_group_name, id=user_group_id 52 | ) 53 | all_security_roles = [] 54 | for member in user_group_.list_members(): 55 | if isinstance(member, UserGroup): 56 | if not include_user_groups: 57 | continue 58 | member_type = 'user_group' 59 | else: 60 | member_type = 'user' 61 | m = { 62 | 'type': member_type, 63 | 'id': member.id, 64 | 'name': member.name, 65 | 'username': member.to_dict().get('username', None), 66 | 'security_roles': member.security_roles, 67 | } 68 | print('Security roles:', flush=True) 69 | for project_ in m['security_roles']: 70 | print("\tProject: " + project_['name'], flush=True) 71 | for sec_role in project_['securityRoles']: 72 | print("\t\t" + sec_role['name'], flush=True) 73 | all_security_roles.append(m) 74 | return all_security_roles 75 | 76 | 77 | # connect to environment without providing user credentials 78 | # variable `workstationData` is stored within Workstation 79 | conn = get_connection(workstationData, 'MicroStrategy Tutorial') 80 | 81 | # list security roles per all users in user group 'System Administrators' 82 | # change name of user group if needed 83 | list_security_roles_per_user(conn, user_group_name='System Administrators') 84 | -------------------------------------------------------------------------------- /workflows/manage_active_and_inactive_users.py: -------------------------------------------------------------------------------- 1 | """ 2 | Manage active and inactive users. 3 | Perform administrative tasks such as setting default language, listing 4 | subscriptions. 5 | 6 | 1. Connect to environment using data from workstation 7 | 2. Get language object based on provided id 8 | 3. For each active user set up language which was retrieved in step 2 9 | as the default one 10 | 4. Get list of all inactive users 11 | 5. Print number of inactive users 12 | 6. For each inactive user get list of its subscriptions and print them with 13 | user name 14 | """ 15 | 16 | from mstrio.distribution_services.subscription.subscription_manager import ( 17 | list_subscriptions, 18 | ) 19 | from mstrio.server.language import Language 20 | from mstrio.users_and_groups.user import list_users 21 | from mstrio.connection import get_connection 22 | 23 | # connect to environment without providing user credentials 24 | # variable `workstationData` is stored within Workstation 25 | conn = get_connection(workstationData, project_name='MicroStrategy Tutorial') 26 | 27 | ALTERED_LANGUAGE = Language(conn, id='000004124F95EF3956E52781700C1E7A') 28 | 29 | # list active users 30 | active_users = list_users(conn, enabled=True) 31 | 32 | # set up default language for those users 33 | for user in active_users: 34 | user.alter(language=ALTERED_LANGUAGE) 35 | 36 | # list all inactive users and count them 37 | inactive_users = list_users(conn, enabled=False) 38 | inactive_users_count = len(inactive_users) 39 | print('Number of inactive users:', inactive_users_count) 40 | 41 | # list all related subscriptions for inactive users 42 | for user in inactive_users: 43 | subscriptions = list_subscriptions(conn, project_id=conn.project_id, owner=user) 44 | print('Subscriptions for user', user.name, ':', subscriptions) 45 | -------------------------------------------------------------------------------- /workflows/manage_project_languages.py: -------------------------------------------------------------------------------- 1 | """ 2 | Manage project languages. 3 | List languages, convert them to objects. Add or remove language from project. 4 | 5 | 1. Connect to the environment using data from the workstation 6 | 2. Get project object based on provided name ('MicroStrategy Tutorial') 7 | 3. Get list of all available datasource connections 8 | 4. Get a datasource connection with a given name ('XQuery(1)') to use it for 9 | the data language 10 | 5. Get the list of all available languages 11 | 6. Get 3 languages needed to be added to a project: Polish, Danish and Dutch 12 | 7. Convert Polish and Danish languages to DataLocalizationLanguage objects 13 | 8. Add those 2 languages to the settings of the project 14 | 9. Convert Dutch language to SimpleLanguage object 15 | 10. Add it to the settings of the project 16 | 11. Remove those 3 languages from the settings of the project 17 | """ 18 | 19 | from mstrio.connection import get_connection 20 | from mstrio.datasources import list_datasource_connections 21 | from mstrio.server import Project, list_languages 22 | from mstrio.server.project_languages import DataLocalizationLanguage, SimpleLanguage 23 | 24 | PROJECT_NAME = 'MicroStrategy Tutorial' # Project to connect to 25 | 26 | # Create connection based on workstation data 27 | conn = get_connection(workstationData, project_name=PROJECT_NAME) 28 | 29 | # Get project with a given name 30 | project = Project(connection=conn, name=PROJECT_NAME) 31 | 32 | # Get list of all available datasource connections 33 | datasource_connections = list_datasource_connections(conn) 34 | # Get a datasource connection with a given name to use it for the data language 35 | xquery_datasource_connection = list_datasource_connections(conn, name='XQuery(1)')[0] 36 | 37 | # Get a list of all available languages 38 | languages = list_languages(connection=conn) 39 | # Get languages needed to be added to a project 40 | polish_language = list_languages(connection=conn, name='Polish')[0] 41 | danish_language = list_languages(connection=conn, name='Danish (Denmark)')[0] 42 | dutch_language = list_languages(connection=conn, name='Dutch (Netherlands)')[0] 43 | 44 | # Convert Polish and Danish languages to DataLocalizationLanguage objects 45 | polish_data_language = DataLocalizationLanguage( 46 | id=polish_language.id, 47 | name=polish_language.name, 48 | column='_pl', 49 | table='', 50 | ) 51 | 52 | danish_data_language = DataLocalizationLanguage( 53 | id=danish_language.id, 54 | name=danish_language.name, 55 | connection_id=xquery_datasource_connection.id, 56 | ) 57 | 58 | # Add them languages to a project 59 | project.data_language_settings.add_language( 60 | languages=[polish_data_language, danish_data_language] 61 | ) 62 | 63 | # Convert Dutch language to SimpleLanguage object 64 | dutch_metadata_language = SimpleLanguage( 65 | id=dutch_language.id, 66 | name=dutch_language.name, 67 | ) 68 | 69 | # Add it to a project 70 | project.metadata_language_settings.add_language(languages=[dutch_metadata_language]) 71 | 72 | # Remove languages from the project 73 | project.data_language_settings.remove_language( 74 | languages=[polish_data_language, danish_data_language] 75 | ) 76 | project.metadata_language_settings.remove_language(languages=[dutch_metadata_language]) 77 | -------------------------------------------------------------------------------- /workflows/manage_project_settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Manage project settings. 3 | Change project settings, copy settings from one project to another. 4 | Export project settings to CSV. 5 | 6 | 1. Connect to the environment using data from the workstation 7 | 2. Get project object based on provided id 8 | 3. Get another project object based on provided id 9 | 4. Change single setting for a project 10 | 5. Apply all settings from one project to another 11 | 6. Export settings to a CSV file and import them to another project 12 | 7. Export settings to a CSV file with their descriptions 13 | """ 14 | 15 | from mstrio.connection import get_connection 16 | from mstrio.server import Project 17 | 18 | conn = get_connection(workstationData, 'MicroStrategy Tutorial') 19 | 20 | project = Project(connection=conn, id='B3FEE61A11E696C8BD0F0080EFC58F44') 21 | test_project = Project(connection=conn, id='B7CA92F04B9FAE8D941C3E9B7E0CD754') 22 | 23 | # Change single setting for a project 24 | # You can get a list of all available settings by calling: 25 | # project.settings.list_properties() 26 | project.settings.maxMstrFileSize = 50 27 | project.settings.update() 28 | 29 | # Apply all settings from one project to another 30 | project.settings.alter(**test_project.settings.list_properties(show_names=False)) 31 | project.settings.update() 32 | 33 | # Or export settings to a CSV file and import them to another project 34 | settings_file_name = 'settings.csv' 35 | test_project.settings.to_csv(settings_file_name) 36 | project.settings.import_from(settings_file_name) 37 | project.settings.update() 38 | 39 | # Export settings to a CSV file with their descriptions 40 | project.settings.to_csv('settings_file_name', show_description=True) 41 | -------------------------------------------------------------------------------- /workflows/migrate_active_users.py: -------------------------------------------------------------------------------- 1 | # Create an administration package with list of active users 2 | # Leave action "use existing" 3 | 4 | from mstrio.connection import get_connection 5 | 6 | 7 | from mstrio.object_management.migration.migration import Migration 8 | from mstrio.object_management.migration.package import ( 9 | PackageConfig, 10 | PackageContentInfo, 11 | PackageSettings, 12 | ) 13 | from mstrio.users_and_groups.user import list_users 14 | 15 | conn = get_connection(workstationData, project_name='Project Analytics') 16 | 17 | all_users = list_users(connection=conn) 18 | active_users = [u for u in all_users if u.enabled] 19 | 20 | # If no settings are specified, defaults will be used: 21 | # - action: "use existing" 22 | # - acl on replacing objects: "use existing" 23 | # - acl on new objects: "keep acl as source object" 24 | package_settings = PackageSettings() 25 | 26 | # For each of the migrated objects, create a PackageContentInfo entry 27 | # An entry can have its own action defined. It will override the default action 28 | # If not specified, the default action will be used 29 | # In this case, we will not set include_dependents to True. This will cause 30 | # I-Server to include all dependent objects, without options to include or 31 | # exclude them. 32 | content_info = [PackageContentInfo(id=obj.id, type=obj.type) for obj in active_users] 33 | 34 | package_config = PackageConfig(package_settings, content_info) 35 | 36 | my_obj_mig_alt = Migration.create_object_migration( 37 | connection=conn, 38 | toc_view=package_config, 39 | name="test_object_mig", 40 | project_id=conn.project_id, 41 | ) 42 | -------------------------------------------------------------------------------- /workflows/migrate_last_modified_objects.py: -------------------------------------------------------------------------------- 1 | # Create an object package using results of search for last modified objects 2 | # Add dataset components for Dashboard object 3 | # Set up action "Replace" to all of the objects 4 | 5 | from datetime import UTC, datetime, timedelta 6 | 7 | from mstrio.connection import get_connection 8 | 9 | 10 | from mstrio.object_management.migration.migration import Migration 11 | from mstrio.object_management.migration.package import ( 12 | Action, 13 | PackageConfig, 14 | PackageContentInfo, 15 | PackageSettings, 16 | ) 17 | from mstrio.object_management.search_operations import full_search 18 | from mstrio.types import ObjectTypes 19 | 20 | conn = get_connection(workstationData, project_name='Project Analytics') 21 | 22 | DATE_CUTOFF = datetime.now(tz=UTC) - timedelta(days=30) 23 | # format to str; example output: 2023-04-04T06:33:32Z 24 | DATE_CUTOFF_STR = DATE_CUTOFF.strftime('%Y-%m-%dT%H:%M:%SZ') 25 | 26 | search_results = full_search( 27 | conn, conn.project_id, begin_modification_time=DATE_CUTOFF_STR 28 | ) 29 | datasets = [] 30 | for obj in search_results: 31 | if obj.type == ObjectTypes.DOCUMENT_DEFINITION: 32 | dependent_datasets = obj.list_dependents(object_types=[ObjectTypes.DBROLE]) 33 | datasets += dependent_datasets 34 | 35 | migrated_objects = search_results + datasets 36 | 37 | # You can set the default action for all migrated objects 38 | # using the PackageSettings object 39 | package_settings = PackageSettings(Action.REPLACE) 40 | 41 | # For each of the migrated objects, create a PackageContentInfo entry 42 | # An entry can have its own action defined. It will override the default action 43 | # If not specified, the default action will be used 44 | # In this case, we will not set include_dependents to True. This will cause 45 | # I-Server to include all dependent objects, without options to include or 46 | # exclude them. 47 | content_info = [ 48 | PackageContentInfo(id=obj.id, type=obj.type, action=Action.REPLACE) 49 | for obj in migrated_objects 50 | ] 51 | 52 | package_config = PackageConfig(package_settings, content_info) 53 | 54 | my_obj_mig_alt = Migration.create_object_migration( 55 | connection=conn, 56 | toc_view=package_config, 57 | name="test_object_mig", 58 | project_id=conn.project_id, 59 | ) 60 | -------------------------------------------------------------------------------- /workflows/migrate_user_contacts_and_addresses.py: -------------------------------------------------------------------------------- 1 | """ 2 | Facilitate the migration of user data from one user to another such as 3 | addresses or contacts. 4 | 5 | 1. Connect to the environment using data from the workstation 6 | 2. Get first user object based on provided name ('User Korean') 7 | 3. Get second user object based on provided name ('Tilda Austin') 8 | 4. Add addresses of the first user to the second user and remove them from 9 | the first user. Print each address before the operation 10 | 5. Get list of contacts for the first user 11 | 6. For each contact in the list alter the linked user to the second user 12 | """ 13 | 14 | from mstrio.connection import get_connection 15 | from mstrio.users_and_groups.contact import list_contacts 16 | from mstrio.users_and_groups.user import User 17 | 18 | # connect to environment without providing user credentials 19 | # variable `workstationData` is stored within Workstation 20 | conn = get_connection(workstationData, project_name='MicroStrategy Tutorial') 21 | 22 | # migrate addresses from one User to another 23 | user_1 = User(conn, name='User Korean') 24 | user_2 = User(conn, name='Tilda Austin') 25 | for address in user_1.addresses: 26 | print(address) 27 | user_2.add_address(contact_address=address) 28 | user_1.remove_address(id=address['id']) 29 | 30 | # migrate User contacts to another User 31 | list_contacts = list_contacts(conn, linked_user=user_1) 32 | for contact in list_contacts: 33 | contact.alter(linked_user=user_2) 34 | -------------------------------------------------------------------------------- /workflows/script_usage_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "add_email_to_new_users.py": "Standard", 3 | "application_end_to_end_showcase.py": "Standard", 4 | "assign_acls_for_objects.py": "Standard", 5 | "control_users_and_security.py": "Standard", 6 | "create_content_group_and_application.py": "Standard", 7 | "delete_addresses_from_departed_users.py": "Standard", 8 | "delete_inactive_cube_caches.py": "Standard", 9 | "delete_subscriptions_of_departed_users.py": "Standard", 10 | "disable_all_active_bots.py": "Standard", 11 | "export_selected_information_to_csv.py": "Standard", 12 | "get_all_columns_in_table.py": "Standard", 13 | "import_cube_data_into_dataframe.py": "Standard", 14 | "import_report_data_into_dataframe.py": "Standard", 15 | "importing_usergroups_user_group_structure_from_csv.py": "Standard", 16 | "list_active_user_privileges.py": "Standard", 17 | "list_empty_user_groups.py": "Standard", 18 | "list_security_roles_per_user.py": "Standard", 19 | "manage_active_and_inactive_users.py": "Standard", 20 | "manage_project_languages.py": "Standard", 21 | "manage_project_settings.py": "Standard", 22 | "manage_subscriptions.py": "Standard", 23 | "manage_translation_for_metric.py": "Standard", 24 | "migrate_active_users.py": "Standard", 25 | "migrate_last_modified_objects.py": "Standard", 26 | "migrate_user_contacts_and_addresses.py": "Standard", 27 | "send_dashboard_email_subscription_after_cube_refresh.py": "Standard", 28 | "user_group_maintenance.py": "Standard", 29 | "export_and_import_translations_to_database.py": "Standard", 30 | "duplicate_user_subscriptions.py": "Standard", 31 | "get_subscriptions_by_emails.py": "Standard", 32 | "manage_pre_post_execution.py": "Standard", 33 | "create_mobile_subscription.py": "Standard", 34 | "disable_inactive_users.py": "Standard", 35 | "create_prompted_subscription.py": "Standard", 36 | "create_bursting_subscription.py": "Standard", 37 | "compare_advanced_properties.py": "Standard" 38 | } 39 | -------------------------------------------------------------------------------- /workflows/send_dashboard_email_subscription_after_cube_refresh.py: -------------------------------------------------------------------------------- 1 | """ 2 | Manage email subscriptions after cube refresh. 3 | Select a cube, refresh, find its subscriptions and run them. 4 | 5 | 1. Connect to the environment using data from the workstation 6 | 2. Get the intelligent cube object based on provided id 7 | 3. Publish retrieved cube and wait for its refresh to complete successfully 8 | 4. Get the list of dashboards dependent on the cube 9 | 5. Get the list of all subscriptions 10 | 6. For each found dashboard, find all subscriptions using it and store them 11 | in a list 12 | 7. Trigger each stored subscription 13 | """ 14 | 15 | import time 16 | from mstrio.connection import get_connection 17 | from mstrio.project_objects.datasets.cube import CubeStates 18 | from mstrio.project_objects.datasets import OlapCube 19 | from mstrio.distribution_services.subscription.subscription_manager import ( 20 | list_subscriptions, 21 | ) 22 | 23 | 24 | conn = get_connection(workstationData, 'MicroStrategy Tutorial') 25 | 26 | 27 | # Select an intelligent cube and refresh it 28 | example_cube = OlapCube( 29 | connection=conn, id="8CCD8D9D4051A4C533C719A6590DEED8" 30 | ) # Intelligent Cube - Drilling 31 | example_cube.publish() 32 | 33 | 34 | # Wait for cube refresh to complete successfully. 35 | example_cube.refresh_status() 36 | while "Processing" in CubeStates.show_status(example_cube.status): 37 | time.sleep(1) 38 | example_cube.refresh_status() 39 | 40 | 41 | # Search for subscriptions dependent on that cube 42 | # (find dashboards dependent on cube, and then subscriptions 43 | # dependent on those dashboards found) 44 | dependent_dashboards = example_cube.list_dependents() 45 | all_subscriptions = list_subscriptions(conn) 46 | subscriptions_to_send = [] 47 | for d in dependent_dashboards: 48 | # For each found Dashboard, find all Subscriptions using it. 49 | # This step requires filtering a list of Subscriptions with Python 50 | # list operations, as Document/Dashboard query responses do not backlink 51 | # to Subscriptions. 52 | dashboard_id = d['id'] 53 | 54 | subscriptions_to_send += [ 55 | sub 56 | for sub in all_subscriptions 57 | if any(content.id == dashboard_id for content in sub.contents) 58 | ] 59 | 60 | # Trigger (run) the subscriptions 61 | for s in subscriptions_to_send: 62 | s.execute() 63 | -------------------------------------------------------------------------------- /workflows/user_group_maintenance.py: -------------------------------------------------------------------------------- 1 | """ 2 | Facilitate user management operations such as changing owner, 3 | Distinguished Name (DN) manipulation. 4 | 5 | 1. Connect to the environment using data from the workstation 6 | 2. Get first user group object based on provided name ('Second Factor Exempt') 7 | 3. Get second user group object based on provided name ('TransactionServer') 8 | 4. Move users from the first user group to the second user group 9 | 5. Change owner for all users to administrator 10 | 6. Get user group object based on provided name ('Second Factor Exempt') 11 | 7. Change/add LDAP Distinguished Name (DN) on target user group 12 | """ 13 | 14 | from mstrio.connection import get_connection 15 | from mstrio.users_and_groups.user import User, list_users 16 | from mstrio.users_and_groups.user_group import UserGroup 17 | 18 | # connect to environment without providing user credentials 19 | # variable `workstationData` is stored within Workstation 20 | conn = get_connection(workstationData, project_name='MicroStrategy Tutorial') 21 | 22 | # Move users from one user group to another 23 | user_group_1 = UserGroup(conn, name='Second Factor Exempt') 24 | user_group_2 = UserGroup(conn, name='TransactionServer') 25 | user_group_2.add_users([user['id'] for user in user_group_1.members]) 26 | user_group_1.remove_all_users() 27 | 28 | # change owner for all the users to Administrator 29 | for user in list_users(conn): 30 | user.alter(owner=User(conn, name='Administrator')) 31 | 32 | # change/add LDAP Distinguished Name (DN) on target user group 33 | user_group = UserGroup(conn, name='Second Factor Exempt') 34 | user_group.alter(ldapdn='CN=Users,DC=domain,DC=com') 35 | --------------------------------------------------------------------------------