-
Notifications
You must be signed in to change notification settings - Fork 4
[LIB-653] GeoPoint re work, add tests, add possibility to filter; #191
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
Changes from all commits
16809b2
251c56f
5437253
5d9ccf2
5e57ea9
e6fcd34
fd1e13d
a1c873f
7ef7d83
893671c
1b4acd1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from syncano.exceptions import SyncanoValueError | ||
|
|
||
|
|
||
| class GeoPoint(object): | ||
|
|
||
| def __init__(self, latitude, longitude): | ||
| self.latitude = latitude | ||
| self.longitude = longitude | ||
|
|
||
| def __repr__(self): | ||
| return "GeoPoint(latitude={}, longitude={})".format(self.latitude, self.longitude) | ||
|
|
||
| def to_native(self): | ||
| geo_struct_dump = {'latitude': self.latitude, 'longitude': self.longitude} | ||
| return geo_struct_dump | ||
|
|
||
|
|
||
| class Distance(object): | ||
|
|
||
| KILOMETERS = '_in_kilometers' | ||
| MILES = '_in_miles' | ||
|
|
||
| def __init__(self, kilometers=None, miles=None): | ||
| if kilometers is not None and miles is not None: | ||
| raise SyncanoValueError('`kilometers` and `miles` can not be set at the same time.') | ||
|
|
||
| if kilometers is None and miles is None: | ||
| raise SyncanoValueError('`kilometers` or `miles` attribute should be specified.') | ||
|
|
||
| self.distance = kilometers or miles | ||
| self.unit = self.KILOMETERS if kilometers is not None else self.MILES | ||
|
|
||
| def to_native(self): | ||
| return { | ||
| 'distance{}'.format(self.unit): self.distance | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| # -*- coding: utf-8 -*- | ||
| import six | ||
| from syncano.exceptions import SyncanoValueError | ||
| from syncano.models import Class, Distance, GeoPoint, Object | ||
| from tests.integration_test import InstanceMixin, IntegrationTest | ||
|
|
||
|
|
||
| class GeoPointApiTest(InstanceMixin, IntegrationTest): | ||
|
|
||
| @classmethod | ||
| def setUpClass(cls): | ||
| super(GeoPointApiTest, cls).setUpClass() | ||
|
|
||
| cls.city_model = Class.please.create( | ||
| instance_name=cls.instance.name, | ||
| name='city', | ||
| schema=[ | ||
| {"name": "city", "type": "string"}, | ||
| {"name": "location", "type": "geopoint", "filter_index": True}, | ||
| ] | ||
| ) | ||
|
|
||
| cls.warsaw = cls.city_model.objects.create(location=(52.2240698, 20.9942933), city='Warsaw') | ||
| cls.paris = cls.city_model.objects.create(location=(52.4731384, 13.5425588), city='Berlin') | ||
| cls.berlin = cls.city_model.objects.create(location=(48.8589101, 2.3125377), city='Paris') | ||
| cls.london = cls.city_model.objects.create(city='London') | ||
|
|
||
| cls.list_london = ['London'] | ||
| cls.list_warsaw = ['Warsaw'] | ||
| cls.list_warsaw_berlin = ['Warsaw', 'Berlin'] | ||
| cls.list_warsaw_berlin_paris = ['Warsaw', 'Berlin', 'Paris'] | ||
|
|
||
| def test_filtering_on_geo_point_near(self): | ||
|
|
||
| distances = { | ||
| 100: self.list_warsaw, | ||
| 600: self.list_warsaw_berlin, | ||
| 1400: self.list_warsaw_berlin_paris | ||
| } | ||
|
|
||
| for distance, cities in six.iteritems(distances): | ||
| objects = Object.please.list(instance_name=self.instance.name, class_name="city").filter( | ||
| location__near={ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe sth like this ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make sense! ;)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or do it similar to how Django does it and add a simplified Distance() object. And wrapping it in GeoLookup seems unnecessary unless it's consistent with other lookups. We only got one
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks quite nice :) kms=distance is for kilometers
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @opalczynski as a non-python Dev, your option is more readable to me.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess Distance(kilometers=10) and Distance(miles=10) is better to me. |
||
| "latitude": 52.2297, | ||
| "longitude": 21.0122, | ||
| "kilometers": distance, | ||
| } | ||
| ) | ||
|
|
||
| result_list = self._prepare_result_list(objects) | ||
|
|
||
| self.assertListEqual(result_list, cities) | ||
|
|
||
| def test_filtering_on_geo_point_near_miles(self): | ||
| objects = Object.please.list(instance_name=self.instance.name, class_name="city").filter( | ||
| location__near={ | ||
| "latitude": 52.2297, | ||
| "longitude": 21.0122, | ||
| "miles": 10, | ||
| } | ||
| ) | ||
| result_list = self._prepare_result_list(objects) | ||
| self.assertListEqual(result_list, self.list_warsaw) | ||
|
|
||
| def test_filtering_on_geo_point_near_with_another_syntax(self): | ||
| objects = self.city_model.objects.filter( | ||
| location__near=(GeoPoint(52.2297, 21.0122), Distance(kilometers=10)) | ||
| ) | ||
| result_list = self._prepare_result_list(objects) | ||
| self.assertListEqual(result_list, self.list_warsaw) | ||
|
|
||
| objects = self.city_model.objects.filter( | ||
| location__near=(GeoPoint(52.2297, 21.0122), Distance(miles=10)) | ||
| ) | ||
| result_list = self._prepare_result_list(objects) | ||
| self.assertListEqual(result_list, self.list_warsaw) | ||
|
|
||
| def test_filtering_on_geo_point_exists(self): | ||
| objects = self.city_model.objects.filter( | ||
| location__exists=True | ||
| ) | ||
|
|
||
| result_list = [o.city for o in objects] | ||
|
|
||
| self.assertListEqual(result_list, self.list_warsaw_berlin_paris) | ||
|
|
||
| objects = self.city_model.objects.filter( | ||
| location__exists=False | ||
| ) | ||
|
|
||
| result_list = self._prepare_result_list(objects) | ||
|
|
||
| self.assertListEqual(result_list, self.list_london) | ||
|
|
||
| def test_distance_fail(self): | ||
| with self.assertRaises(SyncanoValueError): | ||
| self.city_model.objects.filter( | ||
| location__near=(GeoPoint(52.2297, 21.0122), Distance(miles=10, kilometers=20)) | ||
| ) | ||
|
|
||
| with self.assertRaises(SyncanoValueError): | ||
| self.city_model.objects.filter( | ||
| location__near=(GeoPoint(52.2297, 21.0122), Distance()) | ||
| ) | ||
|
|
||
| def _prepare_result_list(self, objects): | ||
| return [o.city for o in objects] | ||
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.
This kind of checks can be done with assertions like that https://wiki.python.org/moin/UsingAssertionsEffectively:
assert isinstance(value, GeoPoint), 'Expected GeoPoint'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.
Using asserts in code that is meant to be normally used (outside of tests) is baaad. Especially for validations.
python -Oand suddenly my validations do not work ;)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 last time I was running python with -O flag was in 2007. It doesn't give you any kind of advantage :) Maybe if you run your tests with -O flag, then it would be a problem, but otherwise IMHO it's ok. And it's the official way for type assertions.
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.
Well, not exactly. It's official in terms of places where the object is actually used. This is a validation function so these are two different things to me. Never seen an assert in a validator of any kind. Although it's likely that main reason for this is the fact that we want a specific Exception class that can be handled rather than AssertionError which suggests something went very wrong.
Anyway, python promotes duck-typing heavily and doesn't like using isinstance. In my opinion that's why they are ok with asserts officially. But coming from C/C++ background, depending on asserts is well, ugly.
Also, kind of unrelated - it's pretty ridiculous that they changed in 3.x
printinto a function butassertis still a statement.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.
Me not like asserts in such context - so leave it that way.