Skip to content

utils

EnvironmentPrepData

Bases: dict

Helper dictionary for storin temp data during environment prep.

Parameters:

Name Type Description Default
data dict

Data must contain required keys.

required
Source code in client/ayon_applications/utils.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
class EnvironmentPrepData(dict):
    """Helper dictionary for storin temp data during environment prep.

    Args:
        data (dict): Data must contain required keys.
    """
    required_keys = (
        "project_entity", "folder_entity", "task_entity", "app", "anatomy"
    )

    def __init__(self, data):
        for key in self.required_keys:
            if key not in data:
                raise MissingRequiredKey(key)

        if not data.get("log"):
            data["log"] = Logger.get_logger("EnvironmentPrepData")

        if data.get("env") is None:
            data["env"] = os.environ.copy()

        project_name = data["project_entity"]["name"]
        if "project_settings" not in data:
            data["project_settings"] = get_project_settings(project_name)

        super(EnvironmentPrepData, self).__init__(data)

apply_project_environments_value(project_name, env, project_settings=None, env_group=None)

Apply project specific environments on passed environments.

The environments are applied on passed env argument value so it is not required to apply changes back.

Parameters:

Name Type Description Default
project_name str

Name of project for which environments should be received.

required
env dict

Environment values on which project specific environments will be applied.

required
project_settings dict

Project settings for passed project name. Optional if project settings are already prepared.

None

Returns:

Name Type Description
dict

Passed env values with applied project environments.

Raises:

Type Description
KeyError

If project settings do not contain keys for project specific environments.

Source code in client/ayon_applications/utils.py
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
def apply_project_environments_value(
    project_name, env, project_settings=None, env_group=None
):
    """Apply project specific environments on passed environments.

    The environments are applied on passed `env` argument value so it is not
    required to apply changes back.

    Args:
        project_name (str): Name of project for which environments should be
            received.
        env (dict): Environment values on which project specific environments
            will be applied.
        project_settings (dict): Project settings for passed project name.
            Optional if project settings are already prepared.

    Returns:
        dict: Passed env values with applied project environments.

    Raises:
        KeyError: If project settings do not contain keys for project specific
            environments.

    """
    if project_settings is None:
        project_settings = get_project_settings(project_name)

    env_value = project_settings["core"]["project_environments"]
    if env_value:
        env_value = json.loads(env_value)
        parsed_value = parse_environments(env_value, env_group)
        env.update(compute_env_variables_structure(
            merge_env_variables(parsed_value, env)
        ))
    return env

get_app_environments_for_context(project_name, folder_path, task_name, app_name, env_group=None, launch_type=None, env=None, addons_manager=None)

Prepare environment variables by context. Args: project_name (str): Name of project. folder_path (str): Folder path. task_name (str): Name of task. app_name (str): Name of application that is launched and can be found by ApplicationManager. env_group (Optional[str]): Name of environment group. If not passed default group is used. launch_type (Optional[str]): Type for which prelaunch hooks are executed. env (Optional[dict[str, str]]): Initial environment variables. os.environ is used when not passed. addons_manager (Optional[AddonsManager]): Initialized modules manager.

Returns:

Name Type Description
dict

Environments for passed context and application.

Source code in client/ayon_applications/utils.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
def get_app_environments_for_context(
    project_name,
    folder_path,
    task_name,
    app_name,
    env_group=None,
    launch_type=None,
    env=None,
    addons_manager=None
):
    """Prepare environment variables by context.
    Args:
        project_name (str): Name of project.
        folder_path (str): Folder path.
        task_name (str): Name of task.
        app_name (str): Name of application that is launched and can be found
            by ApplicationManager.
        env_group (Optional[str]): Name of environment group. If not passed
            default group is used.
        launch_type (Optional[str]): Type for which prelaunch hooks are
            executed.
        env (Optional[dict[str, str]]): Initial environment variables.
            `os.environ` is used when not passed.
        addons_manager (Optional[AddonsManager]): Initialized modules
            manager.

    Returns:
        dict: Environments for passed context and application.
    """

    # Prepare app object which can be obtained only from ApplicationManager
    app_manager = ApplicationManager()
    context = app_manager.create_launch_context(
        app_name,
        project_name=project_name,
        folder_path=folder_path,
        task_name=task_name,
        env_group=env_group,
        launch_type=launch_type,
        env=env,
        addons_manager=addons_manager,
        modules_manager=addons_manager,
    )
    context.run_prelaunch_hooks()
    return context.env

