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

New OpenMensa model classes and use of alternative website for Aachen #70

Closed
wants to merge 60 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
015a38b
Add regression test
y0hy0h Dec 4, 2017
fb7c4f8
Create aachen package and move parser file and tests into it
y0hy0h Jan 10, 2018
4615e5c
Fix aachen_test.py import and rename to regression_test.py
y0hy0h Jan 10, 2018
f909889
Test parsers/ on build
y0hy0h Jan 11, 2018
fd8547e
Make XML output deterministic
y0hy0h Jan 11, 2018
b235233
Base regression test on snapshots
y0hy0h Jan 19, 2018
8f15ab7
Update .gitignore to exclude virtual environment directories
y0hy0h Jan 19, 2018
dc8cc32
Determine parser's URL programmatically
y0hy0h Jan 19, 2018
1727544
Make regression tests generic
y0hy0h Jan 19, 2018
0c963c1
Flatten Aachener parser package
y0hy0h Jan 19, 2018
0172252
Print feedback when updating snapshots
y0hy0h Jan 19, 2018
65c2b26
Add missing build dependency
y0hy0h Jan 19, 2018
d673ff6
Make regression test independent of request-mock package
y0hy0h Jan 21, 2018
12adcc4
Remove unneeded build dependency
y0hy0h Jan 21, 2018
39ef9c7
Add copyable output on failed regression test
y0hy0h Jan 21, 2018
626ce6b
Respect encoding during snapshot upgrade
y0hy0h Jan 21, 2018
c8f11fc
Only allow snapshot updates for individual canteens
y0hy0h Jan 22, 2018
a8e1538
Allow updates of all parsers-under-test' snapshots with flag
y0hy0h Jan 22, 2018
ad40add
Use upstream PyOpenMensa determinism instead of workaround
y0hy0h Jan 24, 2018
9078f01
Detect URL's to store as snapshots automatically
y0hy0h Feb 4, 2018
f3d1dc0
Update snapshots for Aachen
y0hy0h Feb 4, 2018
acd931e
Use unittest.mock for intercepting requests
y0hy0h Feb 5, 2018
811d3b6
Fix HTTP requests not being intercepted
y0hy0h Feb 5, 2018
ff43e5d
Pretty print website snapshot
y0hy0h Feb 5, 2018
519ac05
Fix Aachener usage of requests for testability
y0hy0h Feb 6, 2018
ecce574
Update snapshots
y0hy0h Feb 6, 2018
ff3fa65
Refactor Aachener parser
y0hy0h Jan 22, 2018
663f8eb
Use model for Meal
y0hy0h Jan 22, 2018
e7ccc7b
Use model for table entry
y0hy0h Jan 22, 2018
af1f8f0
Add model for category
y0hy0h Jan 25, 2018
3700e45
Refactor Aachener methods
y0hy0h Jan 25, 2018
a618997
Move up side effects
y0hy0h Jan 25, 2018
140c38b
Reduce use of LazyBuilder and fix model
y0hy0h Jan 25, 2018
c263bb8
Remove docstrings
y0hy0h Jan 29, 2018
0740615
Move conversions from model to user
y0hy0h Feb 2, 2018
8e57932
Fix category ordering
y0hy0h Feb 2, 2018
2c45b52
Use model-based XML generation
y0hy0h Feb 2, 2018
4d055cc
Move error handling up
y0hy0h Feb 2, 2018
cefb263
Remove unused LazyBuilder code
y0hy0h Feb 2, 2018
ba0c5c7
Move aachen into own package, implement legend parsing for new website
y0hy0h Feb 2, 2018
e8d1ab5
Move Aachen to own package, implement parsing for alternate website
y0hy0h Feb 2, 2018
c754b1b
Implement equality for model and rename feed_model to openmensa_model
y0hy0h Feb 2, 2018
7567911
Fix parser bugs
y0hy0h Feb 2, 2018
00f4667
Refactor OpenMensa model for encapsulation
y0hy0h Feb 4, 2018
5a82bca
Move OpenMensa model to root of project
y0hy0h Feb 4, 2018
824a209
Make Aachener model hashable
y0hy0h Feb 6, 2018
8101d87
Determine available categories programmatically
y0hy0h Feb 6, 2018
1df6cfe
Filter out meals containing unavailability disclaimer
y0hy0h Feb 6, 2018
08ac1e8
Handle closed days
y0hy0h Feb 6, 2018
f524ad6
Update Aachener snapshots
y0hy0h Feb 6, 2018
cbb82e9
Defend against whitespace terrorism
y0hy0h Feb 6, 2018
960463e
Factor Aachen into smaller methods
y0hy0h Feb 6, 2018
7971cae
Refactor Aachener parser for readability
y0hy0h Feb 6, 2018
b6c05b3
Export openmensa_model in setup.py
y0hy0h Feb 6, 2018
157b9bb
Use OrderedCounter
y0hy0h Feb 6, 2018
74498bc
Make OpenMensa `Price` closer to XML, replace it with Aachener model
y0hy0h Feb 7, 2018
2c162cc
Allow complete initialization of OpenMensa model objects
y0hy0h Feb 7, 2018
3fee82c
Offload conversion from custom to OpenMensa model from parser to mode…
y0hy0h Feb 7, 2018
d92ec3d
Move local ignored folders out of .gitignore
y0hy0h Feb 7, 2018
8814966
Rename OpenMensa model's `DayClosed` to `ClosedDay`
y0hy0h Feb 7, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
__pycache__
build
.idea/
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ script:
- dpkg --info ../*.deb
- dpkg --contents ../*.deb
- sudo build_scripts/travis-install-and-setup-deb.sh
- py.test-3 ./parsers -vv
- wget --output-document=/dev/null --input-file=build_scripts/test-urls.txt
- wget --output-document=/dev/null --input-file=build_scripts/maybe-urls.txt || true
after_success:
Expand Down
2 changes: 1 addition & 1 deletion build_scripts/travis-install-deps.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
set -ex
apt-get update -qq
apt-get install -qq python3 python3-setuptools python3-bs4 python3-lxml uwsgi uwsgi-plugin-python3 devscripts debhelper
apt-get install -qq python3 python3-setuptools python3-bs4 python3-lxml python3-pytest uwsgi uwsgi-plugin-python3 devscripts debhelper
151 changes: 151 additions & 0 deletions openmensa_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import lxml.etree as ET


class Canteen:
def __init__(self, days=None):
self.days = days or []

def insert(self, day):
self.days.append(day)

def to_string(self, pretty_print=True):
return ET.tostring(self.to_xml(), encoding='UTF-8', xml_declaration=True,
pretty_print=pretty_print).decode('utf-8')

def to_xml(self):
xmlns = 'http://openmensa.org/open-mensa-v2'
xsi = 'http://www.w3.org/2001/XMLSchema-instance'
schemaLocation = 'http://openmensa.org/open-mensa-v2.xsd'
openmensa = ET.Element(
'openmensa',
attrib={
'version': '2.1',
"{" + xsi + "}schemaLocation": " ".join([xmlns, schemaLocation])
},
nsmap={None: xmlns, 'xsi': xsi}
)
canteen = ET.SubElement(openmensa, 'canteen')
day_elements = [day.to_xml() for day in self.days]
canteen.extend(day_elements)

return openmensa

def __repr__(self):
return '<{}: {}>'.format(self.__class__.__name__, self.__dict__)

def __eq__(self, other):
return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are these function actually needed (in all classes)? You don't seem to store these objects in maps or sets.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I do store the Categorys in a Counter, which needs to know when two Categorys and consquently Meals are equal. The __repr__ was for debugging purposes.

I would have liked to use data classes, but I haven't found a way to use them without Pyhotn 3.7.

Copy link
Collaborator

Choose a reason for hiding this comment

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

But this is specific for your model. You even use your own Aachen.Category, so this could be added there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

While I agree that it is possible to leave it out, do those functions do harm in any way? I'd even argue it's better to implement rich comparisons and representations for custom classes.

I'm honestly just not really understanding why you want to remove them. Please tell me what you think about this!

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think they cause any harm. They just felt redundant and distracting while I read through the model. If you want to keep them, I am also fine with that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I totally understand, now. :) But except data classes in Python 3.7 there is no elegant solution, I fear...

I'd like the model to be easily usable by anyone, and those methods help with that, in my opinion.

Copy link
Owner

Choose a reason for hiding this comment

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

They are not class specific and similar in most classes. So it might be better to implement it in a shared meta class, parent class or a decorator.



class Day:
def __init__(self, date, categories=None):
self.date = date
self.categories = categories or []

def append(self, category):
self.categories.append(category)

def to_xml(self):
day_element = ET.Element('day', {'date': self.date.isoformat()})
category_elements = [category.to_xml() for category in self.categories]
day_element.extend(category_elements)
return day_element

def __repr__(self):
return '<{}: {}>'.format(self.__class__.__name__, self.__dict__)

def __eq__(self, other):
return isinstance(other, self.__class__) and self.__dict__ == other.__dict__


class ClosedDay:
def __init__(self, date):
self.date = date

def to_xml(self):
day_element = ET.Element('day', {'date': self.date.isoformat()})
ET.SubElement(day_element, 'closed')
return day_element

def __repr__(self):
return '<{}: {}>'.format(self.__class__.__name__, self.__dict__)

def __eq__(self, other):
return isinstance(other, self.__class__) and self.__dict__ == other.__dict__


class Category:
def __init__(self, name, meals=None):
self.name = name
self.meals = meals or []

def append(self, meal):
self.meals.append(meal)

def to_xml(self):
category_element = ET.Element('category', {'name': self.name})
meal_elements = [meal.to_xml() for meal in self.meals]
category_element.extend(meal_elements)
return category_element

def __repr__(self):
return '<{}: {}>'.format(self.__class__.__name__, self.__dict__)

def __eq__(self, other):
return isinstance(other, self.__class__) and self.__dict__ == other.__dict__


class Meal:
def __init__(self, name, prices=None, notes=None):
self.name = name
self.prices = prices
self.notes = notes

def to_xml(self):
meal_element = ET.Element('meal')
name = ET.SubElement(meal_element, 'name')
name.text = self.name

for note in sorted(self.notes):
note_element = ET.SubElement(meal_element, 'note')
note_element.text = note

for price in sorted(self.prices):
price_element = price.to_xml()
meal_element.append(price_element)

return meal_element

def __repr__(self):
return '<{}: {}>'.format(self.__class__.__name__, self.__dict__)

def __eq__(self, other):
return isinstance(other, self.__class__) and self.__dict__ == other.__dict__


class Price:
def __init__(self, amount, role=None):
self.amount = amount
self.role = role

def to_xml(self):
price_element = ET.Element('price')
price_format = "{:0,.2f}"
price_element.text = price_format.format(self.amount / 100)

if self.role:
price_element.set('role', self.role)

return price_element

def __repr__(self):
return '<{}: {}>'.format(self.__class__.__name__, self.__dict__)

def __eq__(self, other):
return isinstance(other, self.__class__) and self.__dict__ == other.__dict__

def __lt__(self, other):
if not isinstance(other, self.__class__):
raise TypeError(
"Cannot compare type '{}' with type '{}'.".format(type(self), type(other)))
return self.role < other.role
136 changes: 0 additions & 136 deletions parsers/aachen.py

This file was deleted.

2 changes: 2 additions & 0 deletions parsers/aachen/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import model
from .aachen import parser
Loading