-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Refactoring the config system #337
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Ok @ManimCommunity/core so I've been thinking hard and long about the next steps for the config system. Right now everything seems to work fine, but it's all haphazardly put together, and there are some missing features. For example:
As you can see, there are many problems. I believe I have a way to fix all of these in a way that is (mostly) backwards compatible. Basically, we define a new class that behaves like a dict but also contains all of the information that keeps it self-consistent. I believe this is the way to go, but I wanted everyone's opinions before implementing. Here is how that would look like: from collections.abc import MutableMapping
class ManimConfigDict(MutableMapping):
ALL_OPTS = ['pixel_width', 'pixel_height', 'aspect_ratio']
def __init__(self):
self.__dict__ = {k: None for k in ALL_OPTS} Remember setting the What does this def __getitem__(self, key): # c['pixel_width'] returns c.pixel_width
return self.__dict__[key]
def __iter__(self): # required by MutableMapping
return iter(self.__dict__)
def __len__(self): # required by MutableMapping
return len(self.__dict__) This means that But also we don't want anyone to delete any keys, so def __delitem__(self, key):
raise NotImplementedError('Deleting keys from ManimConfigDict is not allowed.') Now here is the fun part. What happens if you set def __setitem__(self, key, value):
self.__dict__[key] = value # this will in turn call the properties defined later
@property
def pixel_width(self):
return self.__dict__['pixel_width']
@pixel_width.setter
def pixel_width(self, val):
# keep aspect ratio!
self.__dict__['pixel_width'] = val
self.__dict__['pixel_height'] = val / self.aspect_ratio So now every time that you change the width, the height keeps the correct aspect. And you can do the same for the height. And you can even change the aspect ratio itself and make the width/height keep consistent. So let's see how this solves the problems above:
Because the config system is its own subpackage (#376), we just have to change one or two files in the codebase. All other files can keep using What do you think? Did I miss anything? Are there better ways of implementing this? Please let me know. Please leave me an emoji here to make sure we have consensus before I go about implementing this. |
My concern with this is that we have a similar issue that the CONFIG system had/has. Because the attributes are added at runtime, will an IDE for example be able to recognize that those attributes are there? |
Fair. I can manually add each config option as an attribute, set them to None, and then replace them with the corresponding property dynamically afterward. I think that'd work? |
First of all, thank you for this post and taking time to do this. Everything is vert good to me. Question tho : What if one wants to mofify the config during the process ? for example, file_writer_config['disable_caching'} is enabled when the code comes from stdlin, how could we do this after this ? |
I'm not sure I understand the question, but this sounds like exactly what |
make sure to specify their types as well, then sure |
But, when exactly Because, in my case, the change that must be done (i.e, disable_caching to True) is not temporary but rather permanently for the whole process. |
Ok I see now. Then tempconfig is not what you want. What you want is to call |
After five merged PRs, I'm ready to close this. @ManimCommunity/tests you can now set a temporary config using |
Yep, the config system strikes again. I'll try to keep it short this time. The main point is that, while the current config system works fairly well, it's quickly become a mess and difficult to maintain. For example, here are just two problems with it:
config
andcamera_config
are ambiguous -- Usage of config vs camera_config and separating them. #283file_writer_config
,cameraconfig_
,config
are confusing, and sometimes read/parse the same cfg files twice -- see REFACTOR : _run_config() should not return file_writer_config. #443The plan
My plan for refactoring is the following, modified from this comment:
I want to refactor the config system so that the order of events would be the following:
import manim
, which in turn executesmanim/__init__.py
. This can be a user scene script or our ownmanim/__main__.py
.__init__.py
, the first line of code isimport config
which executesconfig/__init__.py
.config/__init__.py
first executesconfig/config.py
.config.py
, as much as possible of the config dict should be defined. In particular, all config files must be read and parsed. Also,config.py
also parses the sections of the configuration corresponding to the logger, and exports them as well. All ofconfig
,camera_config
, andfile_writer_config
are defined and exported. NOTE the logger is NOT set here.config/__init__.py
, the next line it runs executesconfig/logger.py
.config/logger.py
takes the config options exported fromconfig/config.py
and uses them to set the logger. NOTE this eliminates the need to execute_run_config
twice.config/__init__.py
, everything exported byconfig/config.py
andconfig/logger.py
is exported up to the top-level package. After this, the config subpackage should never really have to be used again (other than for importing config/logger).config/__init__.py
now returns execution to the top-level__init__.py
.__init__.py
, here comes the split.Scene
classes and ultimately rendering them.__main__
because manim has been called from the command line,__init__.py
returns execution to__main__.main()
. Now, this function must deal with parsing command line flags and then updating the globalconfig
dict. This can easily be done by implementing e.g.manim.config.parse_cli(args)
. Note this will remove the problem we've had in the past whereconfig.py
had to determine whether it had been called from the command line (i.e. the hacky_from_command_line
function). Once the CLI flags have been parsed and the config updated,__main__.main()
simply decides what to execute next, depending on the subcommand used. Thus, a big chunk ofcfg_subcommands
will be ported into__main__.py
.Associated PRs
This is a fairly major overhaul so I'll try to keep the PRs short and self-contained so that they are easy to review.
Refactor config #340(merged): add thetempconfig
context manager for testing all of the upcoming changes. Also makeCamera
read the config at instance construction, not at class definition.Create config subpackage #376(merged): creates a new subpackage for all the stuff related to the config system.Stop using config at import #397(merged): makes all modules use theconfig
dict as little as possible during library initialization.Refactor config subpkg #543(merged): refactors the config subpackage so that onlyconfig/__init__.py
defines and exports global variables, whereas all other files just contain utilities.Replace the global config dict with new class ManimConfig #620(merged): replaces the global config with a custom classManimConfig
.The text was updated successfully, but these errors were encountered: