Learn one of Sanic's series of problems: app.update_config()

Keywords: Python

From Flaskl to Sanic, I encountered some problems during my study. I will try to make it clear and expect a series of articles to come out.

app.update_config()

With Flask, the following configuration classes are used in the tutorial:

# config.py
import os

class Config:
    BASE_DIR = os.path.dirname(os.path.dirname(__file__))

    @staticmethod
    def init_app(app):
        pass

class DevConfig(Config):
    DEBUG = True
    DB_URL = 'mysql://test:Tes@*.*.*.*:3306/test'


class ProConfig(Config):
    DEBUG = False
    DB_URL = 'mysql://production:production@*.*.*.*:3306/production'


config = {
    'development': DevConfig,
    'production': ProConfig,

    'default': DevConfig
}

Use it as follows:

app = Flask(__name__)
app.config.from_object(DevConfig) 

Different environments use different configurations, and some of the same configurations can be placed in the Config base class (such as BASE_DIR).

In anic, app.update_config() can also pass in a class, but unexpectedly:

app = Sanic(__name__)
app.update_config(Devconfig)	# BASE_DIR with no parent in the configuration at this time

Look at the code for update_config() in sanic, which gets attributes from u dict_u when the attributes of the parent class are unreadable:

def update_config(self, config: Union[bytes, str, dict, Any]):
		# config is a bytes,str,Path type of processing
	if isinstance(config, (bytes, str, Path)):
		config = load_module_from_file_location(location=config)
	# Whether it is an incoming class or an object, attributes are acquired through u dict_, and the attributes of its parent class are unreadable
    	if not isinstance(config, dict):
    		cfg = {}
          	if not isclass(config):
              cfg.update(
                  {
                      key: getattr(config, key)
                      for key in config.__class__.__dict__.keys()	# Getting class attributes from u dict_u of the object's class
                  }	
              )

          config = dict(config.__dict__)	# Use u dict_u directly to get attributes
          config.update(cfg)

      config = dict(filter(lambda i: i[0].isupper(), config.items()))

      self.update(config)

flask's app.config.from_object() uses dir(object) to get attributes. It reads the attributes of the parent class, and then uses getattr(obj, key) to read the attribute values:

def from_object(self, obj):
     if isinstance(obj, string_types):
         obj = import_string(obj)
     for key in dir(obj):	# Using dir(obj), the attributes of the parent class are also read out
         if key.isupper():	# Determine if the key is capitalized
             self[key] = getattr(obj, key)

The question arises whether to use u dict_ or dir(object). The differences between the two are as follows:

_u dict_u: View all the properties in the class, it is a dictionary.

  • It is important to note that this property can be invoked with the class name or an instance object of the class, that a direct call to u dict_u with the class name will output the dictionary composed of all the class properties in the class, and that a call to u dict_u with the instance object of the class will output a dictionary composed of all the instance properties in the class.
  • For parent and child classes with inheritance, the parent has its own u dict_u, and the child has its own u dict_u, which does not contain the parent's u dict_u.

dir() function: View a list of the names of all the properties and methods within the object.

  • If dir: Returns the class attribute, method name.
  • Dir (object): Returns class properties, instance properties, and method names.

How can I solve this problem?

  1. Referring to flask:
conf = config[config_name]
app.update_config({key: getattr(conf, key) for key in dir(conf) if key.isupper()})
print(app.config.BASE_DIR)
  1. Add a class method to the base class Config and return its properties as a dictionary. Then use config_cls.to_dict() in app.update_config(). The difference is that before you passed in a config class, you now passed in a dictionary:
# config.py
import os

class Config:
    BASE_DIR = os.path.dirname(os.path.dirname(__file__))

    @staticmethod
    def init_app(app):
        pass

    # Add a class method that returns all uppercase class properties of a class
    @classmethod
    def to_dict(cls):
        return {key: getattr(cls, key) for key in dir(cls) if key.isupper()}


class DevConfig(Config):
    DEBUG = True
    ACCESS_LOG = False
    DB_URL = 'mysql://test:Tes@*.*.*.*:3306/test'


class ProConfig(Config):
    DEBUG = False
    ACCESS_LOG = False
    DB_URL = 'mysql://production:production@*.*.*.*:3306/production'


config = {
    'development': DevConfig,
    'production': ProConfig,

    'default': DevConfig
}

# app.py
from sanic import Sanic
form config import config

app = Sanic(__name__)
conf_name = 'development'
# Call the class method to_dict() of the config class
app.update_config(config[conf_name].to_dict())

In addition, in sanic's update_config(), the isClass() method is used to determine whether a class or an object is passed in.

def isclass(object):
    return isinstance(object, type)

Simply use isinstance(). In python, [Everything is an object].

  conf = DevConfig()
  print(isinstance(conf, object))	# True
  print(isinstance(conf(), object))	# True
  print(isinstance(conf, type)) 	# True
  print(isinstance(conf(), type))	# False

Conf, conf() are objects, but conf (class) is type, conf() (object) is not type.

Posted by herve on Sun, 19 Sep 2021 09:57:44 -0700