Skip to content

host

HostBase

Bases: AbstractHost

Base of host implementation class.

Host is pipeline implementation of DCC application. This class should help to identify what must/should/can be implemented for specific functionality.

Compared to 'avalon' concept: What was before considered as functions in host implementation folder. The host implementation should primarily care about adding ability of creation (mark products to be published) and optionally about referencing published representations as containers.

Host may need extend some functionality like working with workfiles or loading. Not all host implementations may allow that for those purposes can be logic extended with implementing functions for the purpose. There are prepared interfaces to be able identify what must be implemented to be able use that functionality. - current statement is that it is not required to inherit from interfaces but all of the methods are validated (only their existence!)

Installation of host before (avalon concept):

from ayon_core.pipeline import install_host
import ayon_core.hosts.maya.api as host

install_host(host)

Installation of host now:

from ayon_core.pipeline import install_host
from ayon_core.hosts.maya.api import MayaHost

host = MayaHost()
install_host(host)
Todo
  • move content of 'install_host' as method of this class
    • register host object
    • install global plugin paths
  • store registered plugin paths to this object
  • handle current context (project, asset, task)
    • this must be done in many separated steps
  • have it's object of host tools instead of using globals

This implementation will probably change over time when more functionality and responsibility will be added.

Source code in client/ayon_core/host/host.py
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 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
140
141
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
168
169
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
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
328
329
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
class HostBase(AbstractHost):
    """Base of host implementation class.

    Host is pipeline implementation of DCC application. This class should help
    to identify what must/should/can be implemented for specific functionality.

    Compared to 'avalon' concept:
    What was before considered as functions in host implementation folder. The
    host implementation should primarily care about adding ability of creation
    (mark products to be published) and optionally about referencing published
    representations as containers.

    Host may need extend some functionality like working with workfiles
    or loading. Not all host implementations may allow that for those purposes
    can be logic extended with implementing functions for the purpose. There
    are prepared interfaces to be able identify what must be implemented to
    be able use that functionality.
    - current statement is that it is not required to inherit from interfaces
        but all of the methods are validated (only their existence!)

    # Installation of host before (avalon concept):
    ```python
    from ayon_core.pipeline import install_host
    import ayon_core.hosts.maya.api as host

    install_host(host)
    ```

    # Installation of host now:
    ```python
    from ayon_core.pipeline import install_host
    from ayon_core.hosts.maya.api import MayaHost

    host = MayaHost()
    install_host(host)
    ```

    Todo:
        - move content of 'install_host' as method of this class
            - register host object
            - install global plugin paths
        - store registered plugin paths to this object
        - handle current context (project, asset, task)
            - this must be done in many separated steps
        - have it's object of host tools instead of using globals

    This implementation will probably change over time when more
        functionality and responsibility will be added.
    """

    _log = None

    def __init__(self):
        """Initialization of host.

        Register DCC callbacks, host specific plugin paths, targets etc.
        (Part of what 'install' did in 'avalon' concept.)

        Note:
            At this moment global "installation" must happen before host
            installation. Because of this current limitation it is recommended
            to implement 'install' method which is triggered after global
            'install'.
        """

        pass

    def get_app_information(self) -> ApplicationInformation:
        """Running application information.

        Host integration should override this method and return correct
            information.

        Returns:
            ApplicationInformation: Application information.

        """
        return ApplicationInformation()

    def install(self):
        """Install host specific functionality.

        This is where should be added menu with tools, registered callbacks
        and other host integration initialization.

        It is called automatically when 'ayon_core.pipeline.install_host' is
        triggered.

        """
        pass

    @property
    def log(self) -> logging.Logger:
        if self._log is None:
            self._log = logging.getLogger(self.__class__.__name__)
        return self._log

    def get_current_project_name(self) -> str:
        """
        Returns:
            str: Current project name.

        """
        return os.environ["AYON_PROJECT_NAME"]

    def get_current_folder_path(self) -> Optional[str]:
        """
        Returns:
            Optional[str]: Current asset name.

        """
        return os.environ.get("AYON_FOLDER_PATH")

    def get_current_task_name(self) -> Optional[str]:
        """
        Returns:
            Optional[str]: Current task name.

        """
        return os.environ.get("AYON_TASK_NAME")

    def get_current_context(self) -> HostContextData:
        """Get current context information.

        This method should be used to get current context of host. Usage of
        this method can be crucial for host implementations in DCCs where
        can be opened multiple workfiles at one moment and change of context
        can't be caught properly.

        Returns:
            HostContextData: Current context with 'project_name',
                'folder_path' and 'task_name'.

        """
        return {
            "project_name": self.get_current_project_name(),
            "folder_path": self.get_current_folder_path(),
            "task_name": self.get_current_task_name()
        }

    def set_current_context(
        self,
        folder_entity: dict[str, Any],
        task_entity: dict[str, Any],
        *,
        reason: ContextChangeReason = ContextChangeReason.undefined,
        project_entity: Optional[dict[str, Any]] = None,
        anatomy: Optional[Anatomy] = None,
    ) -> HostContextData:
        """Set current context information.

        This method should be used to set current context of host. Usage of
        this method can be crucial for host implementations in DCCs where
        can be opened multiple workfiles at one moment and change of context
        can't be caught properly.

        Notes:
            This method should not care about change of workdir and expect any
                of the arguments.

        Args:
            folder_entity (Optional[dict[str, Any]]): Folder entity.
            task_entity (Optional[dict[str, Any]]): Task entity.
            reason (ContextChangeReason): Reason for context change.
            project_entity (Optional[dict[str, Any]]): Project entity data.
            anatomy (Optional[Anatomy]): Anatomy instance for the project.

        Returns:
            dict[str, Optional[str]]: Context information with project name,
                folder path and task name.

        """
        from ayon_core.pipeline import Anatomy

        folder_path = folder_entity["path"]
        task_name = task_entity["name"]

        context = self.get_current_context()
        # Don't do anything if context did not change
        if (
            context["folder_path"] == folder_path
            and context["task_name"] == task_name
        ):
            return context

        project_name = self.get_current_project_name()
        if project_entity is None:
            project_entity = ayon_api.get_project(project_name)

        if anatomy is None:
            anatomy = Anatomy(project_name, project_entity=project_entity)

        context_change_data = ContextChangeData(
            project_entity,
            folder_entity,
            task_entity,
            reason,
            anatomy,
        )
        self._before_context_change(context_change_data)
        self._set_current_context(context_change_data)
        self._after_context_change(context_change_data)

        return self._emit_context_change_event(
            project_name,
            folder_path,
            task_name,
        )

    def get_context_title(self):
        """Context title shown for UI purposes.

        Should return current context title if possible.

        Note:
            This method is used only for UI purposes so it is possible to
                return some logical title for contextless cases.
            Is not meant for "Context menu" label.

        Returns:
            str: Context title.
            None: Default title is used based on UI implementation.
        """

        # Use current context to fill the context title
        current_context = self.get_current_context()
        project_name = current_context["project_name"]
        folder_path = current_context["folder_path"]
        task_name = current_context["task_name"]
        items = []
        if project_name:
            items.append(project_name)
            if folder_path:
                items.append(folder_path.lstrip("/"))
                if task_name:
                    items.append(task_name)
        if items:
            return "/".join(items)
        return None

    @contextlib.contextmanager
    def maintained_selection(self):
        """Some functionlity will happen but selection should stay same.

        This is DCC specific. Some may not allow to implement this ability
        that is reason why default implementation is empty context manager.

        Yields:
            None: Yield when is ready to restore selected at the end.
        """

        try:
            yield
        finally:
            pass

    def _emit_context_change_event(
        self,
        project_name: str,
        folder_path: Optional[str],
        task_name: Optional[str],
    ) -> HostContextData:
        """Emit context change event.

        Args:
            project_name (str): Name of the project.
            folder_path (Optional[str]): Path of the folder.
            task_name (Optional[str]): Name of the task.

        Returns:
            HostContextData: Data send to context change event.

        """
        data: HostContextData = {
            "project_name": project_name,
            "folder_path": folder_path,
            "task_name": task_name,
        }
        emit_event("taskChanged", data)
        return data

    def _set_current_context(
        self, context_change_data: ContextChangeData
    ) -> None:
        """Method that changes the context in host.

        Can be overriden for hosts that do need different handling of context
            than using environment variables.

        Args:
            context_change_data (ContextChangeData): Context change related
                data.

        """
        project_name = self.get_current_project_name()
        folder_path = None
        task_name = None
        if context_change_data.folder_entity:
            folder_path = context_change_data.folder_entity["path"]
            if context_change_data.task_entity:
                task_name = context_change_data.task_entity["name"]

        envs = {
            "AYON_PROJECT_NAME": project_name,
            "AYON_FOLDER_PATH": folder_path,
            "AYON_TASK_NAME": task_name,
        }

        # Update the Session and environments. Pop from environments all
        #   keys with value set to None.
        for key, value in envs.items():
            if value is None:
                os.environ.pop(key, None)
            else:
                os.environ[key] = value

    def _before_context_change(self, context_change_data: ContextChangeData):
        """Before context is changed.

        This method is called before the context is changed in the host.

        Can be overridden to implement host specific logic.

        Args:
            context_change_data (ContextChangeData): Object with information
                about context change.

        """
        pass

    def _after_context_change(self, context_change_data: ContextChangeData):
        """After context is changed.

        This method is called after the context is changed in the host.

        Can be overridden to implement host specific logic.

        Args:
            context_change_data (ContextChangeData): Object with information
                about context change.

        """
        pass

