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

Config refactor #298

Merged
merged 48 commits into from
Feb 26, 2020
Merged

Config refactor #298

merged 48 commits into from
Feb 26, 2020

Conversation

antalszava
Copy link
Contributor

@antalszava antalszava commented Feb 21, 2020

Context:
Currently, to configure a connection to a remote service the Configuration class is used which encompasses the creation and updating of configuration-related data, allowing environment variables and configuration files to be used. However, no keyword arguments can be passed to this class for configuration. Furthermore, storing an instance of this class that it is available from different components of SF (e.g. RemoteEngine, LocalEngine) seems to be problematic, as a global Configuration instance would be needed for such use cases.

Description of the Change:
This is the first part of replacing the Configuration class with functions, adding the load_config and auxiliary functions.

The configuration object (that is a nested dictionary) would be created based on the following (order defines the importance, going from most important to least important):

  • keyword arguments passed to load_config
  • data contained in environmental variables (if any)
  • data contained in a configuration file (if exists)

If a user wants to simply use a configuration, the configuration object is available from confiugration.py which calls on load_config without any arguments. This solves the issue of having a global configuration if needed. A default configuration object called DEFAULT_CONFIG is also available containing pre-defined data for configuration.

Further changes regarding current implementation

  • If there are several configuration files at multiple locations, the very first to be found will be regarded
  • Moved the creation of a default config object into a function: now the defaults are through having the kwargs.get("option", default) calls
  • Iteration through the dictionary will occur multiple times -> means two iterations but creates a cleaner logic

Further comments

  • Functionality and tests have been removed for saving configuration files as well, these will be added in the subsequent PR
  • Tests that were failing due to parts of SF that will be soon refactored were marked xfail

Benefits:

  • The load_config function can be called anytime for configuration, without having to keep and update the state that was contained in a Configuration instance.
  • There is now an opportunity to load the configuration from keyword arguments and environment variables as well.
  • When parsing environment variables, a logic for parsing integers has been added.
  • The current use cases were taken into consideration.
  • More tests were added.

Possible Drawbacks:
Currently, all three options of keyword arguments, environment variables and configuration file are taken into consideration, eventually giving a union of the options while also taking into consideration the precedence. For now, this wouldn't seem to be a problem with limited details available.

Related GitHub Issues:
N/A

@codecov
Copy link

codecov bot commented Feb 21, 2020

Codecov Report

Merging #298 into demo will not change coverage by %.
The diff coverage is n/a.

@@           Coverage Diff           @@
##             demo     #298   +/-   ##
=======================================
  Coverage   96.86%   96.86%           
=======================================
  Files          48       48           
  Lines        6222     6222           
=======================================
  Hits         6027     6027           
  Misses        195      195           

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0063663...0063663. Read the comment docs.

@antalszava antalszava marked this pull request as ready for review February 24, 2020 21:05
Copy link
Member

@co9olguy co9olguy left a comment

Choose a reason for hiding this comment

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

Looks great @antalszava! Especially the thorough tests and clear separation of labour of the function.

Left a few suggestions for polishing etc.

tests/frontend/test_api_client.py Show resolved Hide resolved
tests/frontend/test_engine.py Show resolved Hide resolved
tests/frontend/test_configuration.py Outdated Show resolved Hide resolved
tests/frontend/test_configuration.py Outdated Show resolved Hide resolved
tests/frontend/test_configuration.py Outdated Show resolved Hide resolved
strawberryfields/configuration.py Outdated Show resolved Hide resolved
strawberryfields/configuration.py Outdated Show resolved Hide resolved
strawberryfields/configuration.py Outdated Show resolved Hide resolved
strawberryfields/configuration.py Outdated Show resolved Hide resolved
strawberryfields/configuration.py Outdated Show resolved Hide resolved
@antalszava
Copy link
Contributor Author

Locally I got the following for configuration.py:

Coverage for strawberryfields/configuration.py : 100%
69 statements   69 run 0 missing 3 excluded

Whereas the changes in other files will be affected by an upcoming refactor there.

Copy link
Member

@josh146 josh146 left a comment