get_app_icon_path(icon_filename)

Get icon path.

Parameters:

Name Type Description Default
icon_filename str

Icon filename.

required

Returns:

Type Description

Union[str, None]: Icon path or None if not found.

Source code in client/ayon_applications/utils.py
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
def get_app_icon_path(icon_filename):
    """Get icon path.

    Args:
        icon_filename (str): Icon filename.

    Returns:
        Union[str, None]: Icon path or None if not found.

    """
    if not icon_filename:
        return None
    icon_name = os.path.basename(icon_filename)
    path = os.path.join(APPLICATIONS_ADDON_ROOT, "icons", icon_name)
    if os.path.exists(path):
        return path
    return None

get_applications_for_context(project_name, folder_entity, task_entity, project_settings=None, project_entity=None)

Get applications for context based on project settings.

Parameters:

Name Type Description Default
project_name str

Name of project.

required
folder_entity dict

Folder entity.

required
task_entity dict

Task entity.

required
project_settings Optional[dict]

Project settings.

None
project_entity Optional[dict]

Project entity.

None

Returns:

Type Description

List[str]: List of applications that can be used in given context.

Source code in client/ayon_applications/utils.py
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
def get_applications_for_context(
    project_name,
    folder_entity,
    task_entity,
    project_settings=None,
    project_entity=None,
):
    """Get applications for context based on project settings.

    Args:
        project_name (str): Name of project.
        folder_entity (dict): Folder entity.
        task_entity (dict): Task entity.
        project_settings (Optional[dict]): Project settings.
        project_entity (Optional[dict]): Project entity.

    Returns:
        List[str]: List of applications that can be used in given context.

    """
    if project_settings is None:
        project_settings = get_project_settings(project_name)
    apps_settings = project_settings["applications"]

    # Use attributes to get available applications
    # - this is older source of the information, will be deprecated in future
    project_applications = apps_settings["project_applications"]
    if not project_applications["enabled"]:
        if project_entity is None:
            project_entity = ayon_api.get_project(project_name)
        apps = project_entity["attrib"].get("applications")
        return apps or []

    task_type = None
    if task_entity:
        task_type = task_entity["taskType"]

    profile = filter_profiles(
        project_applications["profiles"],
        {"task_types": task_type}
    )
    if profile:
        if profile["allow_type"] == "applications":
            return profile["applications"]
        return _get_app_full_names_from_settings(apps_settings)
    return []

get_tools_for_context(project_name, folder_entity, task_entity, project_settings=None)

Get tools for context based on project settings.

Parameters:

Name Type Description Default
project_name str

Name of project.

required
folder_entity dict

Folder entity.

required
task_entity dict

Task entity.

required
project_settings Optional[dict]

Project settings.

None

Returns:

Type Description

List[str]: List of applications that can be used in given context.

