from marshmallow import Schema, fields, post_dump
from urllib.parse import quote
class LocationUrl(object):
def __init__(self, data, protocol, schema):
self._data = data
self._http_protocol = protocol
self._schema_dict = self._schema_values(schema)
def _schema_values(self, schema):
is_valid_schema = schema.validate(self._data)
if bool(is_valid_schema):
raise KeyError(is_valid_schema)
else:
return schema.dump(self._data).data
@property
def protocol(self):
"""This property helps in returning the http protocol to be used as
part of the URL.
:ivar http_protocol: The http protocol passed in (http/https)
:getter: Returns a string relevant to the http
protocol
- http: ``http:/``
- https: ``https:/``
:type: string
"""
if self._http_protocol == 'http':
return 'http:/'
else:
return 'https:/'
@property
def main_url(self):
"""This property helps in returning the main URL used in bingmaps rest
services
:getter: Returns a
URL
- ``dev.virtualearth.net``
:type: string
"""
return 'dev.virtualearth.net'
@property
def rest(self):
"""This property make the URL accessible for REST services.
:getter: Returns a string saying that the URL is a REST service
URL
- ``REST``
:type: string
"""
return 'REST'
@property
def version(self):
"""This property gives the version of the Bing Maps REST services
:getter: Returns a string of Bing Maps REST services
version
- ``v1`` for Locations services
:type: string
"""
if 'version' in self._schema_dict:
return self._schema_dict['version']
else:
return None
@property
def restApi(self):
"""This property gives a part of the URL that identifies the REST API
:getter: Returns a part of the URL that identifies the REST
API
- ``Locations`` for Locations services
:type: string
"""
if 'restApi' in self._schema_dict:
return self._schema_dict['restApi']
else:
return None
@property
def resourcePath(self):
"""This property gives a part of the URL that specifies a resource
such as an address or landmark.
:getter: Returns a string that identifies the resource path such as
address or landmark
- ``None`` for Locations services
:type: string
"""
if 'resourcePath' in self._schema_dict:
return self._schema_dict['resourcePath']
else:
return None
[docs]class Location(Schema):
"""Main class for Locations API schema
Data Fields:
:ivar version: A string containing v and an integer that specifies
the version of the Bing Maps REST Services.
- default: v1
:ivar restApi: A part of the URL path that identifies the REST
API.
- default: 'Locations' (for Locations API services)
:ivar resourcePath: A part of the URL path that specifies a
resource, such as an address or landmark.
This schema helps in creating the main parameters of all types of locations
API based services (location by address, location by point, location by
query. The above mentioned data fields make up the main parameters of the
url.
Example:
The below example can show you a typical example for how the main URL
parameters can be passed in as a dictionary
::
>>> data = {'version': 'v1',
... 'restApi': 'Locations',
... 'resourcePath': ''}
When you dump an empty dictionary, the class helps you in filling the
dictionary with default values. You can see the same behaviour in the
below example:
::
>>> data = {}
>>> schema = Location()
>>> schema.dump(data).data
OrderedDict([('version', 'v1'), ('restApi', 'Locations')])
.. note:: Location class is common for all the location based services
with the same default data.
"""
version = fields.Str(
default='v1'
)
restApi = fields.Str(
default='Locations'
)
resourcePath = fields.Str()
class Meta:
"""Meta class helps in ordering all the fields in the specified order
"""
fields = ('version', 'restApi', 'resourcePath')
ordered = True
[docs]class LocationByAddressSchema(Location, Schema):
"""Inherits from :class:`Location`
Schema for query parameters in which all the fields will be in a ordered
way. All the fields will be dumped in the same order as mentioned in the
schema.
Data Fields for the schema (taken from
https://msdn.microsoft.com/en-us/library/ff701714.aspx):
:ivar adminDistrict[Optional]: A string that contains a subdivision, such
as the abbreviation of a US state
:ivar locality[Optional]: A string that contains the locality, such as a
US city
:ivar postalCode[Optional]: An integer value that contains the postal
code, such as a US ZIP Code
:ivar addressLine[Optional]: A string specifying the street line of an
address
:ivar countryRegion[Optional]: A string specifying the ISO country code
:ivar c[Optional]: A string specifying the culture parameter
:ivar o[Optional]: A string for specifying the format of output(response)
Ex. - xml/json
- If empty, default output would be a JSON data string
- If given xml, the output would be an xml data string
:ivar includeNeighborhood[Optional]: One of the following
values
- 1: Include neighborhood information when available
- 0 [default]: Do not include neighborhood information
:ivar include[Optional]: The only value for this parameter is ciso2. When
you specify include=ciso2, the two-letter ISO country code is included
for addresses in the response
- default='ciso2'
:ivar maxResults[Optional]: An integer between 1 and 20 (number of
results)
- default=20
:ivar key[Required]: Bing maps api key
- Required
This schema helps in serializing the data.
Post-Dump:
After dumping the data, build_query_string builds up the
queryParameters string. The final value after dumping the data would
be a string.
Example:
::
>>> data = { 'adminDistrict': 'WA',
... 'locality': 'Seattle',
... 'c': 'te',
... 'o': 'xml',
... 'includeNeighborhood': 1,
... 'key': 'abs'
... }
>>> query = LocationByAddressSchema()
>>> query.dump(data).data
OrderedDict([('version', 'v1'), ('restApi', 'Locations'), ('query\
', 'adminDistrict=WA&locality=Seattle&c=te&o=xml&\
includeNeighborhood=1&include=ciso2&maxResults=20&key=abs')])
In the output, you can see 'include', 'maxResults' in the string
although we haven't specified any values because they are default
values specified in the schema.
"""
adminDistrict = fields.Str()
locality = fields.Str()
postalCode = fields.Int()
addressLine = fields.Str()
countryRegion = fields.Str()
c = fields.Str()
o = fields.Str()
includeNeighborhood = fields.Int(
default=0
)
include = fields.Str(
default='ciso2'
)
maxResults = fields.Int(
default=20
)
key = fields.Str(
required=True,
error_messages={'required': 'Please provide a key'}
)
class Meta:
"""Meta class helps in ordering all the fields in the specified order
"""
fields = ('version', 'restApi', 'resourcePath',
'adminDistrict', 'locality', 'postalCode',
'addressLine', 'countryRegion', 'c', 'o',
'includeNeighborhood', 'include', 'maxResults', 'key')
ordered = True
@post_dump
[docs] def build_query_string(self, data):
"""This method occurs after dumping the data into the class.
Args:
data (dict): dictionary of all the query values
Returns:
data (dict): ordered dict of all the values
"""
query_params = []
keys_to_be_removed = []
for key, value in data.items():
if key not in ['version', 'restApi', 'resourcePath']:
if key == 'addressLine':
query_params.append('{0}={1}'.format(key,
quote(value)))
keys_to_be_removed.append(key)
else:
query_params.append('{0}={1}'.format(key,
value))
keys_to_be_removed.append(key)
data['query'] = "&".join(query_params)
for k in keys_to_be_removed:
del data[k]
return data
[docs]class LocationByAddressUrl(LocationUrl):
"""This class helps in building a url for location by address service.
:ivar data: Data required for building up the URL
:ivar httpprotocol: http protocol for the url
:ivar schema: location by address schema to which the data will be dumped
All the URL values are retrieved from the schema.
Example:
::
>>> data = { 'adminDistrict': 'WA',
... 'locality': 'Seattle',
... 'c': 'te',
... 'o': 'xml',
... 'includeNeighborhood': 1,
... 'key': 'abs'}
>>> loc_by_address = LocationByAddressUrl(data, 'http')
>>> loc_by_address.main_url
'dev.virtualearth.net'
>>> loc_by_address.protocol
'http:/'
>>> loc_by_address.resourcePath
>>> loc_by_address.restApi
'Locations'
>>> loc_by_address.rest
'REST'
>>> loc_by_address.version
'v1'
>>> loc_by_address.query
'?adminDistrict=WA&locality=Seattle&c=te&o=xml&\
includeNeighborhood=1&include=ciso2&maxResults=20&key=abs'
"""
def __init__(self, data, httpprotocol):
schema = LocationByAddressSchema()
super().__init__(data, httpprotocol, schema)
@property
def query(self):
"""This property helps in retrieving the query part of the URL
:getter: Returns a part of the URL which consist of all the query
parameters. The query is formatted with a ``?`` in front of it.
:type: string
"""
return '?{0}'.format(self._schema_dict['query'])
[docs]class LocationByPointSchema(Location, Schema):
"""Inherits from :class:`Location`
Schema for query parameters in which all the fields will be in a ordered
way. All the fields will be dumped in the same order as mentioned in the
schema.
Data Fields for the schema (taken from
https://msdn.microsoft.com/en-us/library/ff701710.aspx):
:ivar point[Required]: A point on the Earth specified by a latitude and
longitude.
:ivar includeEntityTypes[Optional]: A comma separated list of entity types
selected from the following options:
- Address
- Neighborhood
- PopulatedPlace
- Postcode1
- AdminDivision1
- AdminDivision2
- CountryRegion
These entity types are ordered from the most specific entity to the
least specific entity. When entities of more than one entity type are
found, only the most specific entity is returned. For example, if you
specify Address and AdminDistrict1 as entity types and entities were
found for both types, only the Address entity information is returned
in the response. One exception to this rule is when both
PopulatedPlace and Neighborhood entity types are specified and
information is found for both. In this case, the information for both
entity types is returned. Also, more than one Neighborhood may be
returned because the area covered by two different neighborhoods can
overlap.
:ivar includeNeighborhood[Optional]: One of the following
values:
- 1: Include neighborhood information when available.
- 0 [default]: Do not include neighborhood information.
:ivar include[Optional]: The only value for this parameter is ciso2. When
you specify include=ciso2, the two-letter ISO country code is included
for addresses in the response.
:ivar c[Optional]: A string specifying the culture parameter
:ivar o[Optional]: A string for specifying the format of output(response)
Ex. - xml/json
- If empty, default output would be a JSON data string
- If given xml, the output would be an xml data string
:ivar maxResults[Optional]: An integer between 1 and 20 (number of
results)
- default=20
:ivar key[Required]: Bing maps api
key
- Required
Post-Dump:
After dumping the data, build_query_string builds up the
queryParameters string. The final value after dumping the data would
be a string. The string would be similar to the one mentioned in the
below example.
Example:
::
>>> data = { 'point': '47.64054,-122.12934',
... 'includeEntityTypes': 'Address',
... 'c': 'te',
... 'o': 'xml',
... 'includeNeighborhood': 1,
... 'key': 'abs'
... }
>>> query = LocationByPointSchema()
>>> query.dump(data).data
OrderedDict([('version', 'v1'), ('restApi', 'Locations'), ('query\
', '47.64054,-122.12934?includeEntityTypes=Address&\
includeNeighborhood=1&include=ciso2&c=te&o=xml&maxResults=20&key=abs')])
In the output, you can see 'include', 'maxResults' in the string
although we haven't specified any values because they are default
values specified in the schema.
"""
point = fields.Str(
required=True,
error_messages={'required': 'coordinates should be provided'}
)
includeEntityTypes = fields.Str()
includeNeighborhood = fields.Int(
default=0
)
include = fields.Str(
default='ciso2'
)
c = fields.Str()
o = fields.Str()
maxResults = fields.Int(
default=20
)
key = fields.Str(
required=True,
error_messages={'required': 'Please provide a key'}
)
class Meta:
fields = ('version', 'restApi', 'resourcePath',
'point', 'includeEntityTypes', 'includeNeighborhood',
'include', 'c', 'o', 'maxResults', 'key')
ordered = True
@post_dump
[docs] def build_query_string(self, data):
"""This method occurs after dumping the data into the class.
Args:
data (dict): dictionary of all the query values
Returns:
data (dict): ordered dict of all the values
"""
queryValues = []
keys_to_be_removed = []
for key, value in data.items():
if key not in ['version', 'restApi', 'resourcePath']:
if not key == 'point':
queryValues.append('{0}={1}'.format(key, value))
keys_to_be_removed.append(key)
keys_to_be_removed.append(key)
queryString = '&'.join(queryValues)
data['query'] = '{0}?{1}'.format(data['point'], queryString)
for k in list(set(keys_to_be_removed)):
del data[k]
return data
[docs]class LocationByPointUrl(LocationUrl):
"""This class helps in building a url for location by point service.
:ivar data: Data required for building up the URL
:ivar httpprotocol: http protocol for the url
:ivar schema: location by point schema to which the data will be dumped
All the URL values are retrieved from the schema.
Example:
::
>>> data = { 'point': '47.64054,-122.12934',
... 'includeEntityTypes': 'Address',
... 'c': 'te',
... 'o': 'xml',
... 'includeNeighborhood': 1,
... 'key': 'abs'}
>>> loc_by_point = LocationByPointUrl(data, 'http')
>>> loc_by_point.main_url
'dev.virtualearth.net'
>>> loc_by_point.protocol
'http:/'
>>> loc_by_point.resourcePath
>>> loc_by_point.restApi
'Locations'
>>> loc_by_point.rest
'REST'
>>> loc_by_point.version
'v1'
>>> loc_by_point.query
'47.64054,-122.12934?includeEntityTypes=Address&\
includeNeighborhood=1&include=ciso2&c=te&o=xml&maxResults=20&key=abs'
"""
def __init__(self, data, httpprotocol):
schema = LocationByPointSchema()
super().__init__(data, httpprotocol, schema)
@property
def query(self):
"""This property helps in retrieving the query part of the URL
:getter: Returns a part of the URL which consist of all the query
parameters
:type: string
"""
return self._schema_dict['query']
[docs]class LocationByQuerySchema(Location, Schema):
"""Inherits from :class:`Location`
Schema for query parameters in which all the fields will be in a ordered
way. All the fields will be dumped in the same order as mentioned in the
schema.
Data Fields for the schema (taken from
https://msdn.microsoft.com/en-us/library/ff701711.aspx):
:ivar q[Required]: A string (query) that contains information about a
location, such as an address or landmark name.
:ivar includeNeighborhood[Optional]: One of the following
values:
- 1: Include neighborhood information when available.
- 0 [default]: Do not include neighborhood information.
:ivar include[Optional]: One or more of the following
options:
- queryParse: Specifies that the response shows how the query string
was parsed into address values, such as addressLine, locality,
adminDistrict, and postalCode.
- ciso2: Specifies to include the two-letter ISO country code.
If you specify more than one include value, separate the values with a
comma.
:ivar c[Optional]: A string specifying the culture parameter
:ivar o[Optional]: A string for specifying the format of output(response)
Ex. - xml/json
- If empty, default output would be a JSON data string
- If given xml, the output would be an xml data string
:ivar maxResults[Optional]: An integer between 1 and 20 (number of
results)
- default=20
:ivar key[Required]: Bing maps api
key
- Required
Post-Dump:
After dumping the data, build_query_string builds up the
queryParameters string. The final value after dumping the data would
be a string. The string would be similar to the one mentioned in the
below example.
Example:
::
>>> data = {'q': '1014 Oatney Ridge Ln., Morrisville,NC-27560',
... 'key': 'abs'}
>>> query = LocationByQuerySchema()
>>> query.dump(data).data
OrderedDict([('version', 'v1'), ('restApi', 'Locations'), ('query\
', 'q=1014%20Oatney%20Ridge%20Ln.%2C%20Morrisville\
%2CNC-27560&includeNeighborhood=0&include=ciso2&maxResults=20&key=abs')])
In the output, you can see 'include', 'maxResults' in the string
although we haven't specified any values because they are default
values specified in the schema.
"""
q = fields.Str(
required=True,
error_messages={'required': 'require a query string'}
)
includeNeighborhood = fields.Int(
default=0
)
include = fields.Str(
default='ciso2'
)
c = fields.Str()
o = fields.Str()
maxResults = fields.Int(
default=20
)
key = fields.Str(
required=True,
error_messages={'required': 'Please provide a key'}
)
class Meta:
fields = ('version', 'restApi', 'resourcePath',
'q', 'includeNeighborhood', 'include', 'c', 'o',
'maxResults', 'key')
ordered = True
@post_dump
[docs] def build_query_string(self, data):
"""This method occurs after dumping the data into the class.
Args:
data (dict): dictionary of all the query values
Returns:
data (dict): ordered dict of all the values
The query string consists of all the values concatenated
with '&'. As part of the location by query services, the url will
be encoded. If the 'query' consists of spaces, commas, or other
special characters all those will be encoded.
For example:
- space(' ') - %20
- comma(,) - %2C, etc.
"""
query_params = []
keys_to_be_removed = []
for key, value in data.items():
if key not in ['version', 'restApi', 'resourcePath']:
if key == 'q':
query_params.append('{0}={1}'.format(key,
quote(value)))
keys_to_be_removed.append(key)
else:
query_params.append('{0}={1}'.format(key,
value))
keys_to_be_removed.append(key)
for k in list(set(keys_to_be_removed)):
del data[k]
data['query'] = "&".join(query_params)
return data
[docs]class LocationByQueryUrl(LocationUrl):
"""This class helps in building a url for location by query service.
:ivar data: Data required for building up the URL
:ivar httpprotocol: http protocol for the url
:ivar schema: location by query schema to which the data will be dumped
All the URL values are retrieved from the schema.
Example:
::
>>> data = {'q': '1014 Oatney Ridge Ln.,Morrisville,NC-27560',
... 'key': 'abs'}
>>> loc_by_query = LocationByQueryUrl(data, 'http')
>>> loc_by_query.main_url
'dev.virtualearth.net'
>>> loc_by_query.protocol
'http:/'
>>> loc_by_query.resourcePath
>>> loc_by_query.restApi
'Locations'
>>> loc_by_query.rest
'REST'
>>> loc_by_query.version
'v1'
>>> loc_by_query.query
'?q=1014%20Oatney%20Ridge%20Ln.%2CMorrisville\
%2CNC-27560&includeNeighborhood=0&include=ciso2&maxResults=20&key=abs'
"""
def __init__(self, data, httpprotocol):
schema = LocationByQuerySchema()
super().__init__(data, httpprotocol, schema)
@property
def query(self):
"""This property helps in retrieving the query part of the URL
:getter: Returns a part of the URL which consist of all the query
parameters. The query is formatted with a ``?`` in front of it.
:type: string
"""
return '?{0}'.format(self._schema_dict['query'])