8000 Auto init by avishalom · Pull Request #105 · firebase/firebase-admin-python · GitHub
[go: up one dir, main page]

Skip to content

Auto init #105

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

Merged
merged 32 commits into from
Jan 11, 2018
Merged

Auto init #105

merged 32 commits into from
Jan 11, 2018

Conversation

avishalom
Copy link
Contributor
@avishalom avishalom commented Dec 20, 2017

Adding the auto init from env var to python.

Copy link
Contributor
@hiranya911 hiranya911 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. Needs some refactoring.

@@ -31,7 +32,8 @@
_clock = datetime.datetime.utcnow

_DEFAULT_APP_NAME = '[DEFAULT]'

_CONFIG_JSON_ENV = 'FIREBASE_CONFIG'
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename to _FIREBASE_CONFIG_ENV_VAR

8000

@@ -31,7 +32,8 @@
_clock = datetime.datetime.utcnow

_DEFAULT_APP_NAME = '[DEFAULT]'

_CONFIG_JSON_ENV = 'FIREBASE_CONFIG'
_CONFIG_VALID_KEYS = ['databaseAuthVariableOverride', 'databaseURL', 'projectId', 'storageBucket']
Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose this should also include httpTimeout.

raise ValueError('Illegal Firebase app options type: {0}. Options '
'must be a dictionary.'.format(type(options)))
self._options = options
if options is not None:
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks a bit messy. Please refactor as follows:

if options is None:
    options = _load_from_environment()
...


# This fixture will ignore the environment variable pointing to the default
# configuration for the duration of the tests.
@pytest.fixture(scope="session", autouse=True)
Copy link
Contributor

Choose a reason for hiding this comment

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

What are the tests affected by this fixture? Ideally it shouldn't affect tests in other modules.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes you are right, this is no longer needed.

@@ -102,6 +264,10 @@ class TestFirebaseApp(object):
firebase_admin.App('uninitialized', CREDENTIAL, {})
]