Source code in client/ayon_applications/utils.py
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
def get_tools_for_context(
    project_name,
    folder_entity,
    task_entity,
    project_settings=None,
):
    """Get tools for context based on project settings.

    Args:
        project_name (str): Name of project.
        folder_entity (dict): Folder entity.
        task_entity (dict): Task entity.
        project_settings (Optional[dict]): Project settings.

    Returns:
        List[str]: List of applications that can be used in given context.

    """
    if project_settings is None:
        project_settings = get_project_settings(project_name)
    apps_settings = project_settings["applications"]

    project_tools = apps_settings["project_tools"]
    # Use attributes to get available tools
    # - this is older source of the information, will be deprecated in future
    if not project_tools["enabled"]:
        tools = None
        if task_entity:
            tools = task_entity["attrib"].get("tools")

        if tools is None and folder_entity:
            tools = folder_entity["attrib"].get("tools")

        return tools or []

    folder_path = task_type = task_name = None
    if folder_entity:
        folder_path = folder_entity["path"]
    if task_entity:
        task_type = task_entity["taskType"]
        task_name = task_entity["name"]

    profile = filter_profiles(
        project_tools["profiles"],
        {
            "folder_paths": folder_path,
            "task_types": task_type,
            "task_names": task_name,
        },
        keys_order=["folder_paths", "task_names", "task_types"]
    )
    if profile:
        return profile["tools"]
    return []

parse_environments(env_data, env_group=None, platform_name=None)

Parse environment values from settings byt group and platform.

Data may contain up to 2 hierarchical levels of dictionaries. At the end of the last level must be string or list. List is joined using platform specific joiner (';' for windows and ':' for linux and mac).

Hierarchical levels can contain keys for subgroups and platform name. Platform specific values must be always last level of dictionary. Platform names are "windows" (MS Windows), "linux" (any linux distribution) and "darwin" (any MacOS distribution).

Subgroups are helpers added mainly for standard and on farm usage. Farm may require different environments for e.g. licence related values or plugins. Default subgroup is "standard".

Examples:

{
    # Unchanged value
    "ENV_KEY1": "value",
    # Empty values are kept (unset environment variable)
    "ENV_KEY2": "",

    # Join list values with ':' or ';'
    "ENV_KEY3": ["value1", "value2"],

    # Environment groups
    "ENV_KEY4": {
        "standard": "DEMO_SERVER_URL",
        "farm": "LICENCE_SERVER_URL"
    },

    # Platform specific (and only for windows and mac)
    "ENV_KEY5": {
        "windows": "windows value",
        "darwin": ["value 1", "value 2"]
    },

    # Environment groups and platform combination
    "ENV_KEY6": {
        "farm": "FARM_VALUE",
        "standard": {
            "windows": ["value1", "value2"],
            "linux": "value1",
            "darwin": ""
        }
    }
}
Source code in client/ayon_applications/utils.py
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def parse_environments(env_data, env_group=None, platform_name=None):
    """Parse environment values from settings byt group and platform.

    Data may contain up to 2 hierarchical levels of dictionaries. At the end
    of the last level must be string or list. List is joined using platform
    specific joiner (';' for windows and ':' for linux and mac).

    Hierarchical levels can contain keys for subgroups and platform name.
    Platform specific values must be always last level of dictionary. Platform
    names are "windows" (MS Windows), "linux" (any linux distribution) and
    "darwin" (any MacOS distribution).

    Subgroups are helpers added mainly for standard and on farm usage. Farm
    may require different environments for e.g. licence related values or
    plugins. Default subgroup is "standard".

    Examples:
    ```
    {
        # Unchanged value
        "ENV_KEY1": "value",
        # Empty values are kept (unset environment variable)
        "ENV_KEY2": "",

        # Join list values with ':' or ';'
        "ENV_KEY3": ["value1", "value2"],

        # Environment groups
        "ENV_KEY4": {
            "standard": "DEMO_SERVER_URL",
            "farm": "LICENCE_SERVER_URL"
        },

        # Platform specific (and only for windows and mac)
        "ENV_KEY5": {
            "windows": "windows value",
            "darwin": ["value 1", "value 2"]
        },

        # Environment groups and platform combination
        "ENV_KEY6": {
            "farm": "FARM_VALUE",
            "standard": {
                "windows": ["value1", "value2"],
                "linux": "value1",
                "darwin": ""
            }
        }
    }
    ```
    """
    output = {}
    if not env_data:
        return output

    if not env_group:
        env_group = DEFAULT_ENV_SUBGROUP

    if not platform_name:
        platform_name = platform.system().lower()

    for key, value in env_data.items():
        if isinstance(value, dict):
            # Look if any key is platform key
            #   - expect that represents environment group if does not contain
            #   platform keys
            if not PLATFORM_NAMES.intersection(set(value.keys())):
                # Skip the key if group is not available
                if env_group not in value:
                    continue
                value = value[env_group]

        # Check again if value is dictionary
        #   - this time there should be only platform keys
        if isinstance(value, dict):
            value = value.get(platform_name)

        # Check if value is list and join it's values
        # QUESTION Should empty values be skipped?
        if isinstance(value, (list, tuple)):
            value = os.pathsep.join(value)

        # Set key to output if value is string
        if isinstance(value, str):
            output[key] = value
    return output

