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

added methods to load config from a file #199

Merged
merged 7 commits into from
Jan 29, 2017

Conversation

Archelyst
Copy link
Contributor

@Archelyst Archelyst commented Dec 16, 2016

Sanic is lacking an easy way to configure an application. This adds basically the a simplified version of Flasks configuration. I added __getattr__ and __setattr__ in order to preserve the possibility to access the values with dot-notation.

As a side-effect the ROUTER_CACHE_SIZE got moved to router.py. First of all it doesn't really work with the new config class. Second, it was misleading in the first place, because it suggested that it was configurable - but it wasn't (except by changing the code in config.py). When changing the config, the lru_cache decorator is already fixed and the change has no effect.

@AntonDnepr
Copy link
Contributor

Hi @Tim-Erwin !
Thank you for your work.
I'm not really experienced in the topic of configuration from file, but may I ask you why do you inherit from dict at the Config? I see that you have to declare __getattr__ and __setattr__ because of that. Is there any real advantage for using dict?
And also may I ask you to write tests for this feature?


class Config(dict):
def __init__(self, defaults=None):
dict.__init__(self, defaults or {})
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it's better to use super here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Idk, not really a difference, I'd say. Can change it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that built-in dict will inherit a lot of other stuff, but please go with super here just in case

Copy link
Member

Choose a reason for hiding this comment

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

I'd agree with @AntonDnepr

self.ROUTER_CACHE_SIZE = 1024

def __getattr__(self, attr):
return self[attr]
Copy link
Contributor

Choose a reason for hiding this comment

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

according to https://docs.python.org/3/reference/datamodel.html#object.__getattr__ this method should raise AttributeError, but in this case it will raise KeyError instead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wow, good catch.

module.__file__ = filename
try:
with open(filename) as config_file:
exec(compile(config_file.read(), filename, 'exec'),
Copy link
Contributor

Choose a reason for hiding this comment

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

I understand this is a bit paranoid, but is it really safe to execute compiled third-party file? But in any case I can't propose better approach

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, it's not really third-party, you're including your own file. It's quite common to use a python module as configuration. So I'd call that a well-proven procedure :)

Copy link
Contributor

Choose a reason for hiding this comment

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

ok, thank you :)

Copy link
Member

Choose a reason for hiding this comment

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

you could just dynamically import it with importlib if you want to

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 tried using importlib, however it fails when the config file does not have the .py suffix. Anyway, whether we are loading with importlib or with compile/exec does not really matter, in both cases we're executing python code. Or is there any benefit I'm missing?


:param obj: an object
"""
for key in dir(obj):
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe it would be faster to make a list comprehension with filter for removing keys which starts with __ or _? I don't have a performance test, but I think it would be faster then go through every key

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And then issue self.update()? I'm modifying a dict in the loop, not creating one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

By the way: the isupper() is quite intentional instead of checking for _ because it allows to have some logic in your config file using non-upper variables without messing up your final config.

Copy link
Contributor

Choose a reason for hiding this comment

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

sorry about disturbing, doesn't really matter, existing code is ok

@Archelyst
Copy link
Contributor Author

Wow, that's a fast review, thanks. I let Config inherit because it gives a lot of flexibility for free, such as using update() in order to merge configurations. I wouldn't insist on that, however, I could remove that.
Sure, I'll add tests for that. Usually I just want to be sure that a feature is wanted before I make the effort to write tests :)

:param obj: an object
"""
for key in dir(obj):
if key.isupper():
Copy link
Contributor

@AntonDnepr AntonDnepr Dec 16, 2016

Choose a reason for hiding this comment

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

can't you change this for circle with built-in map? As I remember it works faster then for

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You mean filter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

By the way, speed should really not be of any concern here, this is only executed at startup. Readability is far more important in this area.

Copy link
Contributor

@AntonDnepr AntonDnepr Dec 16, 2016

Choose a reason for hiding this comment

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

I just made quick performance test and this really doesn't matter in this case. +1 about readability, so sorry about disturbing :)

@Archelyst
Copy link
Contributor Author

So, what do you think, @AntonDnepr , leaving as dict ok?

@AntonDnepr
Copy link
Contributor

AntonDnepr commented Dec 16, 2016

@Tim-Erwin thank you for explaining. As I said I'm not really experienced in this topic, so I was curious about this and it would be ok as for me.
As for me it would be nice to have this feature, but we need to summon @seemethere here

@Archelyst
Copy link
Contributor Author

Ok, I added some documentation. I think I've change everything you asked me to. If there's nothing more to change, I think we're good to go.

Copy link
Member

@seemethere seemethere left a comment

Choose a reason for hiding this comment

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

Gonna wait on one more approval from @channelcat to merge this through and package it into the next release

@seemethere seemethere added this to the 0.2.0 milestone Dec 25, 2016
@Archelyst
Copy link
Contributor Author

@channelcat , this is marked for release tomorrow. Any thoughts on this? Would love to have this merged.

@seemethere
Copy link
Member

@Tim-Erwin I'm going to cut a release for 0.2.0 today, I'll move this to the next release.

@seemethere seemethere modified the milestones: 0.2.0, 0.3.0 Jan 18, 2017
@Archelyst
Copy link
Contributor Author

Archelyst commented Jan 25, 2017

I'll just continue resolving conflicts :(

@@ -15,6 +14,8 @@
'alpha': (str, r'[A-Za-z]+'),
}

ROUTER_CACHE_SIZE = 1024
Copy link
Contributor

@r0fls r0fls Jan 27, 2017

Choose a reason for hiding this comment

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

Why not leave this in as a built in configuration value? Then it could be modified by the user in their app with app.config['ROUTER_CACHE_SIZE'] = 2048

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because it actually cannot be modified. The value is read and applied only once when the sanic module is imported, any further changes don't have any effect.

@seemethere seemethere removed this from the 0.3.0 milestone Jan 28, 2017
@Archelyst
Copy link
Contributor Author

@seemethere , why is that removed from the milestone? This is super easy and backwards compatible and not in a performance critical path and the code is (as mentioned before) borrowed from Flask to quite some extent, so it can be considered tested in production environments. There is documentation and tests and nobody seems to have serious problems with it. If something is missing or not conforming to Sanic standards, I'd be happy to change or add missing parts. Please let me know how to get this done.

@seemethere seemethere added this to the 0.3.1 milestone Jan 29, 2017
@seemethere
Copy link
Member

Rolling into 0.3.1

@seemethere seemethere merged commit f56c5e3 into sanic-org:master Jan 29, 2017
@Archelyst
Copy link
Contributor Author

Thanks

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.

4 participants