__init__()

Initialization of host.

Register DCC callbacks, host specific plugin paths, targets etc. (Part of what 'install' did in 'avalon' concept.)

Note

At this moment global "installation" must happen before host installation. Because of this current limitation it is recommended to implement 'install' method which is triggered after global 'install'.

Source code in client/ayon_core/host/host.py
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def __init__(self):
    """Initialization of host.

    Register DCC callbacks, host specific plugin paths, targets etc.
    (Part of what 'install' did in 'avalon' concept.)

    Note:
        At this moment global "installation" must happen before host
        installation. Because of this current limitation it is recommended
        to implement 'install' method which is triggered after global
        'install'.
    """

    pass

get_app_information()

Running application information.

Host integration should override this method and return correct information.

Returns:

Name Type Description
ApplicationInformation ApplicationInformation

Application information.

Source code in client/ayon_core/host/host.py
 99
100
101
102
103
104
105
106
107
108
109
def get_app_information(self) -> ApplicationInformation:
    """Running application information.

    Host integration should override this method and return correct
        information.

    Returns:
        ApplicationInformation: Application information.

    """
    return ApplicationInformation()

get_context_title()

Context title shown for UI purposes.

Should return current context title if possible.

Note

This method is used only for UI purposes so it is possible to return some logical title for contextless cases. Is not meant for "Context menu" label.