Choose a reason for hiding this comment

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

This is great, thanks @antalszava! While it looks like a lot of comments, most are just polish --- the new config logic is 💯 times clearer and better than the previous iteration!

strawberryfields/configuration.py Outdated Show resolved Hide resolved
strawberryfields/configuration.py Outdated Show resolved Hide resolved
hostname (str): the name of the host to connect to
use_ssl (bool): specifies if requests should be sent using SSL
port (int): the port to be used when connecting to the remote service
debug (bool): determines if the debugging mode is requested
Copy link
Member

Choose a reason for hiding this comment

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

I'm thinking it might be best to move all the configuration options to the module docstring. This has several advantages:

  • Less redundancy --- configuration options only need to be specified in one place, and linked from everywhere else

  • We can use Sphinx glossary notation to add more details

  • It is user facing, whereas these docstrings aren't

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added them to the module docstring :) Added the list of environment variables, let me know if you think they need more adjustments :)

for section, sectionconfig in config.items():
for key in sectionconfig:
if key in other_config[section]:
config[section][key] = other_config[section][key]
Copy link
Member

Choose a reason for hiding this comment

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

Could this be made more succinct using the dict.update method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've changed it so, however, will have to add certain validity check most probably, such that no problems can arise from updating with invalid configurations.

strawberryfields/configuration.py Outdated Show resolved Hide resolved

update_from_environment_variables(config)

config_from_keyword_arguments = {"api": kwargs}
Copy link
Member

Choose a reason for hiding this comment

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

It might make sense to define the kwargs as 'belonging' to the "api" section when the defaults/types are defined.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would that be referring to the defaults used in create_config?

One of the problems I came across was that if I use create_config and kwargs here, then having defaults will result in an unexpected overwrite.

That is

In [1]: import strawberryfields.configuration as conf
In [3]:     config = { 
   ...:         "api": { 
   ...:             "authentication_token": 'NotDefaultAuth', 
   ...:             "hostname": 'NotDefaultHostname', 
   ...:             "use_ssl": True, 
   ...:             "port": 123456789, 
   ...:             "debug": False 
   ...:             } 
   ...:     } 
   ...: other_config = conf.create_config(authentication_token="MyToken") 
   ...: conf.update_config(config, other_config) 
   ...: config                                                                  
Out[3]: 
{'api': {'authentication_token': 'MyToken',
  'hostname': 'localhost',
  'use_ssl': True,
  'port': 443,
  'debug': False}}

where 'hostname' and 'port' were overwritten, whereas they were not defined in kwargs.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I see. This is because the other_config is replacing the default values 🤔

strawberryfields/configuration.py Show resolved Hide resolved
strawberryfields/configuration.py Outdated Show resolved Hide resolved
tests/frontend/test_configuration.py Outdated Show resolved Hide resolved
assert v != parsed_value

# Tear-down
del os.environ[env_var]
Copy link
Member

Choose a reason for hiding this comment

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

Mentioned this earlier, but better to use monkeypatch.setenv() here to avoid side effects 🙂

Copy link
Member

@co9olguy co9olguy left a comment

Choose a reason for hiding this comment

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

You've addressed all my concerns @antalszava

I notice a lot of small changes (e.g., to function names, signatures, and return types). I assume these come out of other reviews, so I will defer to e.g., @josh146 for final approval

strawberryfields/configuration.py Outdated Show resolved Hide resolved
…tring, inserting refs to these in function docstrings, correcting Kwargs section naming in docstrings
@antalszava
Copy link
Contributor Author

Reason for the force-push: Traversing the parents of each commit starting from the source 43d385d, it can be seen that the master branch was merged accidentally.

Copy link
Member

@josh146 josh146 left a comment

Choose a reason for hiding this comment

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

💯 Looks good from my end!

strawberryfields/configuration.py Outdated Show resolved Hide resolved
* **hostname (str)** (*optional*): the name of the host to connect to
* **use_ssl (bool)** (*optional*): specifies if requests should be sent using SSL
* **port (int)** (*optional*): the port to be used when connecting to the remote service
* **debug (bool)** (*optional*): determines if the debugging mode is requested
Copy link
Member

