# Buggy code
def _get_best_study_config(self):
    metadata = {
        "best_trial_number": self.study.best_trial.number,
        "best_trial_evaluation": self.study.best_value,
    }

    pipeline_config = dict()
    for k, v in self.study.user_attrs.items():
        if k.startswith("pykeen_"):
            metadata[k[len("pykeen_") :]] = v
        elif k in {"metric"}:
            continue
        else:
            pipeline_config[k] = v

    for field in dataclasses.fields(self.objective):
        if (
            not field.name.endswith("_kwargs")
            and field.name
            not in {
                "training",
                "testing",
                "validation",
            }
        ) or field.name in {"metric"}:
            continue
        field_kwargs = getattr(self.objective, field.name)
        if field_kwargs:
            logger.debug(
                f"saving pre-specified field in pipeline config: {field.name}={field_kwargs}"
            )
            pipeline_config[field.name] = field_kwargs

    for k, v in self.study.best_params.items():
        sk, ssk = k.split(".")
        sk = f"{sk}_kwargs"
        if sk not in pipeline_config:
            pipeline_config[sk] = {}
        logger.debug(f"saving optimized field in pipeline config: {sk}.{ssk}={v}")
        pipeline_config[sk][ssk] = v

    for k in ("stopper", "stopper_kwargs"):
        if k in pipeline_config:
            v = pipeline_config.pop(k)
            metadata[f"_{k}_removed_comment"] = f"{k} config removed after HPO: {v}"

    stopped_epoch = self.study.best_trial.user_attrs.get(STOPPED_EPOCH_KEY)
    if stopped_epoch is not None:
        old_num_epochs = pipeline_config["training_kwargs"]["num_epochs"]
        metadata["_stopper_comment"] = (
            f"While the original config had {old_num_epochs},"
            f" early stopping will now switch it to {int(stopped_epoch)}"
        )
        pipeline_config["training_kwargs"]["num_epochs"] = int(stopped_epoch)
    return dict(metadata=metadata, pipeline=pipeline_config)


# Traceback (TypeError)
Traceback (most recent call last):
File "hpo_run.py", line 80, in <module>
run_hpo_search(args)
File "hpo_run.py", line 62, in run_hpo_search
hpo_pipeline_result.save_to_directory('study')
File "/home/user/anaconda3/envs/pykeen/lib/python3.8/site-packages/pykeen/hpo/hpo.py", line 327, in save_to_directory
json.dump(self._get_best_study_config(), file, indent=2, sort_keys=True)
File "/home/user/anaconda3/envs/pykeen/lib/python3.8/json/__init__.py", line 179, in dump
for chunk in iterable:
File "/home/user/anaconda3/envs/pykeen/lib/python3.8/json/encoder.py", line 431, in _iterencode
yield from _iterencode_dict(o, _current_indent_level)
File "/home/user/anaconda3/envs/pykeen/lib/python3.8/json/encoder.py", line 405, in _iterencode_dict
yield from chunks
File "/home/user/anaconda3/envs/pykeen/lib/python3.8/json/encoder.py", line 405, in _iterencode_dict
yield from chunks
File "/home/user/anaconda3/envs/pykeen/lib/python3.8/json/encoder.py", line 438, in _iterencode
o = _default(o)
File "/home/user/anaconda3/envs/pykeen/lib/python3.8/json/encoder.py", line 179, in default
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type TriplesFactory is not JSON serializable

# Fixed code
def _get_best_study_config(self):
    metadata = {
        "best_trial_number": self.study.best_trial.number,
        "best_trial_evaluation": self.study.best_value,
    }

    pipeline_config = dict()
    for k, v in self.study.user_attrs.items():
        if k.startswith("pykeen_"):
            metadata[k[len("pykeen_") :]] = v
        elif k in {"metric"}:
            continue
        else:
            pipeline_config[k] = v

    for field in dataclasses.fields(self.objective):
        field_value = getattr(self.objective, field.name)
        if not field_value:
            continue
        if field.name.endswith("_kwargs"):
            logger.debug(
                f"saving pre-specified field in pipeline config: {field.name}={field_value}"
            )
            pipeline_config[field.name] = field_value
        elif field.name in {"training", "testing", "validation"}:
            pipeline_config[field.name] = (
                field_value if isinstance(field_value, str) else USER_DEFINED_CODE
            )

    for k, v in self.study.best_params.items():
        sk, ssk = k.split(".")
        sk = f"{sk}_kwargs"
        if sk not in pipeline_config:
            pipeline_config[sk] = {}
        logger.debug(f"saving optimized field in pipeline config: {sk}.{ssk}={v}")
        pipeline_config[sk][ssk] = v

    for k in ("stopper", "stopper_kwargs"):
        if k in pipeline_config:
            v = pipeline_config.pop(k)
            metadata[f"_{k}_removed_comment"] = f"{k} config removed after HPO: {v}"

    stopped_epoch = self.study.best_trial.user_attrs.get(STOPPED_EPOCH_KEY)
    if stopped_epoch is not None:
        old_num_epochs = pipeline_config["training_kwargs"]["num_epochs"]
        metadata["_stopper_comment"] = (
            f"While the original config had {old_num_epochs},"
            f" early stopping will now switch it to {int(stopped_epoch)}"
        )
        pipeline_config["training_kwargs"]["num_epochs"] = int(stopped_epoch)
    return dict(metadata=metadata, pipeline=pipeline_config)