Best way to store package specific user settings?

I want to add persistent theming back to Makie and I wanted to get a feel for what methods are out there and what the tradeoffs of different approaches are.

One consideration is where to store settings files. Should we just add a folder under .julia or maybe even make a .makie folder in the home directory? That’s how matplotlib does it for example. Or use scratch spaces maybe? (Haven’t used those yet)

The other question is what format people use to store settings. Yaml, toml, json, just a jl file? I can imagine that we want users to set parameters that are easier to write in a bit of julia code, but is it good practice to just eval some settings file into the current module at package load time? I mean only the user can edit this, so it seems fine in that regard, but could also cause unexpected results.

I’d be happy for experiences you had with your own packages.

GitHub - JuliaPackaging/Preferences.jl: Project Preferences Package might be worth a try?

1 Like

That looks nice on first glance. But this would not work with any preferences set as Julia code of course. Unless one evals strings that are stored, hmm. For example, define a range of colors with LA () from Colors.jl or something like that.

Yeah, you wouldn’t be able to store arbitrary code in those preferences unless you want to go the eval route. I guess that could be seen as a pro or con really :smiley:

1 Like

You could serialize and base64-encode your Julia object as a string s and then deserialize it when loading the preference:

julia> using Colors

julia> cols = range(colorant"red", stop=colorant"green", length=15)
15-element Array{RGB{N0f8},1} with eltype RGB{FixedPointNumbers.N0f8}:
 RGB{N0f8}(1.0,0.0,0.0)
 RGB{N0f8}(0.929,0.035,0.0)
 RGB{N0f8}(0.859,0.071,0.0)
 RGB{N0f8}(0.784,0.11,0.0)
 RGB{N0f8}(0.714,0.145,0.0)
 RGB{N0f8}(0.643,0.18,0.0)
 RGB{N0f8}(0.573,0.216,0.0)
 RGB{N0f8}(0.502,0.251,0.0)
 RGB{N0f8}(0.427,0.286,0.0)
 RGB{N0f8}(0.357,0.322,0.0)
 RGB{N0f8}(0.286,0.357,0.0)
 RGB{N0f8}(0.216,0.392,0.0)
 RGB{N0f8}(0.141,0.431,0.0)
 RGB{N0f8}(0.071,0.467,0.0)
 RGB{N0f8}(0.0,0.502,0.0)

julia> using Serialization, Base64

julia> s = base64encode(sprint(serialize, cols))
"N0pMDwQAAAAVEAEDUkdCHws/MwtstsumuGBahFn3AqA9AQpDb2xvclR5cGVzRAEAAAAQAQZOb3JtZWQfC5Pqbom3ecWQolx9SheMxFMBEUZpeGVkUG9pbnROdW1iZXJzRAIAAAAAA+fu/wAA7QkA2xIAyBwAtiUApC4AkjcAgEAAbUkAW1IASVsAN2QAJG4AEncAAIAA"

julia> deserialize(IOBuffer(base64decode(s)))
15-element Array{RGB{N0f8},1} with eltype RGB{FixedPointNumbers.N0f8}:
 RGB{N0f8}(1.0,0.0,0.0)
 RGB{N0f8}(0.929,0.035,0.0)
 RGB{N0f8}(0.859,0.071,0.0)
 RGB{N0f8}(0.784,0.11,0.0)
 RGB{N0f8}(0.714,0.145,0.0)
 RGB{N0f8}(0.643,0.18,0.0)
 RGB{N0f8}(0.573,0.216,0.0)
 RGB{N0f8}(0.502,0.251,0.0)
 RGB{N0f8}(0.427,0.286,0.0)
 RGB{N0f8}(0.357,0.322,0.0)
 RGB{N0f8}(0.286,0.357,0.0)
 RGB{N0f8}(0.216,0.392,0.0)
 RGB{N0f8}(0.141,0.431,0.0)
 RGB{N0f8}(0.071,0.467,0.0)
 RGB{N0f8}(0.0,0.502,0.0)

Just store the component values as vectors. I would go with TOML, not that it is a standard library.

Thanks for pointing to Preferences.jl @mike. That compile-time preference thing looks very promising. I might use it to get a cairo-only version of InsepctDR.jl (plotting) running on machines without displays (where Gtk fails to build)!

I still would rather have get a dual-package repository going where one package is Cairo-only, and the other provides the Gtk GUI, but Preferences.jl might be a good “in the meantime” thing.

I’ve struggled with this issue with many of my packages as well. Here’s what I did with InspectDR.jl:

Specify defaults in ~/.julia/config/startup.jl

InspectDR.jl allows users to tailor default settings using a Dict called Main.DEFAULTS_INSPECTDR. The intent is to get users to specify these session-specific settings in their ~/.julia/config/startup.jl file:

DEFAULTS_INSPECTDR = Dict(
	:droppoints => :always, #{:always, :never, :noglyph, :hasline}
	:fontscale => 2,
	:fontname => "DejaVu Serif",
	:halloc_legend => 200,
	:notation_x => :SI, #ex: m, μ, n, p, ...
	:notation_y => :ENG, #Always powers of 3, unlike :SCI
	:rendersvg => false,
	:length_tickmajor => 10,
	:length_tickminor => 5,
	#[...]
)

Settings read @ package initialization

  • Settings are read in upon the initialization of InspectDR (__init__() function).
  • Settings are read into a module level (“global”) “defaults::Defaults” variable.
mutable struct Defaults
	rendersvg::Bool #Might want to dissalow SVG renderings for performance reasons
	xaxiscontrol_visible::Bool
	pointdropmatrix::PointDropMatrix
	colorscale::ColorScale #Default used for heatmaps

	plotlayout::PlotLayout #Can tweak pretty much everything here
	mplotlayout::MultiplotLayout
end
Defaults() = Defaults(
	false, false, PDM_NEVER, ColorScale(),
	PlotLayout(PREDEFAULTS), MultiplotLayout(PREDEFAULTS)
)

:+1: Advantage

Users don’t have to import InspectDR from ~/.julia/config/startup.jl in order to directly manipulate the defaults structure (which they could do).

This is extremely important to me - because I don’t want users to have to load InspectDR every time they start Julia: No need to slow down Julia if they aren’t going to use my module.

:-1: Disadvantages

  • You need to write specific code to read through the settings and write to the defaults::Defaults module-level variable.
  • Users don’t get to use the facilities/structures provided by my module. The option Dict() has to stick to base Julia types (or types from very lightweight modules).

Ex: fonts
Using the Dict() system, users cannot even specify default font using a simple Font(12, "Serif") object - because that is defined in InspectDR itself.

But I thought of a potentially more interesting when I saw your post:

Requires.jl solution

Requires.jl is fairly lightweight, so it seems reasonable to require users to import Requires.jl in their ~/.julia/config/startup.jl file to get package-specific settings working:

(~/.julia/config/startup.jl):

using Requires
@require DSP="717857b8-e6f2-59f4-9121-6e50c889abd2" begin
	@show DSP.conv([1,2],[1,2])

	#Configure your module here!
end

Alternatively, users could push all the settings code to a separate file:

@require DSP="717857b8-e6f2-59f4-9121-6e50c889abd2" include("DSPconfig.jl")

:+1: Advantages

  • Fast.
  • Simple.
  • Straightforward.
  • Users get to actually use the structures your module provides.

:-1: Disadvantages

  • Not sure yet.
2 Likes