Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# Migrate Python code style to ruff
a350473b6dbadff2eef9fdda9086f2d0724891ca
a350473b6dbadff2eef9fdda9086f2d0724891ca
95a918ec809636c6ff19b42d0bf4fe89f1661ab4
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.9.18 (2025-09-11)

- Re-generate graph api.

## 0.9.17 (2025-08-29)

- Add support for string default values in GraphQL APIs.
Expand Down
105 changes: 73 additions & 32 deletions bin/mujin_webstackclientpy_applyconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from mujinwebstackclient.webstackclient import WebstackClient

import logging

log = logging.getLogger(__name__)


Expand All @@ -33,42 +34,84 @@ def _ShowDiffInOneLine(oldconfig, newconfig, parentPath=None, useColours=True):

if isinstance(newconfig, list) and len(newconfig) != len(oldconfig):
absolutePathStr = '.'.join(parentPath or [])
print('[%(cc_mod)sMOD%(cc_restore)s] %(cc_mod)s%(path)s%(cc_restore)s=%(cc_add)s%(newvalue)s%(cc_restore)s (old: %(cc_del)s%(oldvalue)s%(cc_restore)s) [size changed to %(newsize)d from %(oldsize)d]' % _MergeDicts({
'path': absolutePathStr, 'newvalue': newconfig, 'oldvalue': oldconfig, 'newsize': len(newconfig), 'oldsize': len(oldconfig)
}, colourCodes if useColours else colourCodesDummy))
print(
'[%(cc_mod)sMOD%(cc_restore)s] %(cc_mod)s%(path)s%(cc_restore)s=%(cc_add)s%(newvalue)s%(cc_restore)s (old: %(cc_del)s%(oldvalue)s%(cc_restore)s) [size changed to %(newsize)d from %(oldsize)d]'
% _MergeDicts(
{
'path': absolutePathStr,
'newvalue': newconfig,
'oldvalue': oldconfig,
'newsize': len(newconfig),
'oldsize': len(oldconfig),
},
colourCodes if useColours else colourCodesDummy,
),
)
return

for key in sorted(newconfig) if isinstance(newconfig, dict) else range(len(newconfig)):
absolutePathStr = '.'.join((parentPath or []) + [key if isinstance(newconfig, dict) else '[%d]'%key])
if (isinstance(newconfig, dict) and key not in oldconfig):
print('[%(cc_add)sADD%(cc_restore)s] %(cc_add)s%(path)s%(cc_restore)s=%(cc_add)s%(newvalue)s%(cc_restore)s' % _MergeDicts({
'path': absolutePathStr, 'newvalue': newconfig[key]
}, colourCodes if useColours else colourCodesDummy))

elif type(oldconfig[key]) != type(newconfig[key]): # noqa: E721
absolutePathStr = '.'.join((parentPath or []) + [key if isinstance(newconfig, dict) else '[%d]' % key])
if isinstance(newconfig, dict) and key not in oldconfig:
print(
'[%(cc_add)sADD%(cc_restore)s] %(cc_add)s%(path)s%(cc_restore)s=%(cc_add)s%(newvalue)s%(cc_restore)s'
% _MergeDicts(
{
'path': absolutePathStr,
'newvalue': newconfig[key],
},
colourCodes if useColours else colourCodesDummy,
),
)

