We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[BUG - Develop] Global Scripts loading doesn't catch parse errors
#### Describe the bug
When there's an error with loading a global scripts module, e.g. it imports the `Room` class but that class has a syntax error, the loading error isn't caught and the traceback points to [this line](https://github.com/evennia/evennia/blob/251a70275bbd98a3e157cbb4c025597a4bb24ac9/evennia/utils/containers.py#L135) with a KeyError

#### To Reproduce
Steps to reproduce the behavior:
1. Create a simple global script which imports a class such as `Room` from your game dir, and add it to `settings.py`
2. Introduce a syntax error to your `Room` class, such as an extra space in an indent.
3. Reload the server.
4. See error

#### Expected behavior
It'd be more useful if there was an error thrown which indicated that the script had failed being loaded (and, ideally, why), as the current error invariably leads people to think there is a problem with the global scripts dict in `settings.py`.

#### Develop-branch commit
f093c8bcb

#### Additional context
I feel like it should be catching that kind of an error in [`class_from_module`](https://github.com/evennia/evennia/blob/f093c8bcb1321a44e33003cf91cf9f565e028de7/evennia/utils/utils.py#L1524) or it should at least be throwing an exception in [`load_data`](https://github.com/evennia/evennia/blob/f093c8bcb1321a44e33003cf91cf9f565e028de7/evennia/utils/containers.py#L202) but neither seem to be doing so. Instead, they just silently don't load the script.
[BUG - Develop] Evennia can no longer create global scripts from settings.py
#### Describe the bug
As of merging #2882 any attempt to create a new game with global scripts defined in settings does not create the script. It simply fails the assertion check and moves on without attempting to create them.

#### To Reproduce
Steps to reproduce the behavior:
1. Set up a new freshly-made game with `GLOBAL_SCRIPTS` defined in the settings. (I included what I used at the bottom.)
2. Attempt to start the game.
4. See error

#### Expected behavior
It's supposed to automatically create the script if it doesn't exist.

#### Develop-branch commit
b0f24f997

#### Additional context
```py
# added to typeclasses/scripts.py
from evennia.utils import logger

class TestScript(DefaultScript):
    key = "global_test_script"

    def at_repeat(self):
        logger.log_msg("This just prints a nice message.")
```

```py
# added to server/conf/settings.py
GLOBAL_SCRIPTS = {
    "global_test_script": {
        "typeclass": "typeclasses.scripts.TestScript",
        "persistent": True,
        "interval": 60,
        "desc": "A simple script to test creation.",
    },
}
```

```
2022-10-08 04:42:58 [!!] Traceback (most recent call last):
2022-10-08 04:42:58 [!!]   File "./evgames/evennia/evennia/utils/containers.py", line 214, in load_data
2022-10-08 04:42:58 [!!]     assert issubclass(script_typeclass, _BASE_SCRIPT_TYPECLASS)
2022-10-08 04:42:58 [!!] AssertionError
2022-10-08 04:42:58 [!!] GlobalScriptContainer could not start import global script global_test_script. It will be removed (skipped).
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `evennia/utils/containers.py`
Content:
```
1 """
2 Containers
3 
4 Containers are storage classes usually initialized from a setting. They
5 represent Singletons and acts as a convenient place to find resources (
6 available as properties on the singleton)
7 
8 evennia.GLOBAL_SCRIPTS
9 evennia.OPTION_CLASSES
10 
11 """
12 
13 
14 from pickle import dumps
15 
16 from django.conf import settings
17 from django.db.utils import OperationalError, ProgrammingError
18 from evennia.utils import logger
19 from evennia.utils.utils import callables_from_module, class_from_module
20 
21 SCRIPTDB = None
22 _BASE_SCRIPT_TYPECLASS = None
23 
24 
25 class Container:
26     """
27     Base container class. A container is simply a storage object whose
28     properties can be acquired as a property on it. This is generally
29     considered a read-only affair.
30 
31     The container is initialized by a list of modules containing callables.
32 
33     """
34 
35     storage_modules = []
36 
37     def __init__(self):
38         """
39         Read data from module.
40 
41         """
42         self.loaded_data = None
43 
44     def load_data(self):
45         """
46         Delayed import to avoid eventual circular imports from inside
47         the storage modules.
48 
49         """
50         if self.loaded_data is None:
51             self.loaded_data = {}
52             for module in self.storage_modules:
53                 self.loaded_data.update(callables_from_module(module))
54 
55     def __getattr__(self, key):
56         return self.get(key)
57 
58     def get(self, key, default=None):
59         """
60         Retrive data by key (in case of not knowing it beforehand).
61 
62         Args:
63             key (str): The name of the script.
64             default (any, optional): Value to return if key is not found.
65 
66         Returns:
67             any (any): The data loaded on this container.
68 
69         """
70         self.load_data()
71         return self.loaded_data.get(key, default)
72 
73     def all(self):
74         """
75         Get all stored data
76 
77         Returns:
78             scripts (list): All global script objects stored on the container.
79 
80         """
81         self.load_data()
82         return list(self.loaded_data.values())
83 
84 
85 class OptionContainer(Container):
86     """
87     Loads and stores the final list of OPTION CLASSES.
88 
89     Can access these as properties or dictionary-contents.
90     """
91 
92     storage_modules = settings.OPTION_CLASS_MODULES
93 
94 
95 class GlobalScriptContainer(Container):
96     """
97     Simple Handler object loaded by the Evennia API to contain and manage a
98     game's Global Scripts. This will list global Scripts created on their own
99     but will also auto-(re)create scripts defined in `settings.GLOBAL_SCRIPTS`.
100 
101     Example:
102         import evennia
103         evennia.GLOBAL_SCRIPTS.scriptname
104 
105     Note:
106         This does not use much of the BaseContainer since it's not loading
107         callables from settings but a custom dict of tuples.
108 
109     """
110 
111     def __init__(self):
112         """
113         Note: We must delay loading of typeclasses since this module may get
114         initialized before Scripts are actually initialized.
115 
116         """
117         self.typeclass_storage = None
118         self.loaded_data = {
119             key: {} if data is None else data for key, data in settings.GLOBAL_SCRIPTS.items()
120         }
121 
122     def _get_scripts(self, key=None, default=None):
123         global SCRIPTDB
124         if not SCRIPTDB:
125             from evennia.scripts.models import ScriptDB as SCRIPTDB
126         if key:
127             try:
128                 return SCRIPTDB.objects.get(db_key__exact=key, db_obj__isnull=True)
129             except SCRIPTDB.DoesNotExist:
130                 return default
131         else:
132             return SCRIPTDB.objects.filter(db_obj__isnull=True)
133 
134     def _load_script(self, key):
135         self.load_data()
136 
137         typeclass = self.typeclass_storage[key]
138         script = typeclass.objects.filter(
139             db_key=key, db_account__isnull=True, db_obj__isnull=True
140         ).first()
141 
142         kwargs = {**self.loaded_data[key]}
143         kwargs["key"] = key
144         kwargs["persistent"] = kwargs.get("persistent", True)
145 
146         compare_hash = str(dumps(kwargs, protocol=4))
147 
148         if script:
149             script_hash = script.attributes.get("global_script_settings", category="settings_hash")
150             if script_hash is None:
151                 # legacy - store the hash anew and assume no change
152                 script.attributes.add(
153                     "global_script_settings", compare_hash, category="settings_hash"
154                 )
155             elif script_hash != compare_hash:
156                 # wipe the old version and create anew
157                 logger.log_info(f"GLOBAL_SCRIPTS: Settings changed for {key} ({typeclass}).")
158                 script.stop()
159                 script.delete()
160                 script = None
161 
162         if not script:
163             logger.log_info(f"GLOBAL_SCRIPTS: (Re)creating {key} ({typeclass}).")
164 
165             script, errors = typeclass.create(**kwargs)
166             if errors:
167                 logger.log_err("\n".join(errors))
168                 return None
169 
170             # store a hash representation of the setup
171             script.attributes.add("_global_script_settings", compare_hash, category="settings_hash")
172 
173         return script
174 
175     def start(self):
176         """
177         Called last in evennia.__init__ to initialize the container late
178         (after script typeclasses have finished loading).
179 
180         We include all global scripts in the handler and
181         make sure to auto-load time-based scripts.
182 
183         """
184         # populate self.typeclass_storage
185         self.load_data()
186 
187         # make sure settings-defined scripts are loaded
188         for key in self.loaded_data:
189             self._load_script(key)
190         # start all global scripts
191         try:
192             for script in self._get_scripts():
193                 script.start()
194         except (OperationalError, ProgrammingError):
195             # this can happen if db is not loaded yet (such as when building docs)
196             pass
197 
198     def load_data(self):
199         """
200         This delayed import avoids trying to load Scripts before they are
201         initialized.
202 
203         """
204         if self.loaded_data:
205             # we don't always load this, it collides with doc generation
206             global _BASE_SCRIPT_TYPECLASS
207             if not _BASE_SCRIPT_TYPECLASS:
208                 _BASE_SCRIPT_TYPECLASS = class_from_module(settings.BASE_SCRIPT_TYPECLASS)
209 
210         if self.typeclass_storage is None:
211             self.typeclass_storage = {}
212             for key, data in list(self.loaded_data.items()):
213                 try:
214                     typeclass = data.get("typeclass", settings.BASE_SCRIPT_TYPECLASS)
215                     script_typeclass = class_from_module(typeclass)
216                     assert issubclass(script_typeclass, _BASE_SCRIPT_TYPECLASS)
217                     self.typeclass_storage[key] = script_typeclass
218                 except Exception:
219                     logger.log_trace(
220                         f"GlobalScriptContainer could not start import global script {key}. "
221                         "It will be removed (skipped)."
222                     )
223                     # Let's remove this key/value. We want to let other scripts load.
224                     self.loaded_data.pop(key)
225 
226     def get(self, key, default=None):
227         """
228         Retrive data by key (in case of not knowing it beforehand). Any
229         scripts that are in settings.GLOBAL_SCRIPTS that are not found
230         will be recreated on-demand.
231 
232         Args:
233             key (str): The name of the script.
234             default (any, optional): Value to return if key is not found
235                 at all on this container (i.e it cannot be loaded at all).
236 
237         Returns:
238             any (any): The data loaded on this container.
239         """
240         res = self._get_scripts(key)
241         if not res:
242             if key in self.loaded_data:
243                 # recreate if we have the info
244                 return self._load_script(key) or default
245             return default
246         return res
247 
248     def all(self):
249         """
250         Get all global scripts. Note that this will not auto-start
251         scripts defined in settings.
252 
253         Returns:
254             scripts (list): All global script objects stored on the container.
255 
256         """
257         self.typeclass_storage = None
258         self.load_data()
259         for key in self.loaded_data:
260             self._load_script(key)
261         return self._get_scripts(None)
262 
263 
264 # Create all singletons
265 
266 GLOBAL_SCRIPTS = GlobalScriptContainer()
267 OPTION_CLASSES = OptionContainer()
268
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/evennia/utils/containers.py b/evennia/utils/containers.py
--- a/evennia/utils/containers.py
+++ b/evennia/utils/containers.py
@@ -19,8 +19,6 @@
 from evennia.utils.utils import callables_from_module, class_from_module
 
 SCRIPTDB = None
-_BASE_SCRIPT_TYPECLASS = None
-
 
 class Container:
     """
@@ -201,27 +199,11 @@
         initialized.
 
         """
-        if self.loaded_data:
-            # we don't always load this, it collides with doc generation
-            global _BASE_SCRIPT_TYPECLASS
-            if not _BASE_SCRIPT_TYPECLASS:
-                _BASE_SCRIPT_TYPECLASS = class_from_module(settings.BASE_SCRIPT_TYPECLASS)
-
         if self.typeclass_storage is None:
             self.typeclass_storage = {}
             for key, data in list(self.loaded_data.items()):
-                try:
-                    typeclass = data.get("typeclass", settings.BASE_SCRIPT_TYPECLASS)
-                    script_typeclass = class_from_module(typeclass)
-                    assert issubclass(script_typeclass, _BASE_SCRIPT_TYPECLASS)
-                    self.typeclass_storage[key] = script_typeclass
-                except Exception:
-                    logger.log_trace(
-                        f"GlobalScriptContainer could not start import global script {key}. "
-                        "It will be removed (skipped)."
-                    )
-                    # Let's remove this key/value. We want to let other scripts load.
-                    self.loaded_data.pop(key)
+                typeclass = data.get("typeclass", settings.BASE_SCRIPT_TYPECLASS)
+                self.typeclass_storage[key] = class_from_module(typeclass, fallback=settings.BASE_SCRIPT_TYPECLASS)
 
     def get(self, key, default=None):
         """

{"golden_diff": "diff --git a/evennia/utils/containers.py b/evennia/utils/containers.py\n--- a/evennia/utils/containers.py\n+++ b/evennia/utils/containers.py\n@@ -19,8 +19,6 @@\n from evennia.utils.utils import callables_from_module, class_from_module\n \n SCRIPTDB = None\n-_BASE_SCRIPT_TYPECLASS = None\n-\n \n class Container:\n     \"\"\"\n@@ -201,27 +199,11 @@\n         initialized.\n \n         \"\"\"\n-        if self.loaded_data:\n-            # we don't always load this, it collides with doc generation\n-            global _BASE_SCRIPT_TYPECLASS\n-            if not _BASE_SCRIPT_TYPECLASS:\n-                _BASE_SCRIPT_TYPECLASS = class_from_module(settings.BASE_SCRIPT_TYPECLASS)\n-\n         if self.typeclass_storage is None:\n             self.typeclass_storage = {}\n             for key, data in list(self.loaded_data.items()):\n-                try:\n-                    typeclass = data.get(\"typeclass\", settings.BASE_SCRIPT_TYPECLASS)\n-                    script_typeclass = class_from_module(typeclass)\n-                    assert issubclass(script_typeclass, _BASE_SCRIPT_TYPECLASS)\n-                    self.typeclass_storage[key] = script_typeclass\n-                except Exception:\n-                    logger.log_trace(\n-                        f\"GlobalScriptContainer could not start import global script {key}. \"\n-                        \"It will be removed (skipped).\"\n-                    )\n-                    # Let's remove this key/value. We want to let other scripts load.\n-                    self.loaded_data.pop(key)\n+                typeclass = data.get(\"typeclass\", settings.BASE_SCRIPT_TYPECLASS)\n+                self.typeclass_storage[key] = class_from_module(typeclass, fallback=settings.BASE_SCRIPT_TYPECLASS)\n \n     def get(self, key, default=None):\n         \"\"\"\n", "issue": "[BUG - Develop] Global Scripts loading doesn't catch parse errors\n#### Describe the bug\r\nWhen there's an error with loading a global scripts module, e.g. it imports the `Room` class but that class has a syntax error, the loading error isn't caught and the traceback points to [this line](https://github.com/evennia/evennia/blob/251a70275bbd98a3e157cbb4c025597a4bb24ac9/evennia/utils/containers.py#L135) with a KeyError\r\n\r\n#### To Reproduce\r\nSteps to reproduce the behavior:\r\n1. Create a simple global script which imports a class such as `Room` from your game dir, and add it to `settings.py`\r\n2. Introduce a syntax error to your `Room` class, such as an extra space in an indent.\r\n3. Reload the server.\r\n4. See error\r\n\r\n#### Expected behavior\r\nIt'd be more useful if there was an error thrown which indicated that the script had failed being loaded (and, ideally, why), as the current error invariably leads people to think there is a problem with the global scripts dict in `settings.py`.\r\n\r\n#### Develop-branch commit\r\nf093c8bcb\r\n\r\n#### Additional context\r\nI feel like it should be catching that kind of an error in [`class_from_module`](https://github.com/evennia/evennia/blob/f093c8bcb1321a44e33003cf91cf9f565e028de7/evennia/utils/utils.py#L1524) or it should at least be throwing an exception in [`load_data`](https://github.com/evennia/evennia/blob/f093c8bcb1321a44e33003cf91cf9f565e028de7/evennia/utils/containers.py#L202) but neither seem to be doing so. Instead, they just silently don't load the script.\n[BUG - Develop] Evennia can no longer create global scripts from settings.py\n#### Describe the bug\r\nAs of merging #2882 any attempt to create a new game with global scripts defined in settings does not create the script. It simply fails the assertion check and moves on without attempting to create them.\r\n\r\n#### To Reproduce\r\nSteps to reproduce the behavior:\r\n1. Set up a new freshly-made game with `GLOBAL_SCRIPTS` defined in the settings. (I included what I used at the bottom.)\r\n2. Attempt to start the game.\r\n4. See error\r\n\r\n#### Expected behavior\r\nIt's supposed to automatically create the script if it doesn't exist.\r\n\r\n#### Develop-branch commit\r\nb0f24f997\r\n\r\n#### Additional context\r\n```py\r\n# added to typeclasses/scripts.py\r\nfrom evennia.utils import logger\r\n\r\nclass TestScript(DefaultScript):\r\n    key = \"global_test_script\"\r\n\r\n    def at_repeat(self):\r\n        logger.log_msg(\"This just prints a nice message.\")\r\n```\r\n\r\n```py\r\n# added to server/conf/settings.py\r\nGLOBAL_SCRIPTS = {\r\n    \"global_test_script\": {\r\n        \"typeclass\": \"typeclasses.scripts.TestScript\",\r\n        \"persistent\": True,\r\n        \"interval\": 60,\r\n        \"desc\": \"A simple script to test creation.\",\r\n    },\r\n}\r\n```\r\n\r\n```\r\n2022-10-08 04:42:58 [!!] Traceback (most recent call last):\r\n2022-10-08 04:42:58 [!!]   File \"./evgames/evennia/evennia/utils/containers.py\", line 214, in load_data\r\n2022-10-08 04:42:58 [!!]     assert issubclass(script_typeclass, _BASE_SCRIPT_TYPECLASS)\r\n2022-10-08 04:42:58 [!!] AssertionError\r\n2022-10-08 04:42:58 [!!] GlobalScriptContainer could not start import global script global_test_script. It will be removed (skipped).\r\n```\n", "before_files": [{"content": "\"\"\"\nContainers\n\nContainers are storage classes usually initialized from a setting. They\nrepresent Singletons and acts as a convenient place to find resources (\navailable as properties on the singleton)\n\nevennia.GLOBAL_SCRIPTS\nevennia.OPTION_CLASSES\n\n\"\"\"\n\n\nfrom pickle import dumps\n\nfrom django.conf import settings\nfrom django.db.utils import OperationalError, ProgrammingError\nfrom evennia.utils import logger\nfrom evennia.utils.utils import callables_from_module, class_from_module\n\nSCRIPTDB = None\n_BASE_SCRIPT_TYPECLASS = None\n\n\nclass Container:\n    \"\"\"\n    Base container class. A container is simply a storage object whose\n    properties can be acquired as a property on it. This is generally\n    considered a read-only affair.\n\n    The container is initialized by a list of modules containing callables.\n\n    \"\"\"\n\n    storage_modules = []\n\n    def __init__(self):\n        \"\"\"\n        Read data from module.\n\n        \"\"\"\n        self.loaded_data = None\n\n    def load_data(self):\n        \"\"\"\n        Delayed import to avoid eventual circular imports from inside\n        the storage modules.\n\n        \"\"\"\n        if self.loaded_data is None:\n            self.loaded_data = {}\n            for module in self.storage_modules:\n                self.loaded_data.update(callables_from_module(module))\n\n    def __getattr__(self, key):\n        return self.get(key)\n\n    def get(self, key, default=None):\n        \"\"\"\n        Retrive data by key (in case of not knowing it beforehand).\n\n        Args:\n            key (str): The name of the script.\n            default (any, optional): Value to return if key is not found.\n\n        Returns:\n            any (any): The data loaded on this container.\n\n        \"\"\"\n        self.load_data()\n        return self.loaded_data.get(key, default)\n\n    def all(self):\n        \"\"\"\n        Get all stored data\n\n        Returns:\n            scripts (list): All global script objects stored on the container.\n\n        \"\"\"\n        self.load_data()\n        return list(self.loaded_data.values())\n\n\nclass OptionContainer(Container):\n    \"\"\"\n    Loads and stores the final list of OPTION CLASSES.\n\n    Can access these as properties or dictionary-contents.\n    \"\"\"\n\n    storage_modules = settings.OPTION_CLASS_MODULES\n\n\nclass GlobalScriptContainer(Container):\n    \"\"\"\n    Simple Handler object loaded by the Evennia API to contain and manage a\n    game's Global Scripts. This will list global Scripts created on their own\n    but will also auto-(re)create scripts defined in `settings.GLOBAL_SCRIPTS`.\n\n    Example:\n        import evennia\n        evennia.GLOBAL_SCRIPTS.scriptname\n\n    Note:\n        This does not use much of the BaseContainer since it's not loading\n        callables from settings but a custom dict of tuples.\n\n    \"\"\"\n\n    def __init__(self):\n        \"\"\"\n        Note: We must delay loading of typeclasses since this module may get\n        initialized before Scripts are actually initialized.\n\n        \"\"\"\n        self.typeclass_storage = None\n        self.loaded_data = {\n            key: {} if data is None else data for key, data in settings.GLOBAL_SCRIPTS.items()\n        }\n\n    def _get_scripts(self, key=None, default=None):\n        global SCRIPTDB\n        if not SCRIPTDB:\n            from evennia.scripts.models import ScriptDB as SCRIPTDB\n        if key:\n            try:\n                return SCRIPTDB.objects.get(db_key__exact=key, db_obj__isnull=True)\n            except SCRIPTDB.DoesNotExist:\n                return default\n        else:\n            return SCRIPTDB.objects.filter(db_obj__isnull=True)\n\n    def _load_script(self, key):\n        self.load_data()\n\n        typeclass = self.typeclass_storage[key]\n        script = typeclass.objects.filter(\n            db_key=key, db_account__isnull=True, db_obj__isnull=True\n        ).first()\n\n        kwargs = {**self.loaded_data[key]}\n        kwargs[\"key\"] = key\n        kwargs[\"persistent\"] = kwargs.get(\"persistent\", True)\n\n        compare_hash = str(dumps(kwargs, protocol=4))\n\n        if script:\n            script_hash = script.attributes.get(\"global_script_settings\", category=\"settings_hash\")\n            if script_hash is None:\n                # legacy - store the hash anew and assume no change\n                script.attributes.add(\n                    \"global_script_settings\", compare_hash, category=\"settings_hash\"\n                )\n            elif script_hash != compare_hash:\n                # wipe the old version and create anew\n                logger.log_info(f\"GLOBAL_SCRIPTS: Settings changed for {key} ({typeclass}).\")\n                script.stop()\n                script.delete()\n                script = None\n\n        if not script:\n            logger.log_info(f\"GLOBAL_SCRIPTS: (Re)creating {key} ({typeclass}).\")\n\n            script, errors = typeclass.create(**kwargs)\n            if errors:\n                logger.log_err(\"\\n\".join(errors))\n                return None\n\n            # store a hash representation of the setup\n            script.attributes.add(\"_global_script_settings\", compare_hash, category=\"settings_hash\")\n\n        return script\n\n    def start(self):\n        \"\"\"\n        Called last in evennia.__init__ to initialize the container late\n        (after script typeclasses have finished loading).\n\n        We include all global scripts in the handler and\n        make sure to auto-load time-based scripts.\n\n        \"\"\"\n        # populate self.typeclass_storage\n        self.load_data()\n\n        # make sure settings-defined scripts are loaded\n        for key in self.loaded_data:\n            self._load_script(key)\n        # start all global scripts\n        try:\n            for script in self._get_scripts():\n                script.start()\n        except (OperationalError, ProgrammingError):\n            # this can happen if db is not loaded yet (such as when building docs)\n            pass\n\n    def load_data(self):\n        \"\"\"\n        This delayed import avoids trying to load Scripts before they are\n        initialized.\n\n        \"\"\"\n        if self.loaded_data:\n            # we don't always load this, it collides with doc generation\n            global _BASE_SCRIPT_TYPECLASS\n            if not _BASE_SCRIPT_TYPECLASS:\n                _BASE_SCRIPT_TYPECLASS = class_from_module(settings.BASE_SCRIPT_TYPECLASS)\n\n        if self.typeclass_storage is None:\n            self.typeclass_storage = {}\n            for key, data in list(self.loaded_data.items()):\n                try:\n                    typeclass = data.get(\"typeclass\", settings.BASE_SCRIPT_TYPECLASS)\n                    script_typeclass = class_from_module(typeclass)\n                    assert issubclass(script_typeclass, _BASE_SCRIPT_TYPECLASS)\n                    self.typeclass_storage[key] = script_typeclass\n                except Exception:\n                    logger.log_trace(\n                        f\"GlobalScriptContainer could not start import global script {key}. \"\n                        \"It will be removed (skipped).\"\n                    )\n                    # Let's remove this key/value. We want to let other scripts load.\n                    self.loaded_data.pop(key)\n\n    def get(self, key, default=None):\n        \"\"\"\n        Retrive data by key (in case of not knowing it beforehand). Any\n        scripts that are in settings.GLOBAL_SCRIPTS that are not found\n        will be recreated on-demand.\n\n        Args:\n            key (str): The name of the script.\n            default (any, optional): Value to return if key is not found\n                at all on this container (i.e it cannot be loaded at all).\n\n        Returns:\n            any (any): The data loaded on this container.\n        \"\"\"\n        res = self._get_scripts(key)\n        if not res:\n            if key in self.loaded_data:\n                # recreate if we have the info\n                return self._load_script(key) or default\n            return default\n        return res\n\n    def all(self):\n        \"\"\"\n        Get all global scripts. Note that this will not auto-start\n        scripts defined in settings.\n\n        Returns:\n            scripts (list): All global script objects stored on the container.\n\n        \"\"\"\n        self.typeclass_storage = None\n        self.load_data()\n        for key in self.loaded_data:\n            self._load_script(key)\n        return self._get_scripts(None)\n\n\n# Create all singletons\n\nGLOBAL_SCRIPTS = GlobalScriptContainer()\nOPTION_CLASSES = OptionContainer()\n", "path": "evennia/utils/containers.py"}], "after_files": [{"content": "\"\"\"\nContainers\n\nContainers are storage classes usually initialized from a setting. They\nrepresent Singletons and acts as a convenient place to find resources (\navailable as properties on the singleton)\n\nevennia.GLOBAL_SCRIPTS\nevennia.OPTION_CLASSES\n\n\"\"\"\n\n\nfrom pickle import dumps\n\nfrom django.conf import settings\nfrom django.db.utils import OperationalError, ProgrammingError\nfrom evennia.utils import logger\nfrom evennia.utils.utils import callables_from_module, class_from_module\n\nSCRIPTDB = None\n\nclass Container:\n    \"\"\"\n    Base container class. A container is simply a storage object whose\n    properties can be acquired as a property on it. This is generally\n    considered a read-only affair.\n\n    The container is initialized by a list of modules containing callables.\n\n    \"\"\"\n\n    storage_modules = []\n\n    def __init__(self):\n        \"\"\"\n        Read data from module.\n\n        \"\"\"\n        self.loaded_data = None\n\n    def load_data(self):\n        \"\"\"\n        Delayed import to avoid eventual circular imports from inside\n        the storage modules.\n\n        \"\"\"\n        if self.loaded_data is None:\n            self.loaded_data = {}\n            for module in self.storage_modules:\n                self.loaded_data.update(callables_from_module(module))\n\n    def __getattr__(self, key):\n        return self.get(key)\n\n    def get(self, key, default=None):\n        \"\"\"\n        Retrive data by key (in case of not knowing it beforehand).\n\n        Args:\n            key (str): The name of the script.\n            default (any, optional): Value to return if key is not found.\n\n        Returns:\n            any (any): The data loaded on this container.\n\n        \"\"\"\n        self.load_data()\n        return self.loaded_data.get(key, default)\n\n    def all(self):\n        \"\"\"\n        Get all stored data\n\n        Returns:\n            scripts (list): All global script objects stored on the container.\n\n        \"\"\"\n        self.load_data()\n        return list(self.loaded_data.values())\n\n\nclass OptionContainer(Container):\n    \"\"\"\n    Loads and stores the final list of OPTION CLASSES.\n\n    Can access these as properties or dictionary-contents.\n    \"\"\"\n\n    storage_modules = settings.OPTION_CLASS_MODULES\n\n\nclass GlobalScriptContainer(Container):\n    \"\"\"\n    Simple Handler object loaded by the Evennia API to contain and manage a\n    game's Global Scripts. This will list global Scripts created on their own\n    but will also auto-(re)create scripts defined in `settings.GLOBAL_SCRIPTS`.\n\n    Example:\n        import evennia\n        evennia.GLOBAL_SCRIPTS.scriptname\n\n    Note:\n        This does not use much of the BaseContainer since it's not loading\n        callables from settings but a custom dict of tuples.\n\n    \"\"\"\n\n    def __init__(self):\n        \"\"\"\n        Note: We must delay loading of typeclasses since this module may get\n        initialized before Scripts are actually initialized.\n\n        \"\"\"\n        self.typeclass_storage = None\n        self.loaded_data = {\n            key: {} if data is None else data for key, data in settings.GLOBAL_SCRIPTS.items()\n        }\n\n    def _get_scripts(self, key=None, default=None):\n        global SCRIPTDB\n        if not SCRIPTDB:\n            from evennia.scripts.models import ScriptDB as SCRIPTDB\n        if key:\n            try:\n                return SCRIPTDB.objects.get(db_key__exact=key, db_obj__isnull=True)\n            except SCRIPTDB.DoesNotExist:\n                return default\n        else:\n            return SCRIPTDB.objects.filter(db_obj__isnull=True)\n\n    def _load_script(self, key):\n        self.load_data()\n\n        typeclass = self.typeclass_storage[key]\n        script = typeclass.objects.filter(\n            db_key=key, db_account__isnull=True, db_obj__isnull=True\n        ).first()\n\n        kwargs = {**self.loaded_data[key]}\n        kwargs[\"key\"] = key\n        kwargs[\"persistent\"] = kwargs.get(\"persistent\", True)\n\n        compare_hash = str(dumps(kwargs, protocol=4))\n\n        if script:\n            script_hash = script.attributes.get(\"global_script_settings\", category=\"settings_hash\")\n            if script_hash is None:\n                # legacy - store the hash anew and assume no change\n                script.attributes.add(\n                    \"global_script_settings\", compare_hash, category=\"settings_hash\"\n                )\n            elif script_hash != compare_hash:\n                # wipe the old version and create anew\n                logger.log_info(f\"GLOBAL_SCRIPTS: Settings changed for {key} ({typeclass}).\")\n                script.stop()\n                script.delete()\n                script = None\n\n        if not script:\n            logger.log_info(f\"GLOBAL_SCRIPTS: (Re)creating {key} ({typeclass}).\")\n\n            script, errors = typeclass.create(**kwargs)\n            if errors:\n                logger.log_err(\"\\n\".join(errors))\n                return None\n\n            # store a hash representation of the setup\n            script.attributes.add(\"_global_script_settings\", compare_hash, category=\"settings_hash\")\n\n        return script\n\n    def start(self):\n        \"\"\"\n        Called last in evennia.__init__ to initialize the container late\n        (after script typeclasses have finished loading).\n\n        We include all global scripts in the handler and\n        make sure to auto-load time-based scripts.\n\n        \"\"\"\n        # populate self.typeclass_storage\n        self.load_data()\n\n        # make sure settings-defined scripts are loaded\n        for key in self.loaded_data:\n            self._load_script(key)\n        # start all global scripts\n        try:\n            for script in self._get_scripts():\n                script.start()\n        except (OperationalError, ProgrammingError):\n            # this can happen if db is not loaded yet (such as when building docs)\n            pass\n\n    def load_data(self):\n        \"\"\"\n        This delayed import avoids trying to load Scripts before they are\n        initialized.\n\n        \"\"\"\n        if self.typeclass_storage is None:\n            self.typeclass_storage = {}\n            for key, data in list(self.loaded_data.items()):\n                typeclass = data.get(\"typeclass\", settings.BASE_SCRIPT_TYPECLASS)\n                self.typeclass_storage[key] = class_from_module(typeclass, fallback=settings.BASE_SCRIPT_TYPECLASS)\n\n    def get(self, key, default=None):\n        \"\"\"\n        Retrive data by key (in case of not knowing it beforehand). Any\n        scripts that are in settings.GLOBAL_SCRIPTS that are not found\n        will be recreated on-demand.\n\n        Args:\n            key (str): The name of the script.\n            default (any, optional): Value to return if key is not found\n                at all on this container (i.e it cannot be loaded at all).\n\n        Returns:\n            any (any): The data loaded on this container.\n        \"\"\"\n        res = self._get_scripts(key)\n        if not res:\n            if key in self.loaded_data:\n                # recreate if we have the info\n                return self._load_script(key) or default\n            return default\n        return res\n\n    def all(self):\n        \"\"\"\n        Get all global scripts. Note that this will not auto-start\n        scripts defined in settings.\n\n        Returns:\n            scripts (list): All global script objects stored on the container.\n\n        \"\"\"\n        self.typeclass_storage = None\n        self.load_data()\n        for key in self.loaded_data:\n            self._load_script(key)\n        return self._get_scripts(None)\n\n\n# Create all singletons\n\nGLOBAL_SCRIPTS = GlobalScriptContainer()\nOPTION_CLASSES = OptionContainer()\n", "path": "evennia/utils/containers.py"}]}