Added Attract extension.
For now it just contains a management command, but I ensured it is registered properly through the Pillar extension system.
This commit is contained in:
1
bcloud/__init__.py
Normal file
1
bcloud/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Blender Cloud server."""
|
0
bcloud/attract/__init__.py
Normal file
0
bcloud/attract/__init__.py
Normal file
107
bcloud/attract/cli.py
Normal file
107
bcloud/attract/cli.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
"""Commandline interface for Attract."""
|
||||||
|
|
||||||
|
from __future__ import print_function, division
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from bson import ObjectId
|
||||||
|
from eve.methods.put import put_internal
|
||||||
|
from flask import current_app
|
||||||
|
from pillar.cli import manager
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_project(project_url):
|
||||||
|
"""Find a project in the database, or SystemExit()s.
|
||||||
|
|
||||||
|
:param project_url: UUID of the project
|
||||||
|
:type: str
|
||||||
|
:return: the project
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
|
||||||
|
projects_collection = current_app.data.driver.db['projects']
|
||||||
|
|
||||||
|
# Find the project in the database.
|
||||||
|
project = projects_collection.find_one({'url': project_url})
|
||||||
|
if not project:
|
||||||
|
log.error('Project %s does not exist.', project_url)
|
||||||
|
raise SystemExit()
|
||||||
|
|
||||||
|
return project
|
||||||
|
|
||||||
|
|
||||||
|
def _update_project(project):
|
||||||
|
"""Updates a project in the database, or SystemExit()s.
|
||||||
|
|
||||||
|
:param project: the project data, should be the entire project document
|
||||||
|
:type: dict
|
||||||
|
:return: the project
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pillar.api.utils import remove_private_keys
|
||||||
|
|
||||||
|
project_id = ObjectId(project['_id'])
|
||||||
|
project = remove_private_keys(project)
|
||||||
|
result, _, _, status_code = put_internal('projects', project, _id=project_id)
|
||||||
|
|
||||||
|
if status_code != 200:
|
||||||
|
log.error("Can't update project %s, issues: %s", project_id, result['_issues'])
|
||||||
|
raise SystemExit()
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
@manager.option('-r', '--replace', dest='replace', action='store_true', default=False)
|
||||||
|
def setup_for_attract(project_url, replace=False):
|
||||||
|
"""Adds Attract node types to the project.
|
||||||
|
|
||||||
|
Use --replace to replace pre-existing Attract node types
|
||||||
|
(by default already existing Attract node types are skipped).
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO: move those node types into this extension.
|
||||||
|
from pillar.api.node_types.act import node_type_act
|
||||||
|
from pillar.api.node_types.scene import node_type_scene
|
||||||
|
from pillar.api.node_types.shot import node_type_shot
|
||||||
|
|
||||||
|
# Copy permissions from the project, then give everyone with PUT
|
||||||
|
# access also DELETE access.
|
||||||
|
project = _get_project(project_url)
|
||||||
|
permissions = copy.deepcopy(project['permissions'])
|
||||||
|
|
||||||
|
for perms in permissions.values():
|
||||||
|
for perm in perms:
|
||||||
|
methods = set(perm['methods'])
|
||||||
|
if 'PUT' not in perm['methods']:
|
||||||
|
continue
|
||||||
|
methods.add('DELETE')
|
||||||
|
perm['methods'] = list(methods)
|
||||||
|
|
||||||
|
# Make a copy of the node types when setting the permissions, as
|
||||||
|
# we don't want to mutate the global node type objects.
|
||||||
|
node_type_act = dict(permissions=permissions, **node_type_act)
|
||||||
|
node_type_scene = dict(permissions=permissions, **node_type_scene)
|
||||||
|
node_type_shot = dict(permissions=permissions, **node_type_shot)
|
||||||
|
|
||||||
|
# Add the missing node types.
|
||||||
|
for node_type in (node_type_act, node_type_scene, node_type_shot):
|
||||||
|
found = [nt for nt in project['node_types']
|
||||||
|
if nt['name'] == node_type['name']]
|
||||||
|
if found:
|
||||||
|
assert len(found) == 1, 'node type name should be unique (found %ix)' % len(found)
|
||||||
|
|
||||||
|
# TODO: validate that the node type contains all the properties Attract needs.
|
||||||
|
if replace:
|
||||||
|
log.info('Replacing existing node type %s', node_type['name'])
|
||||||
|
project['node_types'].remove(found[0])
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
project['node_types'].append(node_type)
|
||||||
|
|
||||||
|
_update_project(project)
|
||||||
|
|
||||||
|
log.info('Project %s was updated for Attract.', project_url)
|
43
bcloud/attract/extension.py
Normal file
43
bcloud/attract/extension.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
"""Pillar extension for Attract."""
|
||||||
|
|
||||||
|
from pillar.extension import PillarExtension
|
||||||
|
|
||||||
|
|
||||||
|
class AttractExtension(PillarExtension):
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return u'Attract'
|
||||||
|
|
||||||
|
def flask_config(self):
|
||||||
|
"""Returns extension-specific defaults for the Flask configuration.
|
||||||
|
|
||||||
|
Use this to set sensible default values for configuration settings
|
||||||
|
introduced by the extension.
|
||||||
|
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Just so that it registers the management commands.
|
||||||
|
from . import cli
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def blueprints(self):
|
||||||
|
"""Returns the list of top-level blueprints for the extension.
|
||||||
|
|
||||||
|
These blueprints will be mounted at the url prefix given to
|
||||||
|
app.load_extension().
|
||||||
|
|
||||||
|
:rtype: list of flask.Blueprint objects.
|
||||||
|
"""
|
||||||
|
return []
|
||||||
|
|
||||||
|
def eve_settings(self):
|
||||||
|
"""Returns extensions to the Eve settings.
|
||||||
|
|
||||||
|
Currently only the DOMAIN key is used to insert new resources into
|
||||||
|
Eve's configuration.
|
||||||
|
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
return {}
|
4
cloud.py
4
cloud.py
@@ -1,8 +1,12 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
from pillar import PillarServer
|
from pillar import PillarServer
|
||||||
|
from bcloud.attract.extension import AttractExtension
|
||||||
|
|
||||||
|
attract = AttractExtension()
|
||||||
|
|
||||||
app = PillarServer('.')
|
app = PillarServer('.')
|
||||||
|
app.load_extension(attract, '/attract')
|
||||||
app.process_extensions()
|
app.process_extensions()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
18
setup.py
Normal file
18
setup.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
"""Setup file for testing, not for packaging/distribution."""
|
||||||
|
|
||||||
|
import setuptools
|
||||||
|
|
||||||
|
setuptools.setup(
|
||||||
|
name='blender-cloud',
|
||||||
|
version='1.0',
|
||||||
|
packages=setuptools.find_packages('.', exclude=['tests']),
|
||||||
|
tests_require=[
|
||||||
|
'pytest>=2.9.1',
|
||||||
|
'responses>=0.5.1',
|
||||||
|
'pytest-cov>=2.2.1',
|
||||||
|
'mock>=2.0.0',
|
||||||
|
],
|
||||||
|
zip_safe=False,
|
||||||
|
)
|
Reference in New Issue
Block a user