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

feat: add config sources #20

Closed
wants to merge 3 commits into from
Closed

feat: add config sources #20

wants to merge 3 commits into from

Conversation

Kyle-Verhoog
Copy link
Member

Add support for setting and storing configuration from additional sources. This enables a setting to have values coming from a default, environment or programmatic source.

The default priority of the sources is (lowest to highest):

  1. Default
  2. Environment
  3. Programmatic

@Kyle-Verhoog Kyle-Verhoog changed the title Add config sources feat: add config sources Aug 29, 2023
@Kyle-Verhoog Kyle-Verhoog marked this pull request as draft August 29, 2023 21:49
@Kyle-Verhoog Kyle-Verhoog force-pushed the add-sources branch 2 times, most recently from 2a35499 to dbb9f49 Compare August 29, 2023 22:00
Add support for setting and storing configuration from additional
sources. This enables a setting to have values coming from a default,
environment and programmatic sources.

The default priority of the sources is (lowest to highest):

1. Default
2. Environment
3. Programmatic

The active source can be retrieved using a new method: `get_source()`:

```python
class Config:
        foo = Env.var(int, "FOO", default=0)

cfg = Config()
assert cfg.foo == 0
assert cfg.source_type("foo") == "default"

os.environ["FOO"] = 123
cfg = Config()
assert cfg.foo == 123
assert cfg.source_type("foo") == "environment"
```
@Kyle-Verhoog Kyle-Verhoog force-pushed the add-sources branch 4 times, most recently from 389c77c to 9354c1b Compare October 26, 2023 00:43
envier/env.py Outdated
Comment on lines 312 to 315
def __setattr__(self, name, value):
if name != "_items":
self._items[name].set_source("programmatic", value)
super(Env, self).__setattr__(name, value)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the general idea of keeping track of how a value was set. I just wonder if we could make use of descriptors and dynamic classes here to tidy the logic a bit. For example:

from enum import Enum


class Source(Enum):
    DEFAULT = 0
    OVERRIDE = 1


class VarInstance:
    def __init__(self, *args, **kwargs):
        try:
            super().__init__(*args, **kwargs)
        except Exception:
            pass
        self.source = Source.DEFAULT

    # Add support for internal mutation
    def append(self, *args, **kwargs):
        self.source = Source.OVERRIDE
        return super().append(*args, **kwargs)


def make_var_instance(_type):
    return type(f"VarInstance[{_type.__name__}]", (VarInstance, _type), {})


# These should be built on demand
IntVarInstance = make_var_instance(int)
ListVarInstance = make_var_instance(list)

# Descriptor wrapper to keep track of the source
class Var:
    def __init__(self, value):
        self._value = value
        self._source = None

    def __get__(self, obj, objtype=None):
        return self._value

    def __set__(self, obj, value):
        self._value = IntVarInstance(value)
        self._value.source = Source.OVERRIDE


class Config:
    var = Var(IntVarInstance(10))
    lst = Var(ListVarInstance([1, 2, 3]))


config = Config()
assert config.var == 10, config.var
assert config.var.source == Source.DEFAULT
assert config.lst == [1, 2, 3]
assert config.lst.source == Source.DEFAULT

config.var = 20
config.lst.append(4)
assert config.var == 20
assert config.var.source == Source.OVERRIDE
assert config.lst == [1, 2, 3, 4]
assert config.lst.source == Source.OVERRIDE

IIUC, one thing that this change doesn't handle is the case of internal mutation. That is, if a list is mutated programmatically, we don't set the source accordingly (should we?).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@P403n1x87 is this something you would like to do right now or can we leave it for a future refactor? I prefer the simplicity at the moment since we're still fleshing out the feature

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah it would be hard for me to give an "objective" answer here, as TBH I think the descriptor + dynamic types approach is simpler and leaner in my head (and that's why I proposed it). So my honest answer is that I'd prefer we went with this other approach, if we can make it work. Perhaps you might want to get some more eyes on this PR from e.g. the guild so that you could have more opinions besides my very polarised one 🙂 .

envier/env.py Outdated
@@ -190,6 +199,37 @@ def __call__(self, env):
return value


ConfigSourceType = Literal["default", "environment", "programmatic"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we make this an Enum?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I opted for not since we need to support older Pythons still, is this a blocker @P403n1x87?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 I think I'm getting a bit confused about the aim again. If this is for something that needs to go in ddtrace, it would probably be a new feature, hence it will be first released in a 2.x release. There, we no longer have support for older Pythons. We also don't plan any new 1.x releases. Are we planning to use envier in other projects that still support Python 2?

envier/env.py Outdated
self._items = defaultdict(
lambda: _EnvSource(
[
"programmatic",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"programmatic",
"override",

?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I wasn't a fan of programmatic, override is better 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With remote configuration being a thing I opted for code to reflect in-code changes to the configuration rather than override

@P403n1x87
Copy link
Collaborator

I believe this is being addressed elsewhere at the moment so we can close this for now.

@P403n1x87 P403n1x87 closed this Dec 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants