Skip to content

Commit c89a441

Browse files
authored
Refactor config (#340)
* add a context manager to temporarily change the global config. This will be useful for testing * make Camera instances read some of their values from the global config dict at construction, and not when the Camera class is defined as it was previously done * update test_logging to use the correct expected.txt file
1 parent 662777b commit c89a441

File tree

6 files changed

+117
-10
lines changed

6 files changed

+117
-10
lines changed

manim/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
#!/usr/bin/env python
22

3-
43
# Importing the config module should be the first thing we do, since other
54
# modules depend on the global config dict for initialization. Note that the
65
# global config dict is called 'config', just like the module itself. That's
76
# why we import the module first with a different name, and then the dict.
87
from . import config as _config
9-
from .config import config
8+
from .config import config, tempconfig
109

1110
from .constants import *
1211

manim/camera/camera.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,13 @@ class Camera(object):
4444

4545
CONFIG = {
4646
"background_image": None,
47-
"pixel_height": config["pixel_height"],
48-
"pixel_width": config["pixel_width"],
49-
"frame_rate": config["frame_rate"],
5047
# Note: frame height and width will be resized to match
5148
# the pixel aspect ratio
52-
"frame_height": config["frame_height"],
53-
"frame_width": config["frame_width"],
5449
"frame_center": ORIGIN,
5550
"background_color": BLACK,
5651
"background_opacity": 1,
5752
# Points in vectorized mobjects with norm greater
5853
# than this value will be rescaled.
59-
"max_allowable_norm": config["frame_width"],
6054
"image_mode": "RGBA",
6155
"n_channels": 4,
6256
"pixel_array_dtype": "uint8",
@@ -78,6 +72,26 @@ def __init__(self, background=None, **kwargs):
7872
Any local variables to be set.
7973
"""
8074
digest_config(self, kwargs, locals())
75+
76+
# All of the following are set to EITHER the value passed via kwargs,
77+
# OR the value stored in the global config dict at the time of
78+
# _instance construction_. Before, they were in the CONFIG dict, which
79+
# is a class attribute and is defined at the time of _class
80+
# definition_. This did not allow for creating two Cameras with
81+
# different configurations in the same session.
82+
for attr in [
83+
"pixel_height",
84+
"pixel_width",
85+
"frame_height",
86+
"frame_width",
87+
"frame_rate",
88+
]:
89+
setattr(self, attr, kwargs.get(attr, config[attr]))
90+
91+
# This one is in the same boat as the above, but it doesn't have the
92+
# same name as the corresponding key so it has to be handled on its own
93+
self.max_allowable_norm = config["frame_width"]
94+
8195
self.rgb_max_val = np.iinfo(self.pixel_array_dtype).max
8296
self.pixel_array_to_cairo_context = {}
8397

manim/config.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77
import os
88
import sys
9+
from contextlib import contextmanager
910

1011
import colour
1112

@@ -15,7 +16,55 @@
1516
from .logger import logger
1617
from .utils.tex import TexTemplate, TexTemplateFromFile
1718

18-
__all__ = ["file_writer_config", "config", "camera_config"]
19+
__all__ = ["file_writer_config", "config", "camera_config", "tempconfig"]
20+
21+
22+
config = None
23+
24+
25+
@contextmanager
26+
def tempconfig(temp):
27+
"""Context manager that temporarily modifies the global config dict.
28+
29+
The code block inside the ``with`` statement will use the modified config.
30+
After the code block, the config will be restored to its original value.
31+
32+
Parameters
33+
----------
34+
35+
temp : :class:`dict`
36+
A dictionary whose keys will be used to temporarily update the global
37+
config.
38+
39+
Examples
40+
--------
41+
Use ``with tempconfig({...})`` to temporarily change the default values of
42+
certain objects.
43+
44+
.. code_block:: python
45+
46+
c = Camera()
47+
c.frame_width == config['frame_width'] # -> True
48+
with tempconfig({'frame_width': 100}):
49+
c = Camera()
50+
c.frame_width == config['frame_width'] # -> False
51+
c.frame_width == 100 # -> True
52+
53+
"""
54+
global config
55+
original = config.copy()
56+
57+
temp = {k: v for k, v in temp.items() if k in original}
58+
59+
# In order to change the config that every module has acces to, use
60+
# update(), DO NOT use assignment. Assigning config = some_dict will just
61+
# make the local variable named config point to a new dictionary, it will
62+
# NOT change the dictionary that every module has a reference to.
63+
config.update(temp)
64+
try:
65+
yield
66+
finally:
67+
config.update(original) # update, not assignment!
1968

2069

2170
def _parse_config(config_parser, args):

tests/test_camera.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import pytest
2+
from manim import Camera, tempconfig, config
3+
4+
5+
def test_camera():
6+
"""Test that Camera instances initialize to the correct config."""
7+
# by default, use the config
8+
assert Camera().frame_width == config["frame_width"]
9+
# init args override config
10+
assert Camera(frame_width=10).frame_width == 10
11+
12+
# if config changes, reflect those changes
13+
with tempconfig({"frame_width": 100}):
14+
assert Camera().frame_width == 100
15+
# ..init args still override new config
16+
assert Camera(frame_width=10).frame_width == 10

tests/test_config.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import pytest
2+
import numpy as np
3+
from manim import config, tempconfig
4+
5+
6+
def test_tempconfig():
7+
"""Test the tempconfig context manager."""
8+
original = config.copy()
9+
10+
with tempconfig({"frame_width": 100, "frame_height": 42, "foo": -1}):
11+
# check that config was modified correctly
12+
assert config["frame_width"] == 100
13+
assert config["frame_height"] == 42
14+
15+
# 'foo' is not a key in the original dict so it shouldn't be added
16+
assert "foo" not in config
17+
18+
# check that no keys are missing and no new keys were added
19+
assert set(original.keys()) == set(config.keys())
20+
21+
# check that the keys are still untouched
22+
assert set(original.keys()) == set(config.keys())
23+
24+
# check that config is correctly restored
25+
for k, v in original.items():
26+
if isinstance(v, np.ndarray):
27+
assert np.allclose(config[k], v)
28+
else:
29+
assert config[k] == v

tests/test_logging/expected.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
DEBUG Read configuration files: config.py:
1+
DEBUG Read configuration files: config.py:
22
DEBUG Animation : Partial movie file written in scene_file_writer.py:
33
DEBUG Animation : Partial movie file written in scene_file_writer.py:
44
DEBUG Animation : Partial movie file written in scene_file_writer.py:

0 commit comments

Comments
 (0)