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

Enable dot notation in parameter expressions #44

Open
maldoinc opened this issue Dec 4, 2024 · 7 comments
Open

Enable dot notation in parameter expressions #44

maldoinc opened this issue Dec 4, 2024 · 7 comments
Labels
enhancement New feature or request good first issue Good for newcomers

Comments

@maldoinc
Copy link
Owner

maldoinc commented Dec 4, 2024

For cases where parameters are nested it should be possible to do Inject(expr="${foo.bar.baz}") to inject a value.

@maldoinc maldoinc added enhancement New feature or request good first issue Good for newcomers labels Dec 4, 2024
@Rmehta-sudo
Copy link

I am not entirely sure , but shouldn't using fstrings fix that?
eg. Inject(expr=f"{foo.bar.baz}")

@maldoinc
Copy link
Owner Author

maldoinc commented Dec 7, 2024

The requirement is slightly different as you won't have a reference to the parameters in the annotation.

Let's say you create a container as follows:

import wireup

container = wireup.create_container(
    parameters={
        "mailer": {"from": {"name": "foo", "email": "[email protected]"}}
    },
)

Currently, a service that only wants the "from email" will have to request "mailer" and extract the info it needs from it which is not optimal as it depends on more things than is necessary. It's fixable using a factory but is more code to write.

class SomeService:
    def __init__(self, mailer: ...):
        self.from = mailer["from"]["email"]

This makes it so that for configs with nested structures (anything that supports __getitem__), the container can inject the exact part of a nested object.

Ideally this should work for objects as well (say, mailer is a pydantic object) but it's not strictly necessary for now.

@Rmehta-sudo
Copy link

Thanks for the explanation!

Just to confirm , we want to add functionality so that nested dictionaries that are extracted from any object using functions like get_item() on the object , should allow for dot notation right?

One way could be to create a new class called DotDict which inherits from dictionaries and use it whenever a __get_item() returns a dictionary ?

For example:

class DotDict(dict):
  """Dictionary with dot notation access."""
  def __getattr__(self, attr):
      return self.get(attr)

and then for any get_item() function in any container or some class

# or anything like a container class
class container:          
  # some code of the class
  def __getitem__(self):
    # do something and store the dict in originalDict
    return DotDict(originalDict)

Would this not work?
If it will , could you please give an entire example of creating any one such object and show it's usage in the Inject(expr="${foo.bar.baz}") form so I can work on it ?
Thanks!

@maldoinc
Copy link
Owner Author

maldoinc commented Dec 8, 2024

It's even simpler than that.

What we want to do is for expressions only to split the requested parameter name on the dots and retrieve the value from the dict by traversing it.

So in the example above "mailer" expr returns the entire param. Whereas mailer.from.email returns [email protected].

In the parameterbag class there is the interpolate function which resolves string expressions. This doesn't affect the container itself just the parameterbag which is responsible for resolving parameter values.

https://github.com/maldoinc/wireup/blob/master/wireup/ioc/parameter.py#L87

@Rmehta-sudo
Copy link

Rmehta-sudo commented Dec 9, 2024

I'm sorry , but I am slightly confused about the exact expectations . Could you please give an actual example by creating an actual object/dict called foo with nested bar and baz attributes and then pass it into the Inject function and show what the error comes out to be.

I am unable to understand the link between create_container and Inject becuase interpolate function is defined in ParameterBag used in create_container whereas Inject function never uses create_container .

Apologies if this question seems too basic. I just want to ensure I grasp the concept correctly. Thanks!

@maldoinc
Copy link
Owner Author

So the container has several parts, the parameter bag in this case is responsible for providing it with parameter values.

When the container encounters something like Inject(param=x) or Inject(expr=x), it will call the get method parameter bag with the value x, eg: self.params.get(x). The get method's only parameter is either a string which is when we request a param by name, or a templated string which is used for expressions.

Once that value is returned the parameterbag's job is done.

The issue here is about changing how the container deals with templated strings.

All calls of Inject(expr=x) are translated to ParameterBag.get(TemplatedString(x)).

Referring to the above container example with the mailer param. "mailer": {"from": {"name": "foo", "email": "[email protected]"}}

ParameterBag.get(TemplatedString(x)) should result in the entire dict value being injected. However if the template contains dots then we should traverse the dict's value and fetch the value of that key.

  • ParameterBag.get(TemplatedString("mailer.from")) returns {"name": "foo", "email": "[email protected]"}
  • ParameterBag.get(TemplatedString("mailer.from.email")) returns "[email protected]".

Right now the container will only look for parameters in the keys, the change is about it being able to navigate the values and inject specific parts of it denoted with this dot syntax you're already familiar.

@maldoinc
Copy link
Owner Author

Hi, are you still interested in working on this? It's a lot easier than it sounds and entirely isolated in one function of the parameter bag. Let me know if the above helped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

2 participants