bad_config_file = ['firebase_config_empty.json',
Copy link
Contributor

Choose a reason for hiding this comment

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

Inline this list wherever you're using it. Seems to be used in only one test case.

@@ -135,11 +301,29 @@ def test_app_init_with_invalid_options(self, options):
with pytest.raises(ValueError):
firebase_admin.initialize_app(CREDENTIAL, options=options)

@pytest.mark.parametrize('bad_file_name', bad_config_file)
def test_default_app_init_with_bad_config_from_env(self, bad_file_name):
Copy link
Contributor

Choose a reason for hiding this comment

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

test_app_init_with_invalid_config_file

with pytest.raises(ValueError):
firebase_admin.initialize_app(CREDENTIAL)
finally:
if config_file_old:
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you should create a fixture, that sets a given value/file as the FIREBASE_CONFIG variable, and then reverts it back after the test. Then inject that fixture to any tests that is potentially affected by this change. You can probably get rid of that session scoped/autouse fixture that way.

Copy link
Contributor Author
@avishalom avishalom Jan 5, 2018

Choose a reason for hiding this comment

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

This is exactly what I was doing in the OptionsTest class,
nm, PTAL

return dict(zip(("ids", "params"), zip(*named_id_option_pairs)))


@pytest.fixture(**named_option_pairs_for_test([
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a readability nightmare. Can we turn these into individual test cases? At very least, set this dictionary up as a global constant. Then use pytest indirect parameters to pass the test parameters from test case to the fixture: https://docs.pytest.org/en/latest/example/parametrize.html#apply-indirect-on-particular-arguments

@hiranya911 hiranya911 assigned avishalom and unassigned hiranya911 Jan 4, 2018
@avishalom avishalom assigned hiranya911 and unassigned avishalom Jan 6, 2018
Copy link
Contributor
@hiranya911 hiranya911 left a comment

Choose a reason for hiding this comment

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

A few nits here and there. This is very close to being complete.

if options is None:
options = {}
if not isinstance(options, dict):
self._load_from_environment()
Copy link
Contributor

Choose a reason for hiding this comment

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

Make this function side-effect free. I think the overall logic should look like:

if options is None:
  options = self._load_from_environment()
if not isinstance(options, dict):
  # error
self._options = options

'must be a dictionary.'.format(type(options)))
self._options = options
'must be a dictionary.'.format(type(self._options)))

Copy link
Contributor

Choose a reason for hiding this comment

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

Remove redundant empty line


def get(self, key, default=None):
"""Returns the option identified by the provided key."""
return self._options.get(key, default)

def _load_from_environment(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Return a dict from this method, rather than mutating the state.

raise ValueError('Unable to read file {}. {}'.format(config_file, err))
try:
json_data = json.loads(json_str)
except Exception as err:
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we not handle this exception, and just let it be thrown?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we can,
the error message is "Expecting property name: line 1 column 2 (char 1)"
I think it is clearer if we add information, but willing to remove if you want it gone.

revert_config_env(config_old)


OptionsTestCase = namedtuple('OptionsTestCase',
Copy link
Contributor

Choose a reason for hiding this comment

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

This is sitting in the middle of a class definition. Please move to somewhere more appropriate.

app = firebase_admin.initialize_app(CREDENTIAL, options=test_case.init_options)
for field in firebase_admin._CONFIG_VALID_KEYS:
assert app.options.get(field) == test_case.want_options.get(field), test_case.name
revert_config_env(config_old)
Copy link
Contributor

Choose a reason for hiding this comment

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

This won't run if an assertion fails. See if you can turn it into a self-cleaning fixture like https://github.com/firebase/firebase-admin-python/blob/master/tests/test_credentials.py#L83

If that's not possible, at least use a try-finally block.

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 put it in the fixture according to your first suggestion, thanks.
PTAL

'projectId': 'pid1 - mock',
'storageBucket': 'sb1 - .mock'})]

@pytest.mark.parametrize('test_case', options_test_cases)
Copy link
Contributor

Choose a reason for hiding this comment

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

Pass ids= parameter here. Then you don't have to use test_case.name in the body of the test case.

OptionsTestCase = namedtuple('OptionsTestCase',
'name, config_json, init_options, want_options')
options_test_cases = [
OptionsTestCase(name='no env var, empty options',
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you clean up/clarify the test case names a bit? Perhaps get some ideas from the Java PR?

@hiranya911 hiranya911 assigned avishalom and unassigned hiranya911 Jan 6, 2018
@avishalom avishalom assigned hiranya911 and unassigned avishalom Jan 8, 2018
Copy link
Contributor
@hiranya911 hiranya911 left a comment

Choose a reason for hiding this comment

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

LGTM with a couple of nits

@@ -149,12 +149,12 @@ class _AppOptions(object):
def __init__(self, options):
self._options = options
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove this line, now that we're doing the assignment below.

@pytest.fixture(scope="function")
def env_test_case(request):
config_old = set_config_env(request.param.config_json)
def fin():
Copy link
Contributor

Choose a reason for hiding this comment

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

for field in firebase_admin._CONFIG_VALID_KEYS:
assert app.options.get(field) == test_case.want_options.get(field), test_case.name
revert_config_env(config_old)
assert app.options.get(field) == env_test_case.want_options.get(field)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we do a stronger check here? Something like assert app.options._options == env_test_cast.want_options?

@hiranya911 hiranya911 assigned avishalom and unassigned hiranya911 Jan 8, 2018
``databaseURL``, ``storageBucket``, ``projectId``, ``databaseAuthVariableOverride``
and ``httpTimeout``. If ``httpTimeout`` is not set, HTTP connections initiated by client
modules such as ``db`` will not time out.
If options are not provided an attempt is made to load the options from the environment.
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be part of the method description, rather than the argument description.

@avishalom avishalom merged commit f5d6071 into master Jan 11, 2018
@avishalom avishalom deleted the auto_init branch January 11, 2018 00:06
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
0