Skip to content

Commit

Permalink
Automatic migration of deprecated values
Browse files Browse the repository at this point in the history
  • Loading branch information
kageurufu committed Dec 11, 2024
1 parent 136149b commit fb36e36
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 156 deletions.
140 changes: 83 additions & 57 deletions klippy/configfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ def _get_wrapper(
self.access_tracking[acc_id] = default
return default
raise error(
"Option '%s' in section '%s' must be specified"
% (option, self.section)
f"Option '{option}' in section '{self.section}' must be specified"
)
if parser is float:
parser = mathutil.safe_float
Expand All @@ -59,30 +58,25 @@ def _get_wrapper(
raise
except:
raise error(
"Unable to parse option '%s' in section '%s'"
% (option, self.section)
f"Unable to parse option '{option}' in section '{self.section}'"
)
if note_valid:
self.access_tracking[(self.section.lower(), option.lower())] = v
if minval is not None and v < minval:
raise error(
"Option '%s' in section '%s' must have minimum of %s"
% (option, self.section, minval)
f"Option '{option}' in section '{self.section}' must have minimum of {minval}"
)
if maxval is not None and v > maxval:
raise error(
"Option '%s' in section '%s' must have maximum of %s"
% (option, self.section, maxval)
f"Option '{option}' in section '{self.section}' must have maximum of {maxval}"
)
if above is not None and v <= above:
raise error(
"Option '%s' in section '%s' must be above %s"
% (option, self.section, above)
f"Option '{option}' in section '{self.section}' must be above {above}"
)
if below is not None and v >= below:
raise self.error(
"Option '%s' in section '%s' must be below %s"
% (option, self.section, below)
f"Option '{option}' in section '{self.section}' must be below {below}"
)
return v

Expand Down Expand Up @@ -143,8 +137,7 @@ def getchoice(self, option, choices, default=sentinel, note_valid=True):
c = self.get(option, default, note_valid=note_valid)
if c not in choices:
raise error(
"Choice '%s' for option '%s' in section '%s'"
" is not a valid choice" % (c, option, self.section)
f"Choice '{c}' for option '{option}' in section '{self.section}' is not a valid choice"
)
return choices[c]

Expand All @@ -169,8 +162,7 @@ def lparser(value, pos):
res = [parser(p) for p in parts]
if count is not None and len(res) != count:
raise error(
"Option '%s' in section '%s' must have %d elements"
% (option, self.section, count)
f"Option '{option}' in section '{self.section}' must have {count} elements"
)
return tuple(res)

Expand Down Expand Up @@ -239,22 +231,35 @@ def get_prefix_options(self, prefix):
if o.startswith(prefix)
]

def deprecate(self, option, value=None):
def deprecate(self, option, value=None, replace_with=None):
if not self.fileconfig.has_option(self.section, option):
return

if value is None:
msg = "Option '%s' in section '%s' is deprecated." % (
option,
self.section,
msg = (
f"Option '{option}' in section '{self.section}' is deprecated."
)
else:
msg = "Value '%s' in option '%s' in section '%s' is deprecated." % (
value,
option,
self.section,
)
msg = f"Value '{value}' in option '{option}' in section '{self.section}' is deprecated."

if replace_with:
conflicts = [
new_option
for new_option in replace_with
if self.fileconfig.has_option(self.section, new_option)
]
if conflicts:
raise self.error(
f"Option '{option}' in section '{self.section}' is deprecated"
f" and has already been replaced with {', '.join(conflicts)}."
)
for new_option, new_value in replace_with.items():
self.fileconfig.set(self.section, new_option, new_value)
msg += " Replaced with " + ",".join(replace_with.keys())
pconfig = self.printer.lookup_object("configfile")
pconfig.deprecate(self.section, option, value, msg)
pconfig.deprecate(
self.section, option, value, msg, replace_with=replace_with
)


AUTOSAVE_HEADER = """
Expand All @@ -269,6 +274,7 @@ def __init__(self, printer):
self.printer = printer
self.autosave = None
self.deprecated = {}
self.deprecations = {}
self.runtime_warnings = []
self.deprecate_warnings = []
self.status_raw_config = {}
Expand All @@ -295,7 +301,7 @@ def _read_config_file(self, filename):
data = f.read()
f.close()
except:
msg = "Unable to open config file %s" % (filename,)
msg = f"Unable to open config file {filename}"
logging.exception(msg)
raise error(msg)
return data.replace("\r\n", "\n")
Expand All @@ -310,8 +316,7 @@ def _find_autosave_data(self, data):
# Check for errors and strip line prefixes
if "\n#*# " in regular_data:
logging.warning(
"Can't read autosave from config file"
" - autosave state corrupted"
"Can't read autosave from config file - autosave state corrupted"
)
return data, ""
out = [""]
Expand All @@ -332,39 +337,55 @@ def _find_autosave_data(self, data):
comment_r = re.compile("[#;].*$")
value_r = re.compile("[^A-Za-z0-9_].*$")

def _strip_duplicates(self, data, config):
def _strip_duplicates(self, data, config, replacements=None):
# Comment out fields in 'data' that are defined in 'config'
lines = data.split("\n")
result = []
section = None
is_dup_field = False
for lineno, line in enumerate(lines):
replacement = None
for line in lines:
result.append(line)
pruned_line = self.comment_r.sub("", line).rstrip()
if not pruned_line:
continue
if pruned_line[0].isspace():
if is_dup_field:
lines[lineno] = "#" + lines[lineno]
result[-1] = "#" + result[-1]
continue
if is_dup_field and replacement:
for key, value in replacement.items():
if not isinstance(value, str):
value = str(value) # TODO: Proper serialization...
if "\n" in value:
result.insert(-1, key + ":")
for value_line in value.splitlines(False):
result.insert(-1, "\t" + value_line)
else:
result.insert(-1, f"{key}: {value}")
replacement = None
is_dup_field = False
if pruned_line[0] == "[":
section = pruned_line[1:-1].strip()
continue
field = self.value_r.sub("", pruned_line)
if replacements and (section, field) in replacements:
is_dup_field = True
replacement = replacements[(section, field)]
result[-1] = "#" + result[-1]
if config.fileconfig.has_option(section, field):
is_dup_field = True
lines[lineno] = "#" + lines[lineno]
return "\n".join(lines)
result[-1] = "#" + result[-1]

return "\n".join(result)

def _parse_config_buffer(self, buffer, filename, fileconfig):
if not buffer:
return
data = "\n".join(buffer)
del buffer[:]
sbuffer = io.StringIO(data)
if sys.version_info.major >= 3:
fileconfig.read_file(sbuffer, filename)
else:
fileconfig.readfp(sbuffer, filename)
fileconfig.read_file(sbuffer, filename)

def _resolve_include(
self, source_filename, include_spec, fileconfig, visited
Expand All @@ -378,7 +399,7 @@ def _resolve_include(
include_filenames = glob.glob(include_glob)
if not include_filenames and not glob.has_magic(include_glob):
# Empty set is OK if wildcard but not for direct file reference
raise error("Include file '%s' does not exist" % (include_glob,))
raise error(f"Include file '{include_glob}' does not exist")
include_filenames.sort()
for include_filename in include_filenames:
include_data = self._read_config_file(include_filename)
Expand All @@ -390,7 +411,7 @@ def _resolve_include(
def _parse_config(self, data, filename, fileconfig, visited):
path = os.path.abspath(filename)
if path in visited:
raise error("Recursive include of config file '%s'" % (filename))
raise error(f"Recursive include of config file '{filename}'")
visited.add(path)
lines = data.split("\n")
# Buffer lines between includes and parse as a unit so that overrides
Expand Down Expand Up @@ -460,8 +481,7 @@ def check_unused_options(self, config, error_on_unused):
if section not in valid_sections and section not in objects:
if error_on_unused:
raise error(
"Section '%s' is not a valid config section"
% (section,)
f"Section '{section}' is not a valid config section"
)
else:
self.unused_sections.append(section)
Expand All @@ -470,8 +490,7 @@ def check_unused_options(self, config, error_on_unused):
if (section, option) not in access_tracking:
if error_on_unused:
raise error(
"Option '%s' is not valid in section '%s'"
% (option, section)
f"Option '{option}' is not valid in section '{section}'"
)
else:
self.unused_options.append((section, option))
Expand All @@ -493,8 +512,17 @@ def runtime_warning(self, msg):
self.runtime_warnings.append(res)
self.status_warnings = self.runtime_warnings + self.deprecate_warnings

def deprecate(self, section, option, value=None, msg=None):
def deprecate(
self, section, option, value=None, msg=None, replace_with=None
):
self.deprecated[(section, option, value)] = msg
if replace_with:
pending_section = self.status_save_pending.setdefault(section, {})
pending_section[option] = None
for new_opt, new_value in replace_with.items():
pending_section[new_opt] = new_value
self.deprecations[(section, option)] = replace_with
self.save_config_pending = True

def warn(self, type, msg, section=None, option=None, value=None):
res = {
Expand Down Expand Up @@ -554,7 +582,7 @@ def set(self, section, option, value):
pending[section][option] = svalue
self.status_save_pending = pending
self.save_config_pending = True
logging.info("save_config: set [%s] %s = %s", section, option, svalue)
logging.info(f"save_config: set [{section}] {option} = {svalue}")

def remove_section(self, section):
if self.autosave.fileconfig.has_section(section):
Expand Down Expand Up @@ -584,9 +612,8 @@ def _disallow_include_conflicts(self, regular_data, cfgname, gcode):
)
if included_value != autosave_value:
msg = (
"SAVE_CONFIG section '%s' option '%s' value '%s' conflicts "
"with included value '%s' "
% (section, option, autosave_value, included_value)
f"SAVE_CONFIG section '{section}' option '{option}' value '{autosave_value}' conflicts "
f"with included value '{included_value}'"
)
raise gcode.error(msg)

Expand All @@ -610,9 +637,7 @@ def _write_backup(self, cfgpath, cfgdata, gcode):
backup_path = backupdir + "/" + cfgname + datestr
if cfgpath.endswith(".cfg"):
backup_path = backupdir + "/" + cfgname[:-4] + datestr + ".cfg"
logging.info(
"SAVE_CONFIG to '%s' (backup in '%s')", cfgpath, backup_path
)
logging.info(f"SAVE_CONFIG to '{cfgpath}' (backup in '{backup_path}')")
try:
# Read the current config into the backup before making changes to
# the original file
Expand Down Expand Up @@ -657,9 +682,7 @@ def _save_includes(self, cfgpath, data, visitedpaths, gcode):
if not include_filenames and not glob.has_magic(include_glob):
# Empty set is OK if wildcard but not for direct file
# reference
raise error(
"Include file '%s' does not exist" % (include_glob,)
)
raise error(f"Include file '{include_glob}' does not exist")
include_filenames.sort()
# Read the include files and check them against autosave data.
# If autosave data overwites anything we'll update the file
Expand All @@ -673,8 +696,9 @@ def _save_includes(self, cfgpath, data, visitedpaths, gcode):
)

include_postdata = self._strip_duplicates(
include_predata, self.autosave
include_predata, self.autosave, self.deprecations
)

# Only write and backup data that's been changed
if include_predata != include_postdata:
self._write_backup(
Expand All @@ -701,7 +725,9 @@ def cmd_SAVE_CONFIG(self, gcmd):
msg = "Unable to parse existing config on SAVE_CONFIG"
logging.exception(msg)
raise gcode.error(msg)
regular_data = self._strip_duplicates(regular_data, self.autosave)
regular_data = self._strip_duplicates(
regular_data, self.autosave, self.deprecations
)

if get_danger_options().autosave_includes:
self._save_includes(cfgname, data, set(), gcode)
Expand Down
16 changes: 7 additions & 9 deletions klippy/extras/danger_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,14 @@ def __init__(self, config):
"homing_elapsed_distance_tolerance", 0.5, minval=0.0
)

temp_ignore_limits = False
if config.getboolean("temp_ignore_limits", None) is None:
adc_ignore_limits = config.getboolean("adc_ignore_limits", None)
if adc_ignore_limits is not None:
config.deprecate("adc_ignore_limits")
temp_ignore_limits = adc_ignore_limits
adc_ignore_limits = config.getboolean("adc_ignore_limits", None)
if adc_ignore_limits is not None:
config.deprecate(
"adc_ignore_limits",
replace_with={"temp_ignore_limits": adc_ignore_limits},
)

self.temp_ignore_limits = config.getboolean(
"temp_ignore_limits", temp_ignore_limits
)
self.temp_ignore_limits = config.getboolean("temp_ignore_limits", False)

self.autosave_includes = config.getboolean("autosave_includes", False)
self.bgflush_extra_time = config.getfloat(
Expand Down
Loading

0 comments on commit fb36e36

Please sign in to comment.