prepare_app_environments(data, env_group=None, implementation_envs=True, addons_manager=None)

Modify launch environments based on launched app and context.

Parameters:

Name Type Description Default
data EnvironmentPrepData

Dictionary where result and intermediate result will be stored.

required
Source code in client/ayon_applications/utils.py
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
def prepare_app_environments(
    data, env_group=None, implementation_envs=True, addons_manager=None
):
    """Modify launch environments based on launched app and context.

    Args:
        data (EnvironmentPrepData): Dictionary where result and intermediate
            result will be stored.

    """
    app = data["app"]
    log = data["log"]
    source_env = data["env"].copy()

    if addons_manager is None:
        addons_manager = AddonsManager()

    _add_python_version_paths(app, source_env, log, addons_manager)

    # Use environments from local settings
    filtered_local_envs = {}
    # NOTE Overrides for environment variables are not implemented in AYON.
    # project_settings = data["project_settings"]
    # whitelist_envs = project_settings["general"].get("local_env_white_list")
    # if whitelist_envs:
    #     local_settings = get_local_settings()
    #     local_envs = local_settings.get("environments") or {}
    #     filtered_local_envs = {
    #         key: value
    #         for key, value in local_envs.items()
    #         if key in whitelist_envs
    #     }

    # Apply local environment variables for already existing values
    for key, value in filtered_local_envs.items():
        if key in source_env:
            source_env[key] = value

    # `app_and_tool_labels` has debug purpose
    app_and_tool_labels = [app.full_name]
    # Environments for application
    environments = [
        app.group.environment,
        app.environment
    ]

    tools = get_tools_for_context(
        data.get("project_name"),
        data.get("folder_entity"),
        data.get("task_entity"),
    )

    # Add tools environments
    groups_by_name = {}
    tool_by_group_name = collections.defaultdict(dict)
    for key in tools:
        tool = app.manager.tools.get(key)
        if not tool or not tool.is_valid_for_app(app):
            continue
        groups_by_name[tool.group.name] = tool.group
        tool_by_group_name[tool.group.name][tool.name] = tool

    for group_name in sorted(groups_by_name.keys()):
        group = groups_by_name[group_name]
        environments.append(group.environment)
        for tool_name in sorted(tool_by_group_name[group_name].keys()):
            tool = tool_by_group_name[group_name][tool_name]
            environments.append(tool.environment)
            app_and_tool_labels.append(tool.full_name)

    log.info(
        "Will add environments for apps and tools: {}".format(
            ", ".join(app_and_tool_labels)
        )
    )

    env_values = {}
    for _env_values in environments:
        if not _env_values:
            continue

        # Choose right platform
        tool_env = parse_environments(_env_values, env_group)

        # Apply local environment variables
        # - must happen between all values because they may be used during
        #   merge
        for key, value in filtered_local_envs.items():
            if key in tool_env:
                tool_env[key] = value

        # Merge dictionaries
        env_values = merge_env_variables(tool_env, env_values)

    merged_env = merge_env_variables(env_values, source_env)
    loaded_env = compute_env_variables_structure(merged_env)

    final_env = None
    # Add host specific environments
    if app.host_name and implementation_envs:
        host_addon = addons_manager.get_host_addon(app.host_name)
        add_implementation_envs = None
        if host_addon:
            add_implementation_envs = getattr(
                host_addon, "add_implementation_envs", None
            )
        if add_implementation_envs:
            # Function may only modify passed dict without returning value
            final_env = add_implementation_envs(loaded_env, app)

    if final_env is None:
        final_env = loaded_env

    keys_to_remove = set(source_env.keys()) - set(final_env.keys())

    # Update env
    data["env"].update(final_env)
    for key in keys_to_remove:
        data["env"].pop(key, None)

