traitlets Python configuration system

repository·main·Indexed 20 days ago

https://github.com/ipython/traitlets

A pure Python library providing strong typing for object attributes (traits), dynamic default values, automatic validation/coercion, and change notifications. It serves as a core dependency for IPython and Jupyter. Key features include the HasTraits base class, @observe and @validate decorators, and a comprehensive configuration system utilizing Configurable, Application, and Config classes to manage application state via Python or JSON configuration files.

Tokens
20K
Snippets
77
Records
94
Agent score
70%

What's inside traitlets

  1. What is Traitlets?

    main

    Traitlets is a framework for Python classes that provides attributes with the following capabilities:

    • Type checking: Ensures attributes adhere to specified types.
    • Dynamic default values: Allows default values to be calculated at runtime.
    • 'On change' callbacks: Triggers specific logic automatically when an attribute value is updated.
    • Configuration machinery: A separate layer that enables loading values from configuration files or command-line arguments into traitlets.
  2. Reflect class inheritance in configuration

    main

    When using traitlets.config, you can mirror your Python class inheritance hierarchy in your configuration. If a subclass inherits from a parent class that uses config=True for its traits, the subclass will automatically pick up the configuration settings applied to the parent class.

    To implement this:

    1. Define your classes inheriting from Configurable.
    2. Set config=True on the traits you want to be configurable.
    3. In your configuration file, use the class name as a key to set values. Subclasses will inherit these values unless they are explicitly overridden.
    from traitlets.config import Application, Configurable
    from traitlets import Integer, Float, Unicode, Bool
    
    class Foo(Configurable):
        name = Unicode("fooname", config=True)
        value = Float(100.0, config=True)
    
    class Bar(Foo):
        name = Unicode("barname", config=True)
        othervalue = Integer(0, config=True)
    
    # In your configuration file:
    # c = get_config()
    # c.Foo.name = "bestname"
    # c.Bar.othervalue = 10
    
    # Resulting behavior:
    # - Bar().name will be "bestname" (inherited from Foo config)
    # - Bar().value will be 100.0 (class default)
    # - Bar().othervalue will be 10 (explicit Bar config)
  3. How to use dynamic default values

    main

    If a default value needs to be calculated at runtime based on the instance state, use the @default('{traitname}') decorator on a method. This method is called on the instance and must return the desired default value.

    Note: If a value is set for the trait before it is ever accessed, the dynamic default will not be calculated. In such cases, the old_value in callbacks will be the static default of the type (e.g., '' for Unicode) or traitlets.Undefined for types that only support dynamic defaults (like Instance).

    import getpass
    from traitlets import HasTraits, Unicode, default
    
    class Identity(HasTraits):
        username = Unicode()
    
        @default('username')
        def _username_default(self):
            return getpass.getuser()
  4. Core abstractions in the traitlets configuration system

    main

    The traitlets.config system is built on several key abstractions that work together to manage application settings:

    • Config: A dictionary-like object that holds configuration attributes and sub-configuration objects. It supports both dotted access (cfg.Foo.bar) and dictionary access (cfg['Foo']['bar']).
    • Application: A process (like the ipython CLI) that reads configuration files and command-line options to produce a master Config object. It typically provides a log attribute for centralized logging.
    • Configurable: A base class for application components. By tagging class-level traits with config=True, they become configurable via files or the command line. Instances are typically initialized with a config or parent argument.
    • SingletonConfigurable: A specialized Configurable for objects that should have a single canonical instance (e.g., Application). You can access the current instance using .instance() (e.g., app = Application.instance()).

    Key Principle: Configuration allows default values of class attributes to be controlled on a class-by-class basis. While all instances of a class share the same configuration by default, you can override values for specific instances.

  5. Use Configurable classes to manage application state

    main

    The Configurable class is the base for objects that can be configured via a Config object. By inheriting from Configurable, a class gains the ability to have its trait attributes set through a centralized configuration system. This is useful for managing settings across complex applications.

    Related specialized classes include:

    • SingletonConfigurable: For objects that should only have one instance.
    • LoggingConfigurable: For objects that require specific logging configuration integration.
  6. How to use Traitlets in your classes

    main

    To use trait attributes, your class must inherit from HasTraits. Traits are defined as class attributes using specific trait types (e.g., Unicode(), Integer(), Int()).

    from traitlets import HasTraits, Unicode
    
    class MyClass(HasTraits):
        name = Unicode()
  7. Design requirements of the traitlets configuration system

    main

    The traitlets.config system is designed to provide a robust, hierarchical configuration mechanism with the following capabilities:

    • Hierarchical Configuration: Supports nested configuration structures.
    • CLI Integration: Automates the process of overriding configuration file values with command-line options by linking CLI flags to specific attributes in the configuration hierarchy.
    • Python-based Configuration Files: Configuration files are valid Python code, allowing for:
      • Logic based on environment (OS, network, Python version).
      • Simple attribute access (e.g., Foo.Bar.Bam.name).
      • Importing attributes between configuration files.
      • Runtime type checking (e.g., distinguishing between 1 as an integer and '1' as a string).
    • Automated Distribution: Automatically provides configuration information to classes at runtime, removing the need for manual hierarchy traversal.
    • Dynamic Validation: Supports type checking and validation that works with Python's dynamic nature, allowing configuration to be defined even when the full hierarchy isn't known at startup.
  8. Configure Container Traits (List and Dict) via CLI

    main

    In traitlets 5.0+, container traits like List and Dict can be configured by repeating the argument key multiple times on the command line.

    • For Lists: myprogram -l a -l b results in ['a', 'b'].
    • For Dicts: myprogram -d a=5 -d b=10 results in {'a': 5, 'b': 10}.

    Note: When using Dict, you may need to specify the value trait to ensure correct type casting (e.g., converting the string '10' to an integer 10).

    $ examples/docs/container.py -x a -x b -y a=10 -y b=5
    x=['a', 'b']
    y={'a': 10, 'b': 5}
  9. Configure Application via Command-Line Arguments

    main

    You can override any configurable trait from the command line using the --Class.trait=value syntax. Command-line arguments take precedence over values loaded from configuration files.

    Syntax Examples:

    • Full specification: --InteractiveShell.autoindent=False
    • With spaces (v5.0+): --InteractiveShell.autoindent False

    Precedence: Application.cli_config (command-line) is merged over values read from configuration files during Application.load_config_file().

    $ ipython --InteractiveShell.autoindent=False --BaseIPythonApplication.profile='myprofile'
  10. Define new trait types by subclassing TraitType

    main

    To create a custom trait type in traitlets, you must subclass TraitType. When defining your subclass, you can implement the following components:

    • info_text (attribute): A short string describing the purpose of the trait.
    • default_value (attribute): An optional default value for the trait. Only provide this if a sensible default exists for your type.
    • validate(obj, value) (method): A method used to verify and/or coerce values.
      • obj: The instance to which the trait belongs.
      • value: The value being assigned.
      • Behavior: If the value is valid, return the value (optionally coerced to the desired type). If the value is invalid, raise a TraitError.
      • Tip: Use TraitType.error(obj, value) to raise descriptive errors indicating that the value does not match the required type.
    from traitlets import TraitType, TraitError
    
    class MyTrait(TraitType):
        info_text = "A description of my custom trait"
        default_value = 42
    
        def validate(self, obj, value):
            if not isinstance(value, int):
                raise self.error(obj, value)
            return value
  11. Populate trait metadata using the .tag() method

    main

    In Traitlets 4.1+, passing unrecognized keyword arguments to a TraitType constructor (like Int, Unicode, etc.) to populate metadata is deprecated. Instead, use the .tag() method on the trait instance. Additionally, the get_metadata() method is deprecated; access metadata directly via the .metadata attribute.

    # Deprecated approach
    x = Int(allow_none=True, sync=True)
    
    # Recommended approach
    x = Int(allow_none=True).tag(sync=True)
  12. Define attributes with type checking and dynamic defaults in HasTraits

    main

    To use traitlets, subclass traitlets.HasTraits. You can define attributes (traits) with specific types like Int, Unicode, or Dict. Traitlets provides automatic type checking on assignment and allows you to define dynamic default values using the @default decorator.

    Note that assigning a value of the wrong type will raise a TraitError.

    from traitlets import HasTraits, Int, Unicode, default
    import getpass
    
    class Identity(HasTraits):
        username = Unicode()
    
        @default("username")
        def _default_username(self):
            return getpass.getuser()
    
    class Foo(HasTraits):
        bar = Int()
    
    # This will raise a TraitError because '3' is a str, not an int
    foo = Foo(bar="3")