getavalon / core

The safe post-production pipeline - https://getavalon.github.io/2.0
MIT License
214 stars 48 forks source link

Session schema too strict #432

Open mottosso opened 4 years ago

mottosso commented 4 years ago

Problem

Our schema validation is too strict.

You can query available projects, without first giving Avalon a project.

import os
from avalon import io
io.install()
io.projects()
# [batman]

But it'll warn you about it, claiming there are missing required variables when there aren't.

avalon.io : WARNING : u'AVALON_PROJECTS' is a required property

Not only that, it'll warn you about 1 variable at a time. In this case, there are actually 6 required variables. Furthermore, it'll warn you in the most unfriendly way possible; leaving the message itself above a long and indecipherable dump of the full schema contents.

See for yourself ```py >>> io.install() avalon.io : WARNING : u'AVALON_PROJECTS' is a required property Failed validating u'required' in schema: {u'$schema': u'http://json-schema.org/schema#', u'additionalProperties': True, u'description': u'The Avalon environment', u'properties': {u'AVALON_APP': {u'description': u'Name of application', u'example': u'maya2016', u'pattern': u'^\\w*$', u'type': u'string'}, u'AVALON_ASSET': {u'description': u'Name of asset', u'example': u'Bruce', u'pattern': u'^\\w*$', u'type': u'string'}, u'AVALON_CONFIG': {u'description': u'Name of Avalon configuration', u'example': u'polly', u'pattern': u'^\\w*$', u'type': u'string'}, u'AVALON_CONTAINER_ID': {u'default': u'avalon.container', u'description': u'Unique identifier for a loaded representation in a working file', u'example': u'avalon.container', u'pattern': u'^[\\w.]*$', u'type': u'string'}, u'AVALON_DB': {u'default': u'avalon', u'description': u'Name of database', u'example': u'avalon', u'pattern': u'^\\w*$', u'type': u'string'}, u'AVALON_DEADLINE': {u'default': None, u'description': u'Address to Deadline', u'example': u'http://192.168.99.101', u'pattern': u'^http[\\w/@:.]*$', u'type': u'string'}, u'AVALON_DEBUG': {u'default': None, u'description': u'Enable debugging mode. Some applications may use this for e.g. extended verbosity or mock plug-ins.', u'example': u'True', u'type': u'string'}, u'AVALON_INSTANCE_ID': {u'default': u'avalon.instance', u'description': u'Unique identifier for instances in a working file', u'example': u'avalon.instance', u'pattern': u'^[\\w.]*$', u'type': u'string'}, u'AVALON_LABEL': {u'default': u'Avalon', u'description': u'Nice name of Avalon, used in e.g. graphical user interfaces', u'example': u'Mindbender', u'type': u'string'}, u'AVALON_MONGO': {u'default': u'mongodb://localhost:27017', u'description': u'Address to the asset database', u'example': u'mongodb://localhost:27017', u'pattern': u'^mongodb://[\\w/@:.]*$', u'type': u'string'}, u'AVALON_PASSWORD': {u'default': u'secret', u'description': u'Generic password', u'example': u'abc123', u'pattern': u'^\\w*$', u'type': u'string'}, u'AVALON_PROJECT': {u'description': u'Name of project', u'example': u'Hulk', u'pattern': u'^\\w*$', u'type': u'string'}, u'AVALON_PROJECTS': {u'description': u'Absolute path to root of project directories', u'example': u'/nas/projects', u'type': u'string'}, u'AVALON_SENTRY': {u'default': None, u'description': u'Address to Sentry', u'example': u'https://5b872b280de742919b115bdc8da076a5:8d278266fe764361b8fa6024af004a9c@logs.mindbender.com/2', u'pattern': u'^http[\\w/@:.]*$', u'type': u'string'}, u'AVALON_SILO': {u'description': u'Name of asset group or container', u'example': u'assets', u'pattern': u'^\\w*$', u'type': u'string'}, u'AVALON_TASK': {u'description': u'Name of task', u'example': u'modeling', u'pattern': u'^\\w*$', u'type': u'string'}, u'AVALON_TIMEOUT': {u'default': u'1000', u'description': u'Wherever there is a need for a timeout, this is the default value.', u'example': u'1000', u'pattern': u'^[0-9]*$', u'type': u'string'}, u'AVALON_UPLOAD': {u'default': None, u'description': u'Boolean of whether to upload published material to central asset repository', u'example': u'True', u'type': u'string'}, u'AVALON_USERNAME': {u'default': u'avalon', u'description': u'Generic username', u'example': u'myself', u'pattern': u'^\\w*$', u'type': u'string'}, u'AVALON_WORKDIR': {u'description': u"Current working directory of a host, such as Maya's location of workspace.mel", u'example': u'/mnt/projects/alita/assets/vector/maya', u'type': u'string'}}, u'required': [u'AVALON_PROJECTS', u'AVALON_PROJECT', u'AVALON_ASSET', u'AVALON_SILO', u'AVALON_CONFIG'], u'title': u'avalon-core:session-1.0', u'type': u'object'} On instance: {'AVALON_CONFIG': 'no_config', 'AVALON_CONTAINER_ID': 'avalon.container', 'AVALON_DB': 'avalon', 'AVALON_INSTANCE_ID': 'avalon.instance', 'AVALON_LABEL': 'Avalon', 'AVALON_LOCATION': 'http://127.0.0.1', 'AVALON_MONGO': 'mongodb://localhost:27017', 'AVALON_PASSWORD': 'secret', 'AVALON_TIMEOUT': '1000', 'AVALON_USERNAME': 'avalon', 'schema': 'avalon-core:session-1.0'} avalon.io : INFO : Connected to mongodb://localhost:27017, delay 0.011 s ```

Furthermore, it doesn't allow you to list available assets, without first giving it an asset.

Ultimately, this isn't a mere bug in the code, but rather a bug in the architecture. Yes, we need a schema to keep an eye on standardised objects, but the requirements of the Session object are currently too general.

Some queries do require an asset, like querying subsets. Which in turn require a project to have been set. But we can't make a schema for each type of query (or can we?) and neither can we somehow implement logic into it that customises requirements based on what you're asking for (too implicit, too complex).

The query mechanism itself is too general for that.

io.find({"type": "subset", "name": "someSubset"})

And it's good that it's general.. I think? An alternative would be to have explicit query functions for each type of query.

io.find_project(name=None)  # Requires active database
io.find_asset(name=None) # Requires AVALON_PROJECT
io.find_subset(name=None, parent=None) # Requires AVALON_ASSET, or an asset document
io.find_version(name=None, parent=subset) # Requires the above, and a subset document
io.find_representation(name=None, parent=version) # Requires the above, and a version document

Alternatively, maybe this is a good place for a..

io.ls(type="subset")
io.ls(type="project")
io.ls(name="something")  # Anything with a matching name

Where the type= argument could in fact be correlated to required members of a schema.

I think the overall issue is that we're keeping some values as local state, and others not; so we can say io.find() without having to indicate a project and asset each time. That in turn was made to facilitate api.ls() and host.ls() which is supposed to be context sensitive.

Proposal

For starters, io.py:

  1. Do not claim to require variables, when you don't
  2. Skip the unnecessary output in your error

Related to #428