diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index c0fd2cfc..7dbe50c5 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -15,7 +15,7 @@ from flask.ext.assets import Environment, Bundle from babel import Locale from watchdog.observers import Observer from watchdog.observers.polling import PollingObserver -from collections import defaultdict +from collections import defaultdict, OrderedDict from builtins import bytes, range from past.builtins import basestring @@ -1268,7 +1268,7 @@ class Server(object): # TODO remove again in 1.3.8 def js_bundles_for_plugins(collection, filters=None): """Produces Bundle instances""" - result = dict() + result = OrderedDict() for plugin, assets in collection.items(): if len(assets): result[plugin] = Bundle(*assets, filters=filters) @@ -1277,7 +1277,7 @@ class Server(object): else: def js_bundles_for_plugins(collection, filters=None): """Produces JsPluginBundle instances that output IIFE wrapped assets""" - result = dict() + result = OrderedDict() for plugin, assets in collection.items(): if len(assets): result[plugin] = JsPluginBundle(plugin, *assets, filters=filters) diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index f66aa0ee..248737a0 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -28,6 +28,8 @@ import octoprint.server import octoprint.users import octoprint.plugin +from octoprint.util import DefaultOrderedDict + from werkzeug.contrib.cache import BaseCache from past.builtins import basestring @@ -1348,12 +1350,12 @@ def collect_plugin_assets(enable_gcodeviewer=True, preferred_stylesheet="css"): logger = logging.getLogger(__name__ + ".collect_plugin_assets") supported_stylesheets = ("css", "less") - assets = dict(bundled=dict(js=collections.defaultdict(list), - css=collections.defaultdict(list), - less=collections.defaultdict(list)), - external=dict(js=collections.defaultdict(list), - css=collections.defaultdict(list), - less=collections.defaultdict(list))) + assets = dict(bundled=dict(js=DefaultOrderedDict(list), + css=DefaultOrderedDict(list), + less=DefaultOrderedDict(list)), + external=dict(js=DefaultOrderedDict(list), + css=DefaultOrderedDict(list), + less=DefaultOrderedDict(list))) asset_plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.AssetPlugin) for implementation in asset_plugins: diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index e4b7932c..d7a95302 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -20,6 +20,7 @@ import threading from functools import wraps import warnings import contextlib +import collections try: import queue @@ -737,6 +738,51 @@ def dict_filter(dictionary, filter_function): return dict((k, v) for k, v in dictionary.items() if filter_function(k, v)) +# Source: http://stackoverflow.com/a/6190500/562769 +class DefaultOrderedDict(collections.OrderedDict): + def __init__(self, default_factory=None, *a, **kw): + + if default_factory is not None and not callable(default_factory): + raise TypeError('first argument must be callable') + collections.OrderedDict.__init__(self, *a, **kw) + self.default_factory = default_factory + + def __getitem__(self, key): + try: + return collections.OrderedDict.__getitem__(self, key) + except KeyError: + return self.__missing__(key) + + def __missing__(self, key): + if self.default_factory is None: + raise KeyError(key) + self[key] = value = self.default_factory() + return value + + def __reduce__(self): + if self.default_factory is None: + args = tuple() + else: + args = self.default_factory, + return type(self), args, None, None, self.items() + + def copy(self): + return self.__copy__() + + def __copy__(self): + return type(self)(self.default_factory, self) + + def __deepcopy__(self, memo): + import copy + return type(self)(self.default_factory, + copy.deepcopy(self.items())) + + # noinspection PyMethodOverriding + def __repr__(self): + return 'OrderedDefaultDict(%s, %s)' % (self.default_factory, + collections.OrderedDict.__repr__(self)) + + class Object(object): pass