elif type(oldconfig[key]) != type(newconfig[key]): # noqa: E721
# key exists in both confs, but types don't match
print('[%(cc_mod)sMOD%(cc_restore)s] %(cc_mod)s%(path)s%(cc_restore)s=%(cc_add)s%(newvalue)s%(cc_restore)s (old: %(cc_del)s%(oldvalue)s%(cc_restore)s) [type changed to %(newtype)s from %(oldtype)s]' % _MergeDicts({
'path': absolutePathStr, 'newvalue': newconfig[key], 'oldvalue': oldconfig[key], 'newtype': type(newconfig), 'oldtype': type(oldconfig)
}, colourCodes if useColours else colourCodesDummy))
print(
'[%(cc_mod)sMOD%(cc_restore)s] %(cc_mod)s%(path)s%(cc_restore)s=%(cc_add)s%(newvalue)s%(cc_restore)s (old: %(cc_del)s%(oldvalue)s%(cc_restore)s) [type changed to %(newtype)s from %(oldtype)s]'
% _MergeDicts(
{
'path': absolutePathStr,
'newvalue': newconfig[key],
'oldvalue': oldconfig[key],
'newtype': type(newconfig),
'oldtype': type(oldconfig),
},
colourCodes if useColours else colourCodesDummy,
),
)

else:
# key exists in both confs with same type values
if isinstance(newconfig[key], dict) or isinstance(newconfig[key], list):
_ShowDiffInOneLine(oldconfig[key], newconfig[key], parentPath=(parentPath or [])+[key if isinstance(newconfig, dict) else '[%d]'%key], useColours=useColours)
_ShowDiffInOneLine(oldconfig[key], newconfig[key], parentPath=(parentPath or []) + [key if isinstance(newconfig, dict) else '[%d]' % key], useColours=useColours)
elif newconfig[key] != oldconfig[key]:
print('[%(cc_mod)sMOD%(cc_restore)s] %(cc_mod)s%(path)s%(cc_restore)s=%(cc_add)s%(newvalue)s%(cc_restore)s (old: %(cc_del)s%(oldvalue)s%(cc_restore)s)' % _MergeDicts({
'path': absolutePathStr, 'newvalue': newconfig[key], 'oldvalue': oldconfig[key]
}, colourCodes if useColours else colourCodesDummy))
print(
'[%(cc_mod)sMOD%(cc_restore)s] %(cc_mod)s%(path)s%(cc_restore)s=%(cc_add)s%(newvalue)s%(cc_restore)s (old: %(cc_del)s%(oldvalue)s%(cc_restore)s)'
% _MergeDicts(
{
'path': absolutePathStr,
'newvalue': newconfig[key],
'oldvalue': oldconfig[key],
},
colourCodes if useColours else colourCodesDummy,
),
)

else:
pass # same value

if isinstance(newconfig, dict):
for key in list(set(oldconfig.keys()) - set(newconfig.keys())):
absolutePathStr = '.'.join((parentPath or []) + [key])
print('[%(cc_del)sDEL%(cc_restore)s] %(cc_del)s%(path)s%(cc_restore)s (old: %(cc_del)s%(oldvalue)s%(cc_restore)s)' % _MergeDicts({
'path': absolutePathStr, 'oldvalue': oldconfig[key]
}, colourCodes if useColours else colourCodesDummy))
print(
'[%(cc_del)sDEL%(cc_restore)s] %(cc_del)s%(path)s%(cc_restore)s (old: %(cc_del)s%(oldvalue)s%(cc_restore)s)'
% _MergeDicts(
{
'path': absolutePathStr,
'oldvalue': oldconfig[key],
},
colourCodes if useColours else colourCodesDummy,
),
)


def _DiffConfig(oldconfig, newconfig, showInOneLine=False):
Expand Down Expand Up @@ -100,14 +143,14 @@ def _CopyValueOfPath(src, dest, path, parentPath=None):
if isinstance(src, dict) and isinstance(dest, dict):
for srcKey, srcValue in six.iteritems(src):
if srcKey in dest:
_CopyValueOfPath(srcValue, dest[srcKey], path[1:], (parentPath or [])+[path[0]])
_CopyValueOfPath(srcValue, dest[srcKey], path[1:], (parentPath or []) + [path[0]])
else:
log.warn('Key %s does not exist in destination conf: %s' % (srcKey, '.'.join(parentPath or ['(root)'])))