Returns:

Name Type Description
str

Context title.

None

Default title is used based on UI implementation.

Source code in client/ayon_core/host/host.py
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
def get_context_title(self):
    """Context title shown for UI purposes.

    Should return current context title if possible.

    Note:
        This method is used only for UI purposes so it is possible to
            return some logical title for contextless cases.
        Is not meant for "Context menu" label.

    Returns:
        str: Context title.
        None: Default title is used based on UI implementation.
    """

    # Use current context to fill the context title
    current_context = self.get_current_context()
    project_name = current_context["project_name"]
    folder_path = current_context["folder_path"]
    task_name = current_context["task_name"]
    items = []
    if project_name:
        items.append(project_name)
        if folder_path:
            items.append(folder_path.lstrip("/"))
            if task_name:
                items.append(task_name)
    if items:
        return "/".join(items)
    return None

get_current_context()

Get current context information.

This method should be used to get current context of host. Usage of this method can be crucial for host implementations in DCCs where can be opened multiple workfiles at one moment and change of context can't be caught properly.

Returns:

Name Type Description
HostContextData HostContextData

Current context with 'project_name', 'folder_path' and 'task_name'.

Source code in client/ayon_core/host/host.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
def get_current_context(self) -> HostContextData:
    """Get current context information.

    This method should be used to get current context of host. Usage of
    this method can be crucial for host implementations in DCCs where
    can be opened multiple workfiles at one moment and change of context
    can't be caught properly.

    Returns:
        HostContextData: Current context with 'project_name',
            'folder_path' and 'task_name'.

    """
    return {
        "project_name": self.get_current_project_name(),
        "folder_path": self.get_current_folder_path(),
        "task_name": self.get_current_task_name()
    }

get_current_folder_path()

Returns:

Type Description
Optional[str]

Optional[str]: Current asset name.

Source code in client/ayon_core/host/host.py
137
138
139
140
141
142
143
def get_current_folder_path(self) -> Optional[str]:
    """
    Returns:
        Optional[str]: Current asset name.

    """
    return os.environ.get("AYON_FOLDER_PATH")

get_current_project_name()

Returns:

Name Type Description
str str

Current project name.

