Box
Box copied to clipboard
cannot froze a subclass
To continue on my previous issue, I would like to froze a sublcass but I can't manage to make it work. As you manage to make it work in BoxList
what am I missing ?
class toto(Box):
def __init__(self, valeur):
super(Box, self).__init__({"valeur": {"valeur": valeur}}, frozen_box=True)
truc = toto("truc")
truc.valeur = "toto"
The root problem is you're calling the super of Box
which is dict
instead super(toto, self)
Then you're run into a recursion issue if you always try to set values via the super __init__
inside the __init__
You probably want something more like:
from box import Box
class toto(Box):
def __init__(self, *args, **kwargs):
super().__init__(*args, frozen_box=kwargs.pop("frozen_box", True), **kwargs)
truc = toto({"valeur": "me"}, frozen_box=True)
truc.valeur = "toto" # box.exceptions.BoxError: Box is frozen
Thank you very much. So let's expand a bit my question. My final goal is to create a translator object. The user should simply provide a language and I programmatically fetch and sanitize the dictionary before serving it as a Box of Box, that's why I'm interested in using a subclass that does not expose **kwargs
and *args
.
modifying your small example I can do this :
class toto(Box):
def __init__(self, valeur):
dict_ = {"valeur": "me"}
super().__init__(dict_, frozen_box=True)
which work as expected. When I try to expand it to my real Translator object on the other end I get the foollowing error:
TypeError: init() got an unexpected keyword argument 'default_box'
Do you have any clue where it's coming from ?
Could it be related to the fact that I'm not overwritting __new__
?
Note that if I'm slighty changing the following code doing:
super(Box, self).__init__(dict(**private_keys, **ms_boxes), frozen_box=True)
It works but the "root" box is not frozen
here is the skeleton of the class to help you understand what I try to do :
class Translator(Box):
"""
The translator is a Python Box of boxes. It reads 2 Json files, the first one being the source language (usually English) and the second one the target language.
It will replace in the source dictionary every key that exist in both json dictionaries. Following this procedure, every message that is not translated can still be accessed in the source language.
To access the dictionary keys, instead of using [], you can simply use key name as in an object ex: translator.first_key.secondary_key.
There are no depth limits, just respect the snake_case convention when naming your keys in the .json files.
5 internal keys are created upon initialization (there name cannot be used as keys in the translation message):
- (str) _default : the default locale of the translator
- (str) _targeted : the initially requested language. Use to display debug information to the user agent
- (str) _target : the target locale of the translator
- (bool) _match : if the target language match the one requested one by user, used to trigger information in appBar
- (str) _folder : the path to the l10n folder
Args:
json_folder (str | pathlib.Path): The folder where the dictionaries are stored
target (str, optional): The language code (IETF BCP 47) of the target lang (it should be the same as the target dictionary). Default to either the language specified in the parameter file or the default one.
default (str, optional): The language code (IETF BCP 47) of the source lang. default to "en" (it should be the same as the source dictionary)
"""
_protected_keys = ["find_target", "search_key", "sanitize", "_update", "missing_keys", "available_locales", "merge_dict", "delete_empty"] + dir(Box)
"keys that cannot be used as var names as they are protected for methods"
def __init__(self, json_folder, target=None, default="en"):
# the name of the 5 variables that cannot be used as init keys
FORBIDDEN_KEYS = ["_folder", "_default", "_target", "_targeted", "_match"]
# init the box with the folder
folder = Path(json_folder)
# reading the default dict
default_dict = self.merge_dict(folder / default)
# create a dictionary in the target language
targeted, target = self.find_target(folder, target)
target = target or default
target_dict = self.merge_dict(folder / target)
# evaluate the matching of requested and obtained values
match = targeted == target
# create the composite dictionary
ms_dict = self._update(default_dict, target_dict)
# check if forbidden keys are being used, this will raise an error if any
[self.search_key(ms_dict, k) for k in FORBIDDEN_KEYS]
# unpack the dict as encaplsulated Boxes
ms_json = json.dumps(ms_dict)
ms_boxes = json.loads(ms_json, object_hook=lambda d: Box(**d, frozen_box=True))
private_keys = {"_folder": str(folder),"_default": default,"_targeted": targeted,"_target": target,"_match": match}
super().__init__(dict(**private_keys, **ms_boxes), frozen_box=True)
There are several points within the box itself it may have to recreate a new instance of the same class and pass down the current set of settings, so you will always have to accept **kwargs
and pass them through the super
call.
I tried the following:
def __init__(self, json_folder, *args, target=None, default="en", **kwargs):
# do stuff
super().__init__(*args, **private_keys, **ms_boxes, frozen_box=kwargs.pop("frozen_box", True), **kwargs)
and now I have a Path error:
TypeError: expected str, bytes or os.PathLike object, not Box
Digging into it, it seems that init is launched twice and the second time json_folder
argument becomes a Box. If I go back to calling super(Box, self)
the problem disappears but the root Box is still unfrozen.