elif isinstance(src, list) and isinstance(dest, list):
for index in range(len(src)):
if len(dest) > index:
_CopyValueOfPath(src[index], dest[index], path[1:], (parentPath or [])+[path[0]])
_CopyValueOfPath(src[index], dest[index], path[1:], (parentPath or []) + [path[0]])
else:
log.warn('Index %d is out of range in destination conf: %s' % (index, '.'.join(parentPath or ['(root)'])))
break
Expand All @@ -133,12 +176,12 @@ def _CopyValueOfPath(src, dest, path, parentPath=None):
if len(path) == 1:
dest[currentPathElement] = copy.deepcopy(src[currentPathElement])
else:
_CopyValueOfPath(src[currentPathElement], dest[currentPathElement], path[1:], (parentPath or [])+[path[0]])
_CopyValueOfPath(src[currentPathElement], dest[currentPathElement], path[1:], (parentPath or []) + [path[0]])


def _ApplyTemplate(config, template, preservedpaths=None):
newconfig = copy.deepcopy(template)

# preserve the following path in the original config
if preservedpaths is None:
# add some basics
Expand All @@ -155,30 +198,27 @@ def _ApplyTemplate(config, template, preservedpaths=None):
'destContainerInfo',
'destcontainername',
'destcontainernames',

# temp settings
'robotname',
'robotspeed',
'robotaccelmult',
'robotlockmode',

# controller specific
'sceneuri',
'robots', # TODO for now skipping this
'devices', # TODO for now skipping this
'calibrationParameters', # TODO for now skipping this
'robots', # TODO for now skipping this
'devices', # TODO for now skipping this
'calibrationParameters', # TODO for now skipping this
]

# always preserve network settings!
preservedpaths += [
# network specific
'networkInterfaceSettings',
'ntpServer',
'ntpStratum',
]

# others

# others

for pathStr in set(preservedpaths):
path = pathStr.split('.')
Expand All @@ -204,6 +244,7 @@ def _RunMain():
# configure logging
try:
from mujincommon import ConfigureRootLogger

ConfigureRootLogger(level=options.loglevel)
except ImportError:
logging.basicConfig(format='%(asctime)s %(name)s [%(levelname)s] [%(filename)s:%(lineno)s %(funcName)s] %(message)s', level=options.loglevel)
Expand Down
20 changes: 15 additions & 5 deletions bin/mujin_webstackclientpy_downloaddata.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@
import os

import logging

log = logging.getLogger(__name__)


def _ConfigureLogging(level=None):
try:
import mujincommon

mujincommon.ConfigureRootLogger(level=level)
except ImportError:
logging.basicConfig(format='%(levelname)s %(name)s: %(funcName)s, %(message)s', level=logging.DEBUG)


def _ParseArguments():
import argparse

parser = argparse.ArgumentParser(description='Downloads all config files, currently used scene, and currently selected target env file from a controller')
parser.add_argument('--loglevel', type=str, default=None, help='The python log level, e.g. DEBUG, VERBOSE, ERROR, INFO, WARNING, CRITICAL (default: %(default)s)')
parser.add_argument('--url', type=str, default='http://127.0.0.1', help='URL of the controller (default: %(default)s)')
Expand All @@ -23,17 +28,19 @@ def _ParseArguments():
parser.add_argument('--timeout', type=float, default=600, help='Timeout in seconds (default: %(default)s)')
return parser.parse_args()


def _CreateWebstackClient(url, username, password):
from mujinwebstackclient.webstackclient import WebstackClient

# create a webstack client for the controller
log.info('connecting to %s', url)
log.info('connecting to %s', url)
return WebstackClient(
controllerurl=url,
controllerusername=username,
controllerpassword=password,
)


def _GetScenes(webClient):
from mujinwebstackclient import uriutils

