Configurations

CI codecov

Configurations & Options made easy.

Installation

Configurations is a   Julia Language   package. To install Configurations, please open Julia's interactive session (known as REPL) and press ] key in the REPL to use the package mode, then type the following command

pkg> add Configurations

Usage

This package provides a macro @option to let you define structs to represent options/configurations, and serialize between different option/configuration file format.

Configurations.@optionMacro
@option [alias::String] <struct def>

Define an option struct type. This will auto-generate methods that parse a given Dict{String} object (the keys must be of type String) into an instance of the struct type you defined. One can use alias string to distinguish multiple possible option type for the same field.

Special Types

  • Maybe{T}: this type is equivalent to Union{Nothing, T} and is treated specially in @option, it will always have a default value of nothing if not specified.
  • Reflect: this type is treated specially to allow one to use a field to store the corresponding type information.
Configurations 0.16

from v0.16.0 Configurations stops overloading the Base.show method for you, if you need pretty printing of your option types, consider overloading the Base.show(io::IO, mime::MIME, x) method to pprint_struct(io, mime, x) provided by GarishPrint

Configurations 0.12

from v0.12.0 the field alias feature is removed due to the syntax conflict with field docstring. Please refer to #17.

Example

One can define option type via @option macro with or without an alias.

julia> "Option A"
       @option "option_a" struct OptionA
           name::String
           int::Int = 1
       end

julia> "Option B"
       @option "option_b" struct OptionB
           opt::OptionA = OptionA(;name = "Sam")
           float::Float64 = 0.3
       end
julia> option = from_dict(OptionB, d)
OptionB(;
    opt = OptionA(;
        name = "Roger",
        int = 2,
    ),
    float = 0.33,
)

when there are multiple possible option type for one field, one can use the alias to distinguish them

julia> @option struct OptionD
           opt::Union{OptionA, OptionB}
       end

julia> d1 = Dict{String, Any}(
               "opt" => Dict{String, Any}(
                   "option_b" => d
               )
           );

julia> from_dict(OptionD, d1)
OptionD(;
    opt = OptionB(;
        opt = OptionA(;
            name = "Roger",
            int = 2,
        ),
        float = 0.33,
    ),
)
source

Frequently Asked Questions

  • When should I use this package?

When you have a lot settings/preferences/keyword arguments for a package or a function, or you need to validate a JSON schema, a REST API for your web server. A similar package in Python is pydantic in Python, but this package only provides the basic feature, a pydantic compatible package will be developed in the future in KungIChi.

A common type of code is instead of writing many keyword arguments, like foo(;kw_a=1, kw_b=2, kw_c, ...), wrap them in an option type

@option struct FooOptions
    kw_a::Int = 1
    kw_b::Int = 2
    # ...
end

foo(x, y;kw...) = foo(x, y, FooOptions(;kw...))
foo(x, y, options::FooOptions) = #= actual implementation =#

this will make your keyword argument easy to read and serialize with readable markup language like TOML.

  • Why Configurations only supports TOML?

This is not true, Configurations supports converting a dictionary type (subtype of AbstractDict{String}) to option types defined by @option. The reason why TOML is supported by default is because Julia is shipped with a TOML parser already, so we can support TOML without adding extra dependency. And depending on other format parsers such as YAML, JSON etc. will cause an extra loading latency that is not necessary for most of the users who is fine with just TOML.

On the other hand, Configurations aims to be lightweight because it is used by latency sensitive packages like Comonicon. We will put other features into KungIChi in the future (it is still work-in-progress).

the @option macro provides the functionality of reflection in compile time, e.g we support type alias and default value reflection. These feature is not implementable without macros.

  • Why not just use a supertype but a macro?

besides the reason in the previous question, for a specific project, we can write a supertype and implement a set of generic interface, which is fine. But as a package, we need to make things composable and generic, thus, we do not want to block users from defining their own supertype. In this package, we use traits instead of supertypes, this makes things composable, e.g you can use the option types defined in Pluto as part of your own option types.

  • What is the difference between this package and Preferences

Preferences aims to provide a mechanism of reading package preferences that works with the package manager Pkg, but this package aims to provide a mechnism to read a setting/preference to Julia structs. Thus these two are completely orthogonal packages and they can work together.

  • What is the difference between this package and StructTypes

StructTypes is mainly used to provide a standard interface to parse dict-like data to a Julia struct via traits to make parsing faster, but this package aims to support the mapping between a dict-like data and a specific kind of Julia struct defined by @option which provides limited semantic that is not as general as a normal Julia struct (it is closer to Base.@kwdef semantic). And we have plans of supporting StructTypes traits by default once JuliaData/StructTypes#53 is figured out.

License

MIT License