-
Notifications
You must be signed in to change notification settings - Fork 45
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
Closed
Changes from all commits
Commits
Show all changes
60 commits
Select commit
Hold shift + click to select a range
015a38b
Add regression test
y0hy0h fb7c4f8
Create aachen package and move parser file and tests into it
y0hy0h 4615e5c
Fix aachen_test.py import and rename to regression_test.py
y0hy0h f909889
Test parsers/ on build
y0hy0h fd8547e
Make XML output deterministic
y0hy0h b235233
Base regression test on snapshots
y0hy0h 8f15ab7
Update .gitignore to exclude virtual environment directories
y0hy0h dc8cc32
Determine parser's URL programmatically
y0hy0h 1727544
Make regression tests generic
y0hy0h 0c963c1
Flatten Aachener parser package
y0hy0h 0172252
Print feedback when updating snapshots
y0hy0h 65c2b26
Add missing build dependency
y0hy0h d673ff6
Make regression test independent of request-mock package
y0hy0h 12adcc4
Remove unneeded build dependency
y0hy0h 39ef9c7
Add copyable output on failed regression test
y0hy0h 626ce6b
Respect encoding during snapshot upgrade
y0hy0h c8f11fc
Only allow snapshot updates for individual canteens
y0hy0h a8e1538
Allow updates of all parsers-under-test' snapshots with flag
y0hy0h ad40add
Use upstream PyOpenMensa determinism instead of workaround
y0hy0h 9078f01
Detect URL's to store as snapshots automatically
y0hy0h f3d1dc0
Update snapshots for Aachen
y0hy0h acd931e
Use unittest.mock for intercepting requests
y0hy0h 811d3b6
Fix HTTP requests not being intercepted
y0hy0h ff43e5d
Pretty print website snapshot
y0hy0h 519ac05
Fix Aachener usage of requests for testability
y0hy0h ecce574
Update snapshots
y0hy0h ff3fa65
Refactor Aachener parser
y0hy0h 663f8eb
Use model for Meal
y0hy0h e7ccc7b
Use model for table entry
y0hy0h af1f8f0
Add model for category
y0hy0h 3700e45
Refactor Aachener methods
y0hy0h a618997
Move up side effects
y0hy0h 140c38b
Reduce use of LazyBuilder and fix model
y0hy0h c263bb8
Remove docstrings
y0hy0h 0740615
Move conversions from model to user
y0hy0h 8e57932
Fix category ordering
y0hy0h 2c45b52
Use model-based XML generation
y0hy0h 4d055cc
Move error handling up
y0hy0h cefb263
Remove unused LazyBuilder code
y0hy0h ba0c5c7
Move aachen into own package, implement legend parsing for new website
y0hy0h e8d1ab5
Move Aachen to own package, implement parsing for alternate website
y0hy0h c754b1b
Implement equality for model and rename feed_model to openmensa_model
y0hy0h 7567911
Fix parser bugs
y0hy0h 00f4667
Refactor OpenMensa model for encapsulation
y0hy0h 5a82bca
Move OpenMensa model to root of project
y0hy0h 824a209
Make Aachener model hashable
y0hy0h 8101d87
Determine available categories programmatically
y0hy0h 1df6cfe
Filter out meals containing unavailability disclaimer
y0hy0h 08ac1e8
Handle closed days
y0hy0h f524ad6
Update Aachener snapshots
y0hy0h cbb82e9
Defend against whitespace terrorism
y0hy0h 960463e
Factor Aachen into smaller methods
y0hy0h 7971cae
Refactor Aachener parser for readability
y0hy0h b6c05b3
Export openmensa_model in setup.py
y0hy0h 157b9bb
Use OrderedCounter
y0hy0h 74498bc
Make OpenMensa `Price` closer to XML, replace it with Aachener model
y0hy0h 2c162cc
Allow complete initialization of OpenMensa model objects
y0hy0h 3fee82c
Offload conversion from custom to OpenMensa model from parser to mode…
y0hy0h d92ec3d
Move local ignored folders out of .gitignore
y0hy0h 8814966
Rename OpenMensa model's `DayClosed` to `ClosedDay`
y0hy0h File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,2 @@ | ||
__pycache__ | ||
build | ||
.idea/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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__ | ||
|
||
|
||
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 |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from . import model | ||
from .aachen import parser |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Are these function actually needed (in all classes)? You don't seem to store these objects in maps or sets.
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 do store the
Category
s in aCounter
, which needs to know when twoCategory
s and consquentlyMeal
s 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.
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.
But this is specific for your model. You even use your own
Aachen.Category
, so this could be added there.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.
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!
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 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.
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 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.
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.
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.