#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# File: mijnahlib.py
"""
Main module file
Put your classes here
"""
import logging
from requests import Session
from bs4 import BeautifulSoup as Bfs
from .mijnahlibexceptions import InvalidCredentials, UnknownServerError
__author__ = '''Costas Tyfoxylos <costas.tyf@gmail.com>'''
__docformat__ = 'plaintext'
__date__ = '''10-05-2017'''
# This is the main prefix used for logging
LOGGER_BASENAME = '''mijnahlib'''
LOGGER = logging.getLogger(LOGGER_BASENAME)
LOGGER.addHandler(logging.NullHandler())
LOGIN_ERROR_MESSAGE = 'Het e-mailadres en/of wachtwoord is onjuist'
[docs]class Server(object):
"""Object modeling the server connection.
Handles the authentication and exposes all the internal parts as
attributes.
"""
def __init__(self, username, password):
logger_name = '{base}.{suffix}'.format(base=LOGGER_BASENAME,
suffix=self.__class__.__name__)
self._logger = logging.getLogger(logger_name)
self.username = username
self.password = password
self.session = Session()
self.url = 'https://www.ah.nl'
self._authenticate()
self.shopping_cart = ShoppingCart(self)
self._stores = None
self._services = None
def _authenticate(self):
data = {'userName': self.username,
'password': self.password,
'rememberUser': True}
url = '{base}/mijn/inloggen/basis'.format(base=self.url)
response = self.session.post(url, data=data)
# at this point we don't have enough information to know what went
# wrong if we didn't get a valid response.
if not response.ok:
raise UnknownServerError(response.text)
soup = Bfs(response.text, 'html.parser')
# we did not login successfully
if LOGIN_ERROR_MESSAGE in response.text:
error = soup.find('div', {'class': 'error_notices'}).text
raise InvalidCredentials(error)
# we try to get the redirect we are provided with in the meta tag
redirect = soup.find('meta').attrs['content'].split('=')[1]
success_url = '{base}{redirect}'.format(base=self.url,
redirect=redirect)
self.session.get(success_url)
return True
@property
def stores(self):
if not self._stores:
url = ('{base}/data/winkelinformatie/winkels/'
'json').format(base=self.url)
response = self.session.get(url)
data = response.json()
# self._services = [Service(values)
# for name, values in data.get('services').items()]
self._stores = [Store(info) for info in data.get('stores')]
return self._stores
#TODO implement the service and bind it with store object.
[docs]class Service(object):
def __init__(self):
pass
[docs]class Store(object):
def __init__(self, info):
self._info = info
@property
def _format(self):
return self._info.get('format')
@property
def city(self):
return self._info.get('city')
@property
def street(self):
return self._info.get('street')
@property
def street_number(self):
return self._info.get('housenr')
@property
def zip_code(self):
return self._info.get('zip')
@property
def address(self):
return u'{street} {nr} {zip} {city}'.format(street=self.street,
nr=self.street_number,
zip=self.zip_code,
city=self.city)
@property
def telephone(self):
return self._info.get('phoneNumber')
@property
def opening_times_today(self):
return self._info.get('status')
@property
def latitude(self):
return self._info.get('lat')
@property
def longtitude(self):
return self._info.get('lng')
@property
def opens_sunday(self):
return True if self._info.get('sunday') else False
@property
def opens_evenings(self):
return True if self._info.get('openEvening') else False
@property
def id(self):
return self._info.get('no')
[docs]class ItemFactory(object):
"""A factory that instantiates the appropriate object based on type"""
def __new__(cls, ah_instance, info):
product_type = info.get('type').lower()
if product_type == 'product':
return Product(ah_instance, info)
elif product_type == 'unspecifieditem':
return UnspecifiedProduct(ah_instance, info)
[docs]class Item(object):
"""Generic class of the products objects
Handles all the common parts
"""
def __init__(self, ah_instance, info):
logger_name = '{base}.{suffix}'.format(base=LOGGER_BASENAME,
suffix=self.__class__.__name__)
self._logger = logging.getLogger(logger_name)
self._ah = ah_instance
self._info = info
@property
def url(self):
"""The url of the item
:return: The url of the item
"""
_url = self._info.get('navItem', {}).get('link', {}).get('href', {})
return self._ah.url + _url
@property
def quantity(self):
"""The quantity of the items in the shopping cart
:return: The quantity of the items
"""
return self._info.get('_embedded',
{}).get('listItem', {}).get('quantity')
[docs]class UnspecifiedProduct(Item):
"""An object to model the unspecified products"""
def __init__(self, ah_instance, info):
super(UnspecifiedProduct, self).__init__(ah_instance, info)
@property
def description(self):
"""The description of the product"""
description = self._info.get('description')
return description.replace(u'\xad', u'')
def __getattr__(self, attribute):
item = self.__dict__.get(attribute)
if not item:
message = ('Unspecified Products do not support all the '
'attributes of Products. {} is not a supported '
'attribute'.format(attribute))
self._logger.warning(message)
return item
[docs]class Product(Item):
"""An object to model the products."""
def __init__(self, ah_instance, info):
super(Product, self).__init__(ah_instance, info)
@property
def _product(self):
return self._info.get('_embedded').get('product')
@property
def id(self):
"""The internal id of the item"""
return self._product.get('id')
@property
def is_orderable(self):
"""Whether the object is orderable on not
:return: A boolean of the state
"""
return self._product.get('availability', {}).get('orderable', False)
@property
def category(self):
"""The category of the product"""
return self._product.get('categoryName')
@property
def price(self):
"""The price of the product"""
return self._product.get('priceLabel', {}).get('now')
@property
def price_previously(self):
"""The previous price of the product if on discount."""
return self._product.get('priceLabel', {}).get('was')
@property
def has_discount(self):
"""Whether the object is a discounted one"""
discount = self._product.get('discount')
return True if discount else False
@property
def measurement_unit(self):
"""The measurement unit of the product according to AH"""
return self._product.get('unitSize')
@property
def description(self):
"""The description of the object"""
description = self._product.get('description')
return description.replace(u'\xad', u'')
@property
def brand(self):
"""The brand of the product"""
return self._product.get('brandName')