Add basic API for machine states
Signed-off-by: AKP <tom@tdpain.net>
This commit is contained in:
parent
3924467a30
commit
87987553e4
5 changed files with 167 additions and 2 deletions
5
circuit-laundry-notifier/__main__.py
Normal file
5
circuit-laundry-notifier/__main__.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from web import new
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = new()
|
||||
app.run(port=8080, host="127.0.0.1")
|
|
@ -6,6 +6,10 @@ import requests
|
|||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
class ScraperError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class MachineState(Enum):
|
||||
Available = "AVAIL"
|
||||
InUse = "IN_USE"
|
||||
|
@ -25,6 +29,14 @@ class Machine:
|
|||
state: MachineState
|
||||
minutes_remaining: Optional[int]
|
||||
|
||||
def to_dict(self) -> Dict[str, Union[str, Optional[int]]]:
|
||||
return {
|
||||
"number": self.number,
|
||||
"type": self.type.value,
|
||||
"state": self.state.value,
|
||||
"minutes_remaining": self.minutes_remaining,
|
||||
}
|
||||
|
||||
|
||||
class CircuitScraper:
|
||||
_base_url: str = "https://www.circuit.co.uk/circuit-view/laundry-site"
|
||||
|
@ -46,6 +58,10 @@ class CircuitScraper:
|
|||
r = requests.get(site_url)
|
||||
r.raise_for_status()
|
||||
|
||||
# Instead of a nice 404, a bad site ID redirects us to a /circuit-view/site-unavailable with a HTTP 200.
|
||||
if "unavailable" in r.url:
|
||||
raise ScraperError("Unavailable")
|
||||
|
||||
soup = BeautifulSoup(r.content, "html.parser")
|
||||
|
||||
machine_elements = []
|
||||
|
@ -66,7 +82,7 @@ class CircuitScraper:
|
|||
descriptor_text = states[0].get_text().lower()
|
||||
|
||||
machine.type = MachineType.Dryer if "dryer" in descriptor_text else MachineType.Washer
|
||||
machine.number = descriptor_text.replace("washer", "").replace("dryer", "").strip()
|
||||
machine.number = descriptor_text.replace("washer", "").replace("dryer", "").strip().upper()
|
||||
|
||||
# Note that CircuitScraper._class_washer is included on every item, hence if it's none of the other ones are
|
||||
# present, we fall back to that one.
|
||||
|
|
24
circuit-laundry-notifier/web.py
Normal file
24
circuit-laundry-notifier/web.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import flask
|
||||
from circuit_scraper import CircuitScraper, ScraperError
|
||||
|
||||
|
||||
def new() -> flask.Flask:
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
app.add_url_rule("/api/v1/machines/<site_id>", view_func=_api_get_machines, methods=["GET"])
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def _api_get_machines(site_id: str):
|
||||
try:
|
||||
machines = CircuitScraper.get_site_machine_states(site_id)
|
||||
except ScraperError:
|
||||
return flask.jsonify({"ok": False, "message": "This laundry room is unavailable via CircuitView."}), 400
|
||||
|
||||
return flask.jsonify([machine.to_dict() for machine in sorted(
|
||||
machines,
|
||||
# It was annoying me that the machines weren't sorted by number. This sorts the machines by number, taking into
|
||||
# account the fact that some machine numbers include letters, which are quietly ignored.
|
||||
key=lambda x: int("".join(filter(lambda y: y.isdigit(), x.number)))
|
||||
)])
|
121
poetry.lock
generated
121
poetry.lock
generated
|
@ -32,6 +32,44 @@ python-versions = ">=3.6.0"
|
|||
[package.extras]
|
||||
unicode_backport = ["unicodedata2"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.3"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "flask"
|
||||
version = "2.2.2"
|
||||
description = "A simple framework for building complex web applications."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0"
|
||||
importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""}
|
||||
itsdangerous = ">=2.0"
|
||||
Jinja2 = ">=3.0"
|
||||
Werkzeug = ">=2.2.2"
|
||||
|
||||
[package.extras]
|
||||
async = ["asgiref (>=3.2)"]
|
||||
dotenv = ["python-dotenv"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.4"
|
||||
|
@ -40,6 +78,52 @@ category = "main"
|
|||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "5.0.0"
|
||||
description = "Read metadata from Python packages"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
zipp = ">=0.5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"]
|
||||
perf = ["ipython"]
|
||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.1.2"
|
||||
description = "Safely pass data to untrusted environments and back."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.2"
|
||||
description = "A very fast and expressive template engine."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.1"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.28.1"
|
||||
|
@ -79,16 +163,51 @@ brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
|
|||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "2.2.2"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.1.1"
|
||||
|
||||
[package.extras]
|
||||
watchdog = ["watchdog"]
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.10.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "jaraco.tidelift (>=1.4)"]
|
||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "jaraco.functools", "more-itertools", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "f387c917af52c11962400d12327a47dde4e322812f89078f6592ed4809432d7a"
|
||||
content-hash = "28f817d195ff20de983a656a216fbdaac203af52239ca31ed0bf9676eee92bb6"
|
||||
|
||||
[metadata.files]
|
||||
beautifulsoup4 = []
|
||||
certifi = []
|
||||
charset-normalizer = []
|
||||
click = []
|
||||
colorama = []
|
||||
flask = []
|
||||
idna = []
|
||||
importlib-metadata = []
|
||||
itsdangerous = []
|
||||
jinja2 = []
|
||||
markupsafe = []
|
||||
requests = []
|
||||
soupsieve = []
|
||||
urllib3 = []
|
||||
werkzeug = []
|
||||
zipp = []
|
||||
|
|
|
@ -9,6 +9,7 @@ license = "MIT"
|
|||
python = "^3.8"
|
||||
requests = "^2.28.1"
|
||||
beautifulsoup4 = "^4.11.1"
|
||||
Flask = "^2.2.2"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
|
|
Reference in a new issue