Source code in client/ayon_core/host/host.py
129
130
131
132
133
134
135
def get_current_project_name(self) -> str:
    """
    Returns:
        str: Current project name.

    """
    return os.environ["AYON_PROJECT_NAME"]

get_current_task_name()

Returns:

Type Description
Optional[str]

Optional[str]: Current task name.

Source code in client/ayon_core/host/host.py
145
146
147
148
149
150
151
def get_current_task_name(self) -> Optional[str]:
    """
    Returns:
        Optional[str]: Current task name.

    """
    return os.environ.get("AYON_TASK_NAME")

install()

Install host specific functionality.

This is where should be added menu with tools, registered callbacks and other host integration initialization.

It is called automatically when 'ayon_core.pipeline.install_host' is triggered.

Source code in client/ayon_core/host/host.py
111
112
113
114
115
116
117
118
119
120
121
def install(self):
    """Install host specific functionality.

    This is where should be added menu with tools, registered callbacks
    and other host integration initialization.

    It is called automatically when 'ayon_core.pipeline.install_host' is
    triggered.

    """
    pass

maintained_selection()

Some functionlity will happen but selection should stay same.

This is DCC specific. Some may not allow to implement this ability that is reason why default implementation is empty context manager.

Yields:

Name Type Description
None

Yield when is ready to restore selected at the end.

Source code in client/ayon_core/host/host.py
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
@contextlib.contextmanager
def maintained_selection(self):
    """Some functionlity will happen but selection should stay same.

    This is DCC specific. Some may not allow to implement this ability
    that is reason why default implementation is empty context manager.

    Yields:
        None: Yield when is ready to restore selected at the end.
    """

    try:
        yield
    finally:
        pass

set_current_context(folder_entity, task_entity, *, reason=ContextChangeReason.undefined, project_entity=None, anatomy=None)

Set current context information.

This method should be used to set current context of host. Usage of this method can be crucial for host implementations in DCCs where can be opened multiple workfiles at one moment and change of context can't be caught properly.

Notes

This method should not care about change of workdir and expect any of the arguments.

Parameters:

Name Type Description Default
folder_entity Optional[dict[str, Any]]

Folder entity.

required
task_entity Optional[dict[str, Any]]

Task entity.

required
reason ContextChangeReason

Reason for context change.

undefined
project_entity Optional[dict[str, Any]]

Project entity data.

None
anatomy Optional[Anatomy]

Anatomy instance for the project.

None

Returns:

Type Description
HostContextData

dict[str, Optional[str]]: Context information with project name, folder path and task name.

Source code in client/ayon_core/host/host.py
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
def set_current_context(
    self,
    folder_entity: dict[str, Any],
    task_entity: dict[str, Any],
    *,
    reason: ContextChangeReason = ContextChangeReason.undefined,
    project_entity: Optional[dict[str, Any]] = None,
    anatomy: Optional[Anatomy] = None,
) -> HostContextData:
    """Set current context information.

    This method should be used to set current context of host. Usage of
    this method can be crucial for host implementations in DCCs where
    can be opened multiple workfiles at one moment and change of context
    can't be caught properly.

    Notes:
        This method should not care about change of workdir and expect any
            of the arguments.

    Args:
        folder_entity (Optional[dict[str, Any]]): Folder entity.
        task_entity (Optional[dict[str, Any]]): Task entity.
        reason (ContextChangeReason): Reason for context change.
        project_entity (Optional[dict[str, Any]]): Project entity data.
        anatomy (Optional[Anatomy]): Anatomy instance for the project.

    Returns:
        dict[str, Optional[str]]: Context information with project name,
            folder path and task name.

    """
    from ayon_core.pipeline import Anatomy

    folder_path = folder_entity["path"]
    task_name = task_entity["name"]

    context = self.get_current_context()
    # Don't do anything if context did not change
    if (
        context["folder_path"] == folder_path
        and context["task_name"] == task_name
    ):
        return context

    project_name = self.get_current_project_name()
    if project_entity is None:
        project_entity = ayon_api.get_project(project_name)

    if anatomy is None:
        anatomy = Anatomy(project_name, project_entity=project_entity)

    context_change_data = ContextChangeData(
        project_entity,
        folder_entity,
        task_entity,
        reason,
        anatomy,
    )
    self._before_context_change(context_change_data)
    self._set_current_context(context_change_data)
    self._after_context_change(context_change_data)

    return self._emit_context_change_event(
        project_name,
        folder_path,
        task_name,
    )