prepare_context_environments(data, env_group=None, addons_manager=None)

Modify launch environments with context data for launched host.

Parameters:

Name Type Description Default
data EnvironmentPrepData

Dictionary where result and intermediate result will be stored.

required
Source code in client/ayon_applications/utils.py
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
def prepare_context_environments(data, env_group=None, addons_manager=None):
    """Modify launch environments with context data for launched host.

    Args:
        data (EnvironmentPrepData): Dictionary where result and intermediate
            result will be stored.

    """
    # Context environments
    log = data["log"]

    project_entity = data["project_entity"]
    folder_entity = data["folder_entity"]
    task_entity = data["task_entity"]
    if not project_entity:
        log.info(
            "Skipping context environments preparation."
            " Launch context does not contain required data."
        )
        return

    # Load project specific environments
    project_name = project_entity["name"]
    project_settings = get_project_settings(project_name)
    data["project_settings"] = project_settings

    app = data["app"]
    context_env = {
        "AYON_PROJECT_NAME": project_entity["name"],
        "AYON_APP_NAME": app.full_name
    }
    if folder_entity:
        folder_path = folder_entity["path"]
        context_env["AYON_FOLDER_PATH"] = folder_path

        if task_entity:
            context_env["AYON_TASK_NAME"] = task_entity["name"]

    log.debug(
        "Context environments set:\n{}".format(
            json.dumps(context_env, indent=4)
        )
    )
    data["env"].update(context_env)

    # Apply project specific environments on current env value
    # - apply them once the context environments are set
    apply_project_environments_value(
        project_name, data["env"], project_settings, env_group
    )

    if not app.is_host:
        return

    data["env"]["AYON_HOST_NAME"] = app.host_name

    if not folder_entity or not task_entity:
        # QUESTION replace with log.info and skip workfile discovery?
        # - technically it should be possible to launch host without context
        raise ApplicationLaunchFailed(
            "Host launch require folder and task context."
        )

    workdir_data = get_template_data(
        project_entity,
        folder_entity,
        task_entity,
        app.host_name,
        project_settings
    )
    data["workdir_data"] = workdir_data

    anatomy = data["anatomy"]

    task_type = workdir_data["task"]["type"]
    # Temp solution how to pass task type to `_prepare_last_workfile`
    data["task_type"] = task_type

    try:
        workdir = get_workdir_with_workdir_data(
            workdir_data,
            anatomy.project_name,
            anatomy,
            project_settings=project_settings
        )

    except Exception as exc:
        raise ApplicationLaunchFailed(
            "Error in anatomy.format: {}".format(str(exc))
        )

    if not os.path.exists(workdir):
        log.debug(
            "Creating workdir folder: \"{}\"".format(workdir)
        )
        try:
            os.makedirs(workdir)
        except Exception as exc:
            raise ApplicationLaunchFailed(
                "Couldn't create workdir because: {}".format(str(exc))
            )

    data["env"]["AYON_WORKDIR"] = workdir

    _prepare_last_workfile(data, workdir, addons_manager)