Expand All @@ -52,17 +59,18 @@ def _GetScenes(webClient):
for targetname in config.get('selectedtargetnames', []):
if not targetname.startswith('mujin:'):
# use current scene's extension
targetname = 'mujin:/%s.mujin%s'%(targetname, sceneExtension)
targetname = 'mujin:/%s.mujin%s' % (targetname, sceneExtension)
sceneList.append(uriutils.GetPrimaryKeyFromURI(targetname))

return sceneList


def _DownloadBackup(webClient, sceneList, timeout=600.0):
import re
import tarfile

log.info('downloading scenes %s and all configs', sceneList)
response = webClient.Backup(
response = webClient.Backup(
saveconfig=True,
backupscenepks=sceneList,
timeout=timeout,
Expand All @@ -77,14 +85,16 @@ def _DownloadBackup(webClient, sceneList, timeout=600.0):
log.info('download completed, data saved to: %s', downloadDirectory)
return downloadDirectory


def _Main():
options = _ParseArguments()
_ConfigureLogging(options.loglevel)

webClient = _CreateWebstackClient(options.url, options.username, options.password)
sceneList = _GetScenes(webClient)
downloadDirectory = _DownloadBackup(webClient, sceneList, timeout=options.timeout)
print(downloadDirectory) # other scripts can read stdout and learn the directory path
print(downloadDirectory) # other scripts can read stdout and learn the directory path


if __name__ == "__main__":
if __name__ == '__main__':
_Main()
54 changes: 31 additions & 23 deletions bin/mujin_webstackclientpy_runregistrationtask.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from mujinwebstackclient import uriutils

import logging

log = logging.getLogger(__name__)


Expand All @@ -32,11 +33,11 @@ def _RunMain():
# configure logging
try:
from mujincommon import ConfigureRootLogger

ConfigureRootLogger(level=options.logLevel)
except ImportError:
logging.basicConfig(format='%(asctime)s %(name)s [%(levelname)s] [%(filename)s:%(lineno)s %(funcName)s] %(message)s', level=options.logLevel)


taskType = 'registration'
command = None
if options.syncMasterFile:
Expand Down Expand Up @@ -65,32 +66,39 @@ def _RunMain():
webstackclient.DeleteSceneTask(options.scenepk, task['pk'])

# create task
task = webstackclient.CreateSceneTask(options.scenepk, {
'tasktype': taskType,
'name': taskName,
'taskparameters': {
'command': command,
'fileStorageInfo': {
'type': 'ftp',
'username': options.ftpUsername,
'password': options.ftpPassword,
'host': options.ftpHost,
'port': options.ftpPort,
'remotePath': options.ftpPath,
task = webstackclient.CreateSceneTask(
options.scenepk,
{
'tasktype': taskType,
'name': taskName,
'taskparameters': {
'command': command,
'fileStorageInfo': {
'type': 'ftp',
'username': options.ftpUsername,
'password': options.ftpPassword,
'host': options.ftpHost,
'port': options.ftpPort,
'remotePath': options.ftpPath,
},
'remoteMasterFilePath': options.syncMasterFile,
},
'remoteMasterFilePath': options.syncMasterFile,
}
})
},
)
taskpk = task['pk']
log.info('task created: %s: %s', taskpk, task['name'])


# run task async
jobpk = webstackclient._webclient.APICall('POST', 'job/', data={
'scenepk': options.scenepk,
'target_pk': taskpk,
'resource_type': 'task',
}, expectedStatusCode=200)['jobpk']
jobpk = webstackclient._webclient.APICall(
'POST',
'job/',
data={
'scenepk': options.scenepk,
'target_pk': taskpk,
'resource_type': 'task',
},
expectedStatusCode=200,
)['jobpk']
log.info('job started: %s', jobpk)

# wait for job
Expand All @@ -113,7 +121,7 @@ def _RunMain():
newProgress = (
min(1.0, max(jobProgress[0] if jobProgress else 0.0, float(job['progress']))),
job['status'],
job['status_text']
job['status_text'],
)
if newProgress != jobProgress:
jobProgress = newProgress
Expand Down
Loading