Choose a reason for hiding this comment

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

The bullet point list needs to be de-indented to match the indentation level of The following... above.

* SF_API_HOSTNAME
* SF_API_USE_SSL
* SF_API_DEBUG
* SF_API_PORT
Copy link
Member

Choose a reason for hiding this comment

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

This is sufficient for now, but we should create a new ticket to fix up the configuration keys documentation 🙂

strawberryfields/configuration.py Outdated Show resolved Hide resolved
strawberryfields/configuration.py Outdated Show resolved Hide resolved

update_from_environment_variables(config)

config_from_keyword_arguments = {"api": kwargs}
Copy link
Member

Choose a reason for hiding this comment

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

Ah, I see. This is because the other_config is replacing the default values 🤔

strawberryfields/configuration.py Show resolved Hide resolved
tests/frontend/test_configuration.py Show resolved Hide resolved
@antalszava
Copy link
Contributor Author

These last bit of modifications included:

  • removing the update_config function
  • adding the keep_valid_options function and adding tests.

The keep_valid_options function makes sure that only options that are recognized can be included coming from a configuration file or keyword arguments.

These modifications make up for using the dict.update function where applicable. As currently the only sectionconfig is "api", this is used more than previously. This can be extended to more complex code once new sectionconfigs are introduced (and potentially reintroducing the logic from update_config)

Further changes were carried out to configuration.rst to update it with:

  • keyword arguments
  • environment variables
  • port (where applicable)

Copy link
Member

@josh146 josh146 left a comment

Choose a reason for hiding this comment

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

All comments are minor, this is in very good state :)

strawberryfields/configuration.py Outdated Show resolved Hide resolved
* SF_API_AUTHENTICATION_TOKEN
* SF_API_HOSTNAME
* SF_API_USE_SSL
* SF_API_PORT
Copy link
Member

Choose a reason for hiding this comment

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

I would combine the two sections above into one, something like (feel free to modify/clean):

Configuration options
*********************

 **authentication_token (str)** (*required*)
    The authentication token to use when connecting to the API. Will be sent with every request in
    the header. Corresponding environment variable: ``SF_API_AUTHENTICATION_TOKEN``

**hostname (str)** (*optional*)
    The hostname of the server to connect to. Defaults to ``localhost``. Must be one of the allowed
    hosts. Corresponding environment variable: ``SF_API_HOSTNAME``

**use_ssl (bool)** (*optional*)
    Whether to use SSL or not when connecting to the API. True or False.
    Corresponding environment variable: ``SF_API_USE_SSL``

**port (int)** (*optional*)
    The port to be used when connecting to the remote service.
    Corresponding environment variable: ``SF_API_PORT``

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh thanks for that!

.. warning::

The following configuration options are available:
:doc:`/introduction/configuration`.
Copy link
Member

Choose a reason for hiding this comment

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

For consistency with our other developer pages:

.. warning::

    Unless you are a Strawberry Fields developer, you likely do not need to access this module directly.
    See more details regarding Strawberry Fields configuration and available configuration options 
    on the :doc:`/introduction/configuration` page.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure! Here the first part will already be included from https:/XanaduAI/strawberryfields/blob/demo/doc/code/sf_configuration.rst

so I'll add the reworded second part :)

}

BOOLEAN_KEYS = ("debug", "use_ssl")
BOOLEAN_KEYS = {"use_ssl"}
INTEGER_KEYS = {"port"}
Copy link
Member

Choose a reason for hiding this comment

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

Now that the above DEFAULT_CONFIG_SPEC specifies the types, are these still needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh not at all, thanks for that!

strawberryfields/configuration.py Outdated Show resolved Hide resolved
VALID_KEYS = set(create_config()["api"].keys())
DEFAULT_CONFIG = create_config()
configuration = load_config()
config_filepath = get_config_filepath()
Copy link
Member

Choose a reason for hiding this comment

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

This is very nice :)

@antalszava antalszava merged commit 7a9ef04 into demo Feb 26, 2020
@antalszava antalszava deleted the config_refactor branch February 26, 2020 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants