Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cannot froze a subclass #223

Open
12rambau opened this issue Jun 9, 2022 · 4 comments
Open

cannot froze a subclass #223

12rambau opened this issue Jun 9, 2022 · 4 comments
Labels

Comments

@12rambau
Copy link

12rambau commented Jun 9, 2022

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"
@cdgriffith
Copy link
Owner

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

@12rambau
Copy link
Author

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)

@cdgriffith
Copy link
Owner

cdgriffith commented Jun 10, 2022

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.

@12rambau
Copy link
Author

12rambau commented Jun 14, 2022

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants