-
Notifications
You must be signed in to change notification settings - Fork 2.2k
GeoJson StyleCheck #1024
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
GeoJson StyleCheck #1024
Conversation
…hat they are callable, and that they return a dictionary.
…in Assertion failure erros to the user.
folium/features.py
Outdated
|
||
for caller_key in callers: | ||
caller = callers[caller_key] | ||
if caller is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for the None
and caller = callers[caller_key]
and we can drop the assert here in lieu of Python Exceptions. You can use .items()
like:
for key, caller in callers.items():
if not callable(caller) and not isinstance(caller(self.data['features'][0]), dict):
raise ValueError(f'{key} must be a callable function returning a dictionary.')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, I always forget about .items()
on dicts. Been spending more time in the JS world lately.
Don't we need the not None
check though? The default values for style_function
and highlight_function
are None
s right now - so they'll fail this check if unmodified, if I'm not mistaken.
Also, will our CI travis build environment accept the f-string usage? Last time I tried to commit one it caused a failure so I've been using .format()
instead.
Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we need the
not None
check though?
if not callable(caller)
should take care of that if I'm not mistaken. The caller
will be None
and will be True
in not callable(caller)
, but I believe the and
should be an or
. We want to raise if one or the other is True
, right?
Also, will our CI travis build environment accept the f-string usage? Last time I tried to commit one it caused a failure so I've been using
.format()
instead.
I wrote that quickly and I use f-strings everywhere, it was not meant to be a copy-n-paste review, sorry.
With that said, we will drop Python 2.7 support in the next release and we can use a f-string backport package for py35, so I'm OK if we start introducing them now. Let's just wait for @Conengmo opinion on this one 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The caller will be None and will be True in not callable(caller), but I believe the and should be an or. We want to raise if one or the other is True, right?
But this will pass any other uncallable object, in addition to None
, I'm pretty sure - like the dictionary in #1020.
IMO there should be only two passing cases for the callers - None
and functions returning dicts.
Calling an uncallable (like None(self.data['features'][0])
) raises TypeError
. So we need the and
operator to take the False
from callable
if our second call results in that TypeError
, and still raise our custom ValueError
.
So we need:
if caller is not None:
if not callable(caller) or not isinstance(caller(self.data['features'][0]), dict):
raise ValueError('Pass a function returning a dictionary to `{}`'.format(key))
Or maybe pack all the conditionals into one line? I know it is long, but I want to make it as airtight as possible and give useful feedback to the user. Let me know what you think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you do the validation after the lines where self.style_function
and the other one are set, you don't have to check for None
. But if you validate the class input then I suppose you do need to check for None
because None
is a valid input so it shouldn't raise an error.
I think your check is good, using callable()
and checking whether normal use returns a dictionary. I'm not a fan though of putting stuff in a container and then looping over that when you want to do the same thing multiple times. In these situations a function that you call multiple times is better IMO. For example:
self._validate_function(self.style_function, 'style_function')
self._validate_function(self.highlight_function, 'highlight_function')
def _validate_function(self, func, name):
"""Check if style_function and highlight_function work as intended."""
if not callable(func) or not isinstance(func(self.data['features'][0]), dict):
raise ValueError()
But note that I don't mind keeping it as it is. I'm interested though in what you think is cleaner code.
Another idea to make it less convoluted is to maybe not make a dictionary to loop over:
for func_name in ('style_function', 'highlight_function'):
func = getattr(self, func_name)
# do the check
Change assertion to raise ValueError.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added my take on your discussion. I guess I'm not helping by adding more text :) But it is interesting to hear your thoughts on code style.
By the way the Travis build is failing on a single notebook related test:
https://travis-ci.org/python-visualization/folium/jobs/459020933#L2934
folium/features.py
Outdated
|
||
for caller_key in callers: | ||
caller = callers[caller_key] | ||
if caller is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you do the validation after the lines where self.style_function
and the other one are set, you don't have to check for None
. But if you validate the class input then I suppose you do need to check for None
because None
is a valid input so it shouldn't raise an error.
I think your check is good, using callable()
and checking whether normal use returns a dictionary. I'm not a fan though of putting stuff in a container and then looping over that when you want to do the same thing multiple times. In these situations a function that you call multiple times is better IMO. For example:
self._validate_function(self.style_function, 'style_function')
self._validate_function(self.highlight_function, 'highlight_function')
def _validate_function(self, func, name):
"""Check if style_function and highlight_function work as intended."""
if not callable(func) or not isinstance(func(self.data['features'][0]), dict):
raise ValueError()
But note that I don't mind keeping it as it is. I'm interested though in what you think is cleaner code.
Another idea to make it less convoluted is to maybe not make a dictionary to loop over:
for func_name in ('style_function', 'highlight_function'):
func = getattr(self, func_name)
# do the check
What is the best design pattern here? To me, it seems like it would be best to head off these errors before assigning to Whatever makes it easiest for maintainers down the line to understand/make changes as the library evolves. @ocefpaf what do you think? |
I guess if we can't choose, then it doesn't matter what we choose :) |
😄
More inclined to follow the pattern where you don't have to check for |
Haha, I wasn't sure if there was a 'pythonic' or OOP best practice way to manage that kind of stuff. Sounds good - I'll go ahead and rewrite it to a |
Also handled case in which `self.data` is a single feature instead of a FeatureCollection.
Looks like I was able to fix the failing test by handling the case where |
Added a check on the GeoJson class constructor to ensure that
style_function
andhighlight_function
are functions, and that they return dictionaries.