Skip to content

Comments

Config class#21

Merged
NickPapONS merged 9 commits intomainfrom
#config-class
Feb 8, 2024
Merged

Config class#21
NickPapONS merged 9 commits intomainfrom
#config-class

Conversation

@NickPapONS
Copy link
Contributor

@NickPapONS NickPapONS commented Jan 29, 2024

Added functionality to StringProperty, created IntegerProperty, and also Config class. Added unit tests for all StringProperty and IntegerProperty methods, Config unit tests had to be reverted and put on hold until some feedback hopefully helps complete the class.

Almost finished, but I am uncertain on a couple of points and would like some more input, so please have a look at the described lines and see if it makes sense:

In string.py line 30-31:
Not sure what kind of matching process is meant to be done here? This affects how the string property would be validated.

In config.py line 47:
This is where a property is defined and populated from the input dictionary if it is to be of the StringProperty type. What I need help on is what the "value" field should be assigned to. I understand what the value field represents, but don't really see where that is coming from in the input dictionary. (If you need an example of what an input dictionary looks like see here: https://github.com/GSS-Cogs/dp-python-tools/tree/main/dpytools/config )

Would appreciate just a general sanity check on the config class/the from_env method. This was pretty confusing at first but I think the loop that goes through the dictionary and uses setattr() to make attributes dynamically based on the dictionary contents basically does what the ticket specifies. It's not 100% done yet but hopefully another pair of eyes can get it there. Thanks.

Also, not sure why there are so many weird binary files in the Files changed. Looking into it now

Copy link
Contributor

@mikeAdamss mikeAdamss left a comment

Choose a reason for hiding this comment

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

So this is quite a gnarly one so I had to work through it myself. Code below but I need to explain a few things first.

@staticmethod

Is just syntactic sugar, it just associates a function with a class for usibility, so

class MyClass:

       @staticmethod
       def from_env(): -> MyClass:
          ...

is identical in functionality to

MyClass:
   ...
   
  
 # Note - just a function, not a method on the class
def myclass_from_env() -> MyClass
   ...
   

It''s basically loose assocation to make code easier to read (in this case making it clear its the Config class that we're making from env vars).

....which is why... you codes not working, because there's no "self" in a function.

Hopefully the following tweaked version will make more sense.

annotations

That from future import at the top does some magic stuff. Without that you cant reference a class within itself (the -> Config) type hint after .from_env. That's all this import is - allowing that type hint/annotation.

worked example

Like I said, it was easier to just work this through myself, can you run through this and make sure t makes sense to you and come back at me with any questions?

from __future__ import annotations

import os
from typing import Dict, List

from .properties.base import BaseProperty
from .properties.string import StringProperty
from .properties.intproperty import IntegerProperty

class Config:
    
    def __init__(self):
        self._properties_to_validate: List[BaseProperty] = []

    @staticmethod
    def from_env(config_dict: Dict[str, Dict[str, BaseProperty]]) -> Config:

        config = Config()

        for env_var_name, value in config_dict.items():

            value_for_property = os.environ.get(env_var_name, None)
            assert value_for_property is not None, f'Required envionrment value "{env_var_name}" could not be found.'

            if value["class"] == StringProperty:
                if value["kwargs"]:
                    regex = value["kwargs"].get("regex")
                    min_len = value["kwargs"].get("min_len")
                    max_len = value["kwargs"].get("max_len")
                else:
                    regex = None
                    min_len = None
                    max_len = None

                # Best way to determine populating regex, min len, max len
                # if they are none?    
                stringprop = StringProperty(
                    name = value["property"],
                    value = value_for_property,
                    regex = regex,
                    min_len = min_len,
                    max_len = max_len
                )

                prop_name = value["property"]
                setattr(config, prop_name, stringprop)
                config._properties_to_validate.append(stringprop)

            elif value["class"] == IntegerProperty:
                if value["kwargs"]:
                    min_val = value["kwargs"].get("min_val")
                    max_val = value["kwargs"].get("max_val")
                else:
                    min_val = None
                    max_val = None

                intprop = IntegerProperty(
                    name = value["property"],
                    value = value_for_property,
                    min_val = min_val,
                    max_val = max_val
                )

                prop_name = value["property"]
                setattr(config, prop_name, intprop)
                config._properties_to_validate.append(intprop)

            else:
                raise ValueError("Incorrect value type specified for property assignment.")

        return  config


    def assert_valid_config(self):
        """
        Assert that then Config class has the properties that
        provided properties.
        """
        for property in self._properties_to_validate:
            property.type_is_valid()
            property.secondary_validation()

        self._properties_to_validate = []

Take your time to understand that, using a static method for class constructors in this way is one of those python pattern that's really handy when used in just the right circumstances, worth some brain cells.

I might have some comments on the code itself but worth going through the rewrite and any tests first and we'll go from there.

validation

I might change my mind in time, but for now we want a separate validation we can call against the Config class, don't bother validating each property as you construct, aim for a populated Config class with an assert_valid_config method that someone can choose to call.

Hope that all makes sense, have a look and grab me if you want to get into the weeds on any of this.

@NickPapONS NickPapONS marked this pull request as ready for review February 2, 2024 17:02
Copy link
Contributor

@mikeAdamss mikeAdamss left a comment

Choose a reason for hiding this comment

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

coming along nicely. Added a few comments but its around the edges, the meat of this is done and working.

Copy link
Contributor

@mikeAdamss mikeAdamss left a comment

Choose a reason for hiding this comment

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

couple of bits of tidy up, but this is essentially done.

Copy link
Contributor

Choose a reason for hiding this comment

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

You'll need to delete all these .pyc files, then commit the fact you deleted them all, that should get them out of repo.

You may need to temporarily unignore them in the gitignore to do it, not sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The temporary unignore seems to be what I missed, I'm hoping I did it right this time 😅 let me know if not

Copy link
Contributor

@mikeAdamss mikeAdamss left a comment

Choose a reason for hiding this comment

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

LGTM, well done 👍

@NickPapONS NickPapONS merged commit 0f38bf0 into main Feb 8, 2024
@NickPapONS NickPapONS deleted the #config-class branch February 8, 2024 17:12
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