15 Commits

Author SHA1 Message Date
5690080a6a Version is set to 0.4.0-rc.1
Some checks reported warnings
Tests / test (3.11) (push) Has been cancelled
Tests / test (3.8) (push) Has been cancelled
Tests / test (3.9) (push) Has been cancelled
Tests / test (3.10) (push) Has been cancelled
2023-11-25 14:21:48 +01:00
f7873ac66b Fixed installation methods
Some checks reported warnings
Tests / test (3.11) (push) Has been cancelled
Tests / test (3.8) (push) Has been cancelled
Tests / test (3.9) (push) Has been cancelled
Tests / test (3.10) (push) Has been cancelled
2023-11-25 13:20:00 +01:00
83ae0999ea Documentation prepared and improved
Some checks reported warnings
Tests / test (3.11) (push) Has been cancelled
Tests / test (3.8) (push) Has been cancelled
Tests / test (3.9) (push) Has been cancelled
Tests / test (3.10) (push) Has been cancelled
2023-11-25 13:16:48 +01:00
3656a040f4 Okay, 1.0.0 was an overkill, module is in alpha
Some checks failed
Tests / test (3.10) (push) Failing after 22m55s
Tests / test (3.11) (push) Failing after 22m56s
Tests / test (3.9) (push) Has been cancelled
Tests / test (3.8) (push) Has been cancelled
2023-11-25 12:23:33 +01:00
414f3966da Attempt to temporarily fix #2
Some checks failed
Tests / test (3.10) (push) Failing after 23m0s
Tests / test (3.11) (push) Failing after 22m55s
Tests / test (3.8) (push) Failing after 22m56s
Tests / test (3.9) (push) Failing after 22m54s
2023-11-24 23:52:50 +01:00
efedb2533b WIP: Automatic tests
Some checks failed
Tests / test (3.10) (push) Failing after 18m31s
Tests / test (3.11) (push) Failing after 16m39s
Tests / test (3.8) (push) Failing after 1m15s
Tests / test (3.9) (push) Failing after 14m41s
2023-11-24 13:35:47 +01:00
059c511e05 Removed Client from classes
Some checks failed
Tests / test (3.10) (push) Failing after 5m26s
Tests / test (3.11) (push) Failing after 5m28s
Tests / test (3.8) (push) Failing after 5m25s
Tests / test (3.9) (push) Failing after 5m23s
2023-11-24 12:44:57 +01:00
c4b7197267 Classes restructure
Some checks failed
Tests / test (3.10) (push) Failing after 1m10s
Tests / test (3.11) (push) Failing after 1m7s
Tests / test (3.8) (push) Failing after 1m11s
Tests / test (3.9) (push) Has been cancelled
2023-11-24 12:39:25 +01:00
3a65991257 This commit breaks everything 2023-11-24 12:37:24 +01:00
7b9367c0a7 Changed imports 2023-11-24 12:35:51 +01:00
f31fa65d78 Small refactor and isort+black formatting
Some checks reported warnings
Tests / test (3.11) (push) Has been cancelled
Tests / test (3.8) (push) Has been cancelled
Tests / test (3.9) (push) Has been cancelled
Tests / test (3.10) (push) Has been cancelled
2023-11-24 11:21:02 +01:00
fa4f7b83ec Drop support for Python 3.7 (due to EOL)
Some checks failed
Tests / test (3.10) (push) Failing after 5m30s
Tests / test (3.11) (push) Failing after 5m33s
Tests / test (3.8) (push) Failing after 5m29s
Tests / test (3.9) (push) Failing after 5m29s
2023-11-19 23:47:41 +01:00
b0b874bfc8 env fix
Some checks failed
Tests / test (3.10) (push) Failing after 5m31s
Tests / test (3.11) (push) Failing after 5m34s
Tests / test (3.7) (push) Failing after 39s
Tests / test (3.8) (push) Failing after 5m53s
Tests / test (3.9) (push) Failing after 5m23s
2023-11-19 23:08:09 +01:00
9733557463 Added a few basic tests 2023-11-19 23:05:48 +01:00
9282578788 Structural changes
Some checks failed
Tests / test (3.10) (push) Failing after 1m10s
Tests / test (3.7) (push) Has been cancelled
Tests / test (3.8) (push) Has been cancelled
Tests / test (3.9) (push) Has been cancelled
Tests / test (3.11) (push) Has been cancelled
2023-11-19 22:38:51 +01:00
95 changed files with 1513 additions and 1175 deletions

View File

@@ -0,0 +1,36 @@
name: Tests
on:
push:
branches:
- dev
- main
tags-ignore:
- v*
pull_request:
jobs:
test:
runs-on: ubuntu-latest
container: catthehacker/ubuntu:act-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
env:
AGENT_TOOLSDIRECTORY: /opt/hostedtoolcache
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions
- name: Test with tox
env:
RMV_TOKEN: ${{ secrets.RMV_TOKEN }}
run: tox

15
.gitignore vendored
View File

@@ -154,12 +154,9 @@ cython_debug/
# Custom
.pypirc
tests
test.bat
test_key.json
build.*
cleanup.*
publish.*
publish-release.*
.vscode
.vscode/
.venv_linux/
.venv_windows/
# Module-related
tests/output/

View File

@@ -1,11 +1,16 @@
# PythonRMV
<h1 align="center">PythonRMV</h1>
<p align="center">
<a href="https://git.end-play.xyz/profitroll/PythonRMV/src/branch/master/LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-blue"></a>
<a href="https://git.end-play.xyz/profitroll/PythonRMV"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
</p>
Small module that makes your journey with RMV REST API somehow easier. Based fully on official RMV API reference and HAFAS documentation.
## Requirements
* RMV API key (Get it [here](https://opendata.rmv.de/site/start.html))
* Python3 (Tested versions are 3.7.9 and 3.9.13)
* Python 3.8+
* git (Only for installation from source)
## Installation
@@ -14,13 +19,11 @@ If you have everything listed in [requirements](#requirements), then let's begin
### Variant 1
1. `python -m pip install pyrmv`
`python -m pip install pyrmv`
### Variant 2
1. `git clone https://git.end-play.xyz/profitroll/PythonRMV.git`
2. `cd PythonRMV`
3. `python setup.py install`
`python -m pip install git+https://git.end-play.xyz/profitroll/PythonRMV.git`
## Usage
@@ -34,7 +37,7 @@ client = pyrmv.Client("AcessId")
origin = client.stop_by_name("Frankfurt Hauptbahnhof", max_number=3)[0]
destination = client.stop_by_coords(50.099613, 8.685449, max_number=3)[0]
# Find a trip by locations got
# Find a trip by locations you got above
trip = client.trip_find(origin_id=origin.id, dest_id=destination.id)
```

69
pyproject.toml Normal file
View File

@@ -0,0 +1,69 @@
[build-system]
requires = ["setuptools>=52.0"]
build-backend = "setuptools.build_meta"
[project]
name = "pyrmv"
dynamic = ["version", "dependencies", "optional-dependencies"]
authors = [{ name = "Profitroll", email = "profitroll@end-play.xyz" }]
maintainers = [{ name = "Profitroll", email = "profitroll@end-play.xyz" }]
description = "Small module that makes your journey with RMV REST API somehow easier."
readme = "README.md"
requires-python = ">=3.8"
license = { text = "GPL3" }
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Utilities",
]
[project.urls]
Homepage = "https://git.end-play.xyz/profitroll/PythonRMV"
Source = "https://git.end-play.xyz/profitroll/PythonRMV"
Tracker = "https://git.end-play.xyz/profitroll/PythonRMV/issues"
Documentation = "https://git.end-play.xyz/profitroll/PythonRMV/wiki"
[tool.setuptools.dynamic]
version = { attr = "pyrmv.__version__" }
dependencies = { file = "requirements/_.txt" }
[tool.setuptools.dynamic.optional-dependencies]
dev = { file = "requirements/dev.txt" }
[tool.setuptools.packages.find]
where = ["src"]
[tool.black]
target-version = ['py38', 'py39', 'py310', 'py311']
line-length = 94
[tool.isort]
profile = "black"
[tool.pytest.ini_options]
minversion = "6.0"
python_files = ["test_*.py"]
pythonpath = "."
testpaths = ["tests"]
[tool.mypy]
namespace_packages = true
install_types = true
strict = true
show_error_codes = true
[tool.pylint.main]
extension-pkg-whitelist = ["ujson"]
py-version = 3.8
[tool.coverage.run]
source = ["pyrmv"]

View File

@@ -1,11 +0,0 @@
from isodate import parse_duration
class Gis():
"""Gis object."""
def __init__(self, ref: str, route: dict):
self.ref = ref
self.dist = route["dist"]
self.duration = parse_duration(route["durS"])
self.geo = route["dirGeo"]

View File

@@ -1,22 +0,0 @@
from pyrmv.classes.Stop import Stop
from pyrmv.classes.Message import Message
class Journey():
"""Journey object."""
def __init__(self, data: dict):
self.stops = []
self.ref = data["ref"]
self.direction = data["Directions"]["Direction"][0]["value"]
self.direction_flag = data["Directions"]["Direction"][0]["flag"]
self.messages = []
self.stops.extend(Stop(stop) for stop in data["Stops"]["Stop"])
if "Messages" in data:
self.messages.extend(
Message(message) for message in data["Messages"]["Message"]
)
def __str__(self) -> str:
return f"Journey with total of {len(self.stops)} stops and {len(self.messages)} messages heading {self.direction} ({self.direction_flag})"

View File

@@ -1,43 +0,0 @@
from pyrmv.classes.Gis import Gis
from pyrmv.classes.Message import Message
from pyrmv.classes.Stop import StopTrip
from isodate import parse_duration
class Leg():
"""Trip leg object."""
def __init__(self, data: dict):
self.origin = StopTrip(data["Origin"])
self.destination = StopTrip(data["Destination"])
if "GisRef" in data:
self.gis = Gis(data["GisRef"]["ref"], data["GisRoute"])
else:
self.gis = None
self.index = data["idx"]
self.name = data["name"]
self.type = data["type"]
if "direction" in data:
self.direction = data["direction"]
else:
self.direction = None
self.messages = []
if "Messages" in data:
for message in data["Messages"]["Message"]:
self.messages.append(Message(message))
if "number" in data:
self.number = data["number"]
else:
self.number = None
self.duration = parse_duration(data["duration"])
if "dist" in data:
self.distance = data["dist"]
else:
self.distance = None

View File

@@ -1,54 +0,0 @@
from datetime import datetime
class Stop():
"""Stop object."""
def __init__(self, data: dict):
self.name = data["name"]
self.id = data["id"]
if "extId" in data:
self.ext_id = data["extId"]
else:
self.ext_id = None
if "description" in data:
self.description = data["description"]
else:
self.description = None
self.lon = data["lon"]
self.lat = data["lat"]
if "routeIdx" in data:
self.route_index = data["routeIdx"]
else:
self.route_index = None
if "arrTrack" in data:
self.track_arrival = data["arrTrack"]
else:
self.track_arrival = None
if "depTrack" in data:
self.track_departure = data["depTrack"]
else:
self.track_departure = None
def __str__(self) -> str:
return f"Stop {self.name} at {self.lon}, {self.lat}"
class StopTrip(Stop):
"""Trip stop object. It's like a Stop object, but with a date and time."""
def __init__(self, data: dict):
self.type = data["type"]
self.date = datetime.strptime(data["date"], "%Y-%m-%d")
self.time = datetime.strptime(data["time"], "%H:%M:%S")
super().__init__(data)
def __str__(self) -> str:
return f"Stop {self.name} at {self.lon}, {self.lat} at {self.time.time()} {self.date.date()}"

View File

@@ -1,9 +0,0 @@
from .Board import BoardArrival, BoardDeparture, LineArrival, LineDeparture
from .Gis import Gis
from .Journey import Journey
from .Leg import Leg
from .Message import Message, Channel, Url
from .Stop import Stop, StopTrip
from .Ticket import Ticket
from .Trip import Trip
from .Client import Client

12
requirements/dev.txt Normal file
View File

@@ -0,0 +1,12 @@
black==23.11.0
build==1.0.3
isort==5.12.0
mypy==1.7.0
pylint==3.0.2
pytest-asyncio==0.22.0
pytest-cov==4.1.0
pytest==7.4.3
tox==4.11.3
twine==4.0.2
types-aiofiles==23.2.0.0
types-ujson==5.8.0.1

View File

@@ -1,3 +0,0 @@
[metadata]
description_file=README.md
license_files=LICENSE

View File

@@ -1,36 +0,0 @@
from setuptools import setup
setup(
name="pyrmv",
version="0.3.5",
author="Profitroll",
description="Small module that makes your journey with RMV REST API somehow easier.",
long_description="Small module that makes your journey with RMV REST API somehow easier. Based fully on official RMV API reference and HAFAS documentation.\n\n## Requirements\n\n* RMV API key (Get it [here](https://opendata.rmv.de/site/start.html))\n* Python3 (Tested versions are 3.7.9 and 3.9.13)\n* git (Only for installation from source)\n\n## Installation\n\nIf you have everything listed in [requirements](#requirements), then let's begin.\n\n### Variant 1\n\n1. `python -m pip install pyrmv`\n\n### Variant 2\n\n1. `git clone https://git.end-play.xyz/profitroll/PythonRMV.git`\n2. `cd PythonRMV`\n3. `python setup.py install`\n\n## Usage\n\n```py\nimport pyrmv\n\n# Define a Client with API key\nclient = pyrmv.Client(\"AcessId\")\n\n# Get origin's and destination's location\norigin = client.stop_by_name(\"Frankfurt Hauptbahnhof\", max_number=3)[0]\ndestination = client.stop_by_coords(50.099613, 8.685449, max_number=3)[0]\n\n# Find a trip by locations got\ntrip = client.trip_find(origin_id=origin.id, dest_id=destination.id)\n```\n\n## Frequently Asked Questions\n\n* [Why are there raw versions and formatted ones?](#why-are-there-raw-versions-and-formatted-ones)\n* [Some methods work slightly different](#some-methods-work-slightly-different)\n\n### Why are there raw versions and formatted ones?\n\nFor the purposes of my projects I don't really need all the stuff RMV gives (even though it's not much).\nI only need some specific things. However I do understand that in some cases other users may find\nthose methods quite useful so I implemented them as well.\n\n### Some methods work slightly different\n\nCan be. Not all function arguments written may work perfectly because I simply did not test each and\nevery request. Some of arguments may be irrelevant in my use-case and the others are used quite rare at all.\nJust [make an issue](https://git.end-play.xyz/profitroll/PythonRMV/issues/new) and I'll implement it correct when I'll have some free time.\n\n## To-Do\n\n### General\n\n* [ ] Docs in Wiki\n* [ ] Tickets",
long_description_content_type="text/markdown",
author_email="profitroll@end-play.xyz",
url="https://git.end-play.xyz/profitroll/PythonRMV",
project_urls={
"Bug Tracker": "https://git.end-play.xyz/profitroll/PythonRMV/issues",
"Documentation": "https://git.end-play.xyz/profitroll/PythonRMV/wiki",
"Source Code": "https://git.end-play.xyz/profitroll/PythonRMV.git",
},
packages=[
"pyrmv",
"pyrmv.raw",
"pyrmv.const",
"pyrmv.enums",
"pyrmv.errors",
"pyrmv.utility",
"pyrmv.classes",
],
install_requires=["requests", "xmltodict", "isodate"],
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Utilities",
],
)

View File

@@ -21,10 +21,9 @@ trip = client.trip_find(origin_id=origin.id, dest_id=destination.id)
"""
__name__ = "pyrmv"
__version__ = "0.3.5"
__version__ = "0.4.0-rc.1"
__license__ = "MIT License"
__author__ = "Profitroll"
from . import const, enums, errors, raw, utility
from .classes import *
from .classes.Client import Client
from . import classes, const, enums, errors, raw, utility
from .classes.client import Client

View File

@@ -0,0 +1,8 @@
from .board import BoardArrival, BoardDeparture, LineArrival, LineDeparture
from .gis import Gis
from .journey import Journey
from .leg import Leg
from .message import Channel, Message, Url
from .stop import Stop, StopTrip
from .ticket import Ticket
from .trip import Trip

View File

@@ -1,94 +1,106 @@
from datetime import datetime
from pyrmv.classes.Message import Message
from typing import Any, Mapping
class LineArrival():
from pyrmv.classes.message import Message
from pyrmv.utility import ref_upgrade
class LineArrival:
def __init__(self, data: Mapping[str, Any], client, retrieve_stops: bool = True):
# Upgrade is temporarily used due to RMV API mismatch
# self.journey = client.journey_detail(data["JourneyDetailRef"]["ref"])
self.journey = client.journey_detail(ref_upgrade(data["JourneyDetailRef"]["ref"]))
def __init__(self, data, client, retrieve_stops: bool = True):
self.journey = client.journey_detail(data["JourneyDetailRef"]["ref"])
self.status = data["JourneyStatus"]
self.messages = []
if "Messages" in data:
self.messages.extend(
Message(message) for message in data["Messages"]["Message"]
)
self.name = data["name"]
self.type = data["type"]
self.stop_name = data["stop"]
self.stop_id = data["stopid"]
self.stop_id_ext = data["stopExtId"]
if retrieve_stops:
self.stop = client.stop_by_id(self.stop_id)
else:
self.stop = None
self.stop = client.stop_by_id(self.stop_id) if retrieve_stops else None
self.stop = client.stop_by_id(self.stop_id)
self.time = datetime.strptime(data["time"], "%H:%M:%S")
self.date = datetime.strptime(data["date"], "%Y-%m-%d")
if ("rtTime" in data) and ("rtDate" in data):
self.time_real_time = datetime.strptime(data["rtTime"], "%H:%M:%S")
self.date_real_time = datetime.strptime(data["rtDate"], "%Y-%m-%d")
self.reachable = data["reachable"]
self.origin = data["origin"]
self.origin = data["origin"]
if "Messages" in data:
self.messages.extend(Message(message) for message in data["Messages"]["Message"])
if ("rtTime" in data) and ("rtDate" in data):
self.time_real_time = datetime.strptime(data["rtTime"], "%H:%M:%S")
self.date_real_time = datetime.strptime(data["rtDate"], "%Y-%m-%d")
else:
self.time_real_time = None
self.date_real_time = None
def __str__(self) -> str:
return f"{self.name} coming from {self.origin} at {self.time.time()} {self.date.date()}"
return (
f"{self.name} coming from {self.origin} at {self.time.time()} {self.date.date()}"
)
class LineDeparture():
def __init__(self, data, client, retrieve_stops: bool = True):
self.journey = client.journey_detail(data["JourneyDetailRef"]["ref"])
class LineDeparture:
def __init__(self, data: Mapping[str, Any], client, retrieve_stops: bool = True):
# Upgrade is temporarily used due to RMV API mismatch
# self.journey = client.journey_detail(data["JourneyDetailRef"]["ref"])
self.journey = client.journey_detail(ref_upgrade(data["JourneyDetailRef"]["ref"]))
self.status = data["JourneyStatus"]
self.messages = []
if "Messages" in data:
self.messages.extend(
Message(message) for message in data["Messages"]["Message"]
)
self.name = data["name"]
self.type = data["type"]
self.stop_name = data["stop"]
self.stop_id = data["stopid"]
self.stop_id_ext = data["stopExtId"]
if retrieve_stops:
self.stop = client.stop_by_id(self.stop_id)
else:
self.stop = None
self.stop = client.stop_by_id(self.stop_id) if retrieve_stops else None
self.time = datetime.strptime(data["time"], "%H:%M:%S")
self.date = datetime.strptime(data["date"], "%Y-%m-%d")
if ("rtTime" in data) and ("rtDate" in data):
self.time_real_time = datetime.strptime(data["rtTime"], "%H:%M:%S")
self.date_real_time = datetime.strptime(data["rtDate"], "%Y-%m-%d")
self.reachable = data["reachable"]
self.direction = data["direction"]
self.direction_flag = data["directionFlag"]
if "Messages" in data:
self.messages.extend(Message(message) for message in data["Messages"]["Message"])
if ("rtTime" in data) and ("rtDate" in data):
self.time_real_time = datetime.strptime(data["rtTime"], "%H:%M:%S")
self.date_real_time = datetime.strptime(data["rtDate"], "%Y-%m-%d")
else:
self.time_real_time = None
self.date_real_time = None
def __str__(self) -> str:
return f"{self.name} heading {self.direction} at {self.time.time()} {self.date.date()}"
return (
f"{self.name} heading {self.direction} at {self.time.time()} {self.date.date()}"
)
class BoardArrival(list):
def __init__(self, data: dict, client, retrieve_stops: bool = True):
def __init__(self, data: Mapping[str, Any], client, retrieve_stops: bool = True):
super().__init__([])
if "Arrival" not in data:
return
for line in data["Arrival"]:
self.append(LineArrival(line, client, retrieve_stops=retrieve_stops))
def __str__(self) -> str:
lines = []
for line in self:
lines.append(str(line))
return "Arrival board\n" + "\n".join(lines)
return "Arrival board\n" + "\n".join([str(line) for line in self])
class BoardDeparture(list):
def __init__(self, data: dict, client, retrieve_stops: bool = True):
def __init__(self, data: Mapping[str, Any], client, retrieve_stops: bool = True):
super().__init__([])
if "Departure" not in data:
return
for line in data["Departure"]:
self.append(LineDeparture(line, client, retrieve_stops=retrieve_stops))
def __str__(self) -> str:
lines = []
for line in self:
lines.append(str(line))
return "Departure board\n" + "\n".join(lines)
return "Departure board\n" + "\n".join([str(line) for line in self])

View File

@@ -1,23 +1,33 @@
from datetime import datetime, timedelta
from typing import List, OrderedDict, Union
from pyrmv.classes import *
from pyrmv.classes import (
BoardArrival,
BoardDeparture,
Journey,
Message,
Stop,
StopTrip,
Trip,
)
from pyrmv.enums import *
from pyrmv.raw import board_arrival as raw_board_arrival
from pyrmv.raw.board_departure import board_departure as raw_board_departure
from pyrmv.raw.journey_detail import journey_detail as raw_journey_detail
from pyrmv.raw.stop_by_name import stop_by_name as raw_stop_by_name
from pyrmv.raw.stop_by_coords import stop_by_coords as raw_stop_by_coords
from pyrmv.raw.trip_find import trip_find as raw_trip_find
from pyrmv.raw.trip_recon import trip_recon as raw_trip_recon
from pyrmv.raw.him_search import him_search as raw_him_search
from pyrmv.utility.find_exception import find_exception
from pyrmv.raw import board_departure as raw_board_departure
from pyrmv.raw import him_search as raw_him_search
from pyrmv.raw import journey_detail as raw_journey_detail
from pyrmv.raw import stop_by_coords as raw_stop_by_coords
from pyrmv.raw import stop_by_name as raw_stop_by_name
from pyrmv.raw import trip_find as raw_trip_find
from pyrmv.raw import trip_recon as raw_trip_recon
from pyrmv.utility import find_exception
try:
from typing import Literal
except ImportError:
from typing_extensions import Literal
class Client():
class Client:
"""The main class in the whole module. Is used to use all non-raw methods.
More detailed docs for each method can be found by using IDE's docstring
@@ -69,7 +79,8 @@ class Client():
def __init__(self, access_id: str) -> None:
self.access_id = access_id
def board_arrival(self,
def board_arrival(
self,
id: Union[str, None] = None,
id_ext: Union[str, None] = None,
direction: Union[str, Stop, StopTrip, None] = None,
@@ -80,8 +91,13 @@ class Client():
operators: Union[str, list, None] = None,
lines: Union[str, list, None] = None,
passlist: bool = False,
board_type: Literal[BoardArrivalType.ARR, BoardArrivalType.ARR_EQUIVS, BoardArrivalType.ARR_MAST, BoardArrivalType.ARR_STATION] = BoardArrivalType.ARR,
retrieve_stops: bool = True
board_type: Literal[
BoardArrivalType.ARR,
BoardArrivalType.ARR_EQUIVS,
BoardArrivalType.ARR_MAST,
BoardArrivalType.ARR_STATION,
] = BoardArrivalType.ARR,
retrieve_stops: bool = True,
) -> BoardArrival:
"""Method returns a board with arriving transport.
@@ -105,7 +121,7 @@ class Client():
* BoardArrival: Instance of `BoardArrival` object.
"""
if (isinstance(direction, Stop) or isinstance(direction, StopTrip)):
if isinstance(direction, (Stop, StopTrip)):
direction = direction.id
board_raw = raw_board_arrival(
@@ -120,14 +136,15 @@ class Client():
operators=operators,
lines=lines,
passlist=passlist,
boardType=board_type.code
boardType=board_type.code,
)
find_exception(board_raw)
return BoardArrival(board_raw, self, retrieve_stops=retrieve_stops)
def board_departure(self,
def board_departure(
self,
id: Union[str, None] = None,
id_ext: Union[str, None] = None,
direction: Union[str, Stop, StopTrip, None] = None,
@@ -138,8 +155,13 @@ class Client():
operators: Union[str, list, None] = None,
lines: Union[str, list, None] = None,
passlist: bool = False,
board_type: Literal[BoardDepartureType.DEP, BoardDepartureType.DEP_EQUIVS, BoardDepartureType.DEP_MAST, BoardDepartureType.DEP_STATION] = BoardDepartureType.DEP,
retrieve_stops: bool = True
board_type: Literal[
BoardDepartureType.DEP,
BoardDepartureType.DEP_EQUIVS,
BoardDepartureType.DEP_MAST,
BoardDepartureType.DEP_STATION,
] = BoardDepartureType.DEP,
retrieve_stops: bool = True,
) -> BoardDeparture:
"""Method returns a board with departing transport.
@@ -163,7 +185,7 @@ class Client():
* BoardDeparture: Instance of `BoardDeparture` object.
"""
if (isinstance(direction, Stop) or isinstance(direction, StopTrip)):
if isinstance(direction, (Stop, StopTrip)):
direction = direction.id
board_raw = raw_board_departure(
@@ -178,14 +200,15 @@ class Client():
operators=operators,
lines=lines,
passlist=passlist,
boardType=board_type.code
boardType=board_type.code,
)
find_exception(board_raw)
return BoardDeparture(board_raw, self, retrieve_stops=retrieve_stops)
def him_search(self,
def him_search(
self,
date_begin: Union[str, datetime, None] = None,
date_end: Union[str, datetime, None] = None,
time_begin: Union[str, datetime, None] = None,
@@ -203,11 +226,22 @@ class Client():
station_to: Union[str, Stop, None] = None,
both_ways: Union[bool, None] = None,
train_names: Union[list, None] = None,
search_mode: Union[Literal[SearchMode.MATCH, SearchMode.NOMATCH, SearchMode.TFMATCH], None] = None,
affected_journey_mode: Union[Literal[AffectedJourneyMode.ALL, AffectedJourneyMode.OFF], None] = None,
affected_journey_stop_mode: Union[Literal[AffectedJourneyStopMode.ALL, AffectedJourneyStopMode.IMP, AffectedJourneyStopMode.OFF], None] = None,
search_mode: Union[
Literal[SearchMode.MATCH, SearchMode.NOMATCH, SearchMode.TFMATCH], None
] = None,
affected_journey_mode: Union[
Literal[AffectedJourneyMode.ALL, AffectedJourneyMode.OFF], None
] = None,
affected_journey_stop_mode: Union[
Literal[
AffectedJourneyStopMode.ALL,
AffectedJourneyStopMode.IMP,
AffectedJourneyStopMode.OFF,
],
None,
] = None,
priority_min: Union[int, None] = None,
priority_max: Union[int, None] = None
priority_max: Union[int, None] = None,
) -> List[Message]:
"""The him_search method will deliver a list of HIM messages if matched by the given criteria as
well as affected products if any.
@@ -257,20 +291,13 @@ class Client():
new_stations.append(stop)
stations = new_stations
if search_mode == None:
search_mode = None
else:
search_mode = search_mode.code
if affected_journey_mode == None:
affected_journey_mode = None
else:
affected_journey_mode = affected_journey_mode.code
if affected_journey_stop_mode == None:
affected_journey_stop_mode = None
else:
affected_journey_stop_mode = affected_journey_stop_mode.code
search_mode = None if search_mode is None else search_mode.code
affected_journey_mode = (
None if affected_journey_mode is None else affected_journey_mode.code
)
affected_journey_stop_mode = (
None if affected_journey_stop_mode is None else affected_journey_stop_mode.code
)
messages = []
messages_raw = raw_him_search(
@@ -296,25 +323,34 @@ class Client():
affectedJourneyMode=affected_journey_mode, # type: ignore
affectedJourneyStopMode=affected_journey_stop_mode, # type: ignore
maxprio=priority_max,
minprio=priority_min
minprio=priority_min,
)
find_exception(messages_raw)
if "Message" in messages_raw:
for message in messages_raw["Message"]:
messages.append(Message(message))
messages.extend(Message(message) for message in messages_raw["Message"])
return messages
def journey_detail(self,
def journey_detail(
self,
id: str,
date: Union[str, datetime, None] = None,
real_time_mode: Union[Literal[RealTimeMode.FULL, RealTimeMode.INFOS, RealTimeMode.OFF, RealTimeMode.REALTIME, RealTimeMode.SERVER_DEFAULT], None] = None,
real_time_mode: Union[
Literal[
RealTimeMode.FULL,
RealTimeMode.INFOS,
RealTimeMode.OFF,
RealTimeMode.REALTIME,
RealTimeMode.SERVER_DEFAULT,
],
None,
] = None,
from_id: Union[str, None] = None,
from_index: Union[int, None] = None,
to_id: Union[str, None] = None,
to_index: Union[int, None] = None
to_index: Union[int, None] = None,
) -> Journey:
"""The journey_detail method will deliver information about the complete route of a vehicle. The journey
identifier is part of a trip or `board_departure()` response. It contains a list of all stops/stations of this journey
@@ -336,10 +372,7 @@ class Client():
* Journey: Instance of `Journey` object.
"""
if real_time_mode == None:
real_time_mode = None
else:
real_time_mode = real_time_mode.code
real_time_mode = None if real_time_mode is None else real_time_mode.code
journey_raw = raw_journey_detail(
accessId=self.access_id,
@@ -349,22 +382,43 @@ class Client():
fromId=from_id,
fromIdx=from_index,
toId=to_id,
toIdx=to_index
toIdx=to_index,
)
find_exception(journey_raw)
return Journey(journey_raw)
def stop_by_coords(self,
def stop_by_coords(
self,
coords_lat: Union[str, float],
coords_lon: Union[str, float],
lang: Literal[Language.DE, Language.DA, Language.EN, Language.ES, Language.FR, Language.HU, Language.IT, Language.NL, Language.NO, Language.PL, Language.SV, Language.TR] = Language.EN,
lang: Literal[
Language.DE,
Language.DA,
Language.EN,
Language.ES,
Language.FR,
Language.HU,
Language.IT,
Language.NL,
Language.NO,
Language.PL,
Language.SV,
Language.TR,
] = Language.EN,
radius: Union[int, float] = 1000,
max_number: int = 10,
stop_type: Literal[LocationType.S, LocationType.P, LocationType.SP, LocationType.SE, LocationType.SPE] = LocationType.S,
selection_mode: Union[Literal[SelectionMode.SLCT_A, SelectionMode.SLCT_N], None] = None,
stop_type: Literal[
LocationType.S,
LocationType.P,
LocationType.SP,
LocationType.SE,
LocationType.SPE,
] = LocationType.S,
selection_mode: Union[
Literal[SelectionMode.SLCT_A, SelectionMode.SLCT_N], None
] = None,
) -> List[Stop]:
"""Method returns a list of stops around a given center coordinate.
The returned results are ordered by their distance to the center coordinate.
@@ -384,10 +438,7 @@ class Client():
* List[Stop]: List of `Stop` objects. Empty list if none found.
"""
if selection_mode == None:
selection_mode = None
else:
selection_mode = selection_mode.code
selection_mode = None if selection_mode is None else selection_mode.code
stops = []
stops_raw = raw_stop_by_coords(
@@ -398,7 +449,7 @@ class Client():
radius=radius,
maxNo=max_number,
stopType=stop_type.code,
locationSelectionMode=selection_mode # type: ignore
locationSelectionMode=selection_mode, # type: ignore
)
find_exception(stops_raw)
@@ -412,9 +463,23 @@ class Client():
return stops
def stop_by_id(self,
def stop_by_id(
self,
query: str,
lang: Literal[Language.DE, Language.DA, Language.EN, Language.ES, Language.FR, Language.HU, Language.IT, Language.NL, Language.NO, Language.PL, Language.SV, Language.TR] = Language.EN,
lang: Literal[
Language.DE,
Language.DA,
Language.EN,
Language.ES,
Language.FR,
Language.HU,
Language.IT,
Language.NL,
Language.NO,
Language.PL,
Language.SV,
Language.TR,
] = Language.EN,
) -> Union[Stop, None]:
"""Method can be used to get Stop object whilst only having id available.
@@ -427,15 +492,14 @@ class Client():
"""
stops_raw = raw_stop_by_name(
accessId=self.access_id,
inputString=query,
lang=lang.code,
maxNo=1
accessId=self.access_id, inputString=query, lang=lang.code, maxNo=1
)
find_exception(stops_raw)
if len(stops_raw["stopLocationOrCoordLocation"]) > 0:
if len(stops_raw["stopLocationOrCoordLocation"]) <= 0:
return None
stop = stops_raw["stopLocationOrCoordLocation"][0]
if "StopLocation" in stop:
@@ -444,21 +508,45 @@ class Client():
return Stop(stop["CoordLocation"])
else:
return None
else:
return None
def stop_by_name(self,
def stop_by_name(
self,
query: str,
lang: Literal[Language.DE, Language.DA, Language.EN, Language.ES, Language.FR, Language.HU, Language.IT, Language.NL, Language.NO, Language.PL, Language.SV, Language.TR] = Language.EN,
lang: Literal[
Language.DE,
Language.DA,
Language.EN,
Language.ES,
Language.FR,
Language.HU,
Language.IT,
Language.NL,
Language.NO,
Language.PL,
Language.SV,
Language.TR,
] = Language.EN,
max_number: int = 10,
stop_type: Literal[LocationType.A, LocationType.ALL, LocationType.AP, LocationType.P, LocationType.S, LocationType.SA, LocationType.SP] = LocationType.ALL,
selection_mode: Union[Literal[SelectionMode.SLCT_A, SelectionMode.SLCT_N], None] = None,
stop_type: Literal[
LocationType.A,
LocationType.ALL,
LocationType.AP,
LocationType.P,
LocationType.S,
LocationType.SA,
LocationType.SP,
] = LocationType.ALL,
selection_mode: Union[
Literal[SelectionMode.SLCT_A, SelectionMode.SLCT_N], None
] = None,
coord_lat: Union[str, float, None] = None,
coord_lon: Union[str, float, None] = None,
radius: Union[int, float] = 1000,
refine_id: Union[str, None] = None,
stations: Union[str, list, None] = None,
filter_mode: Literal[FilterMode.DIST_PERI, FilterMode.EXCL_PERI, FilterMode.SLCT_PERI] = FilterMode.DIST_PERI
filter_mode: Literal[
FilterMode.DIST_PERI, FilterMode.EXCL_PERI, FilterMode.SLCT_PERI
] = FilterMode.DIST_PERI,
) -> List[Stop]:
"""Method can be used to perform a pattern matching of a user input and to retrieve a list
of possible matches in the journey planner database. Possible matches might be stops/stations,
@@ -483,10 +571,7 @@ class Client():
* List[Stop]: List of `Stop` objects. Empty list if none found.
"""
if selection_mode == None:
selection_mode = None
else:
selection_mode = selection_mode.code
selection_mode = None if selection_mode is None else selection_mode.code
stops = []
stops_raw = raw_stop_by_name(
@@ -501,7 +586,7 @@ class Client():
radius=radius,
refineId=refine_id,
stations=stations,
filterMode=filter_mode.code
filterMode=filter_mode.code,
)
find_exception(stops_raw)
@@ -515,65 +600,72 @@ class Client():
return stops
def trip_find(self,
lang: Literal[Language.DE, Language.DA, Language.EN, Language.ES, Language.FR, Language.HU, Language.IT, Language.NL, Language.NO, Language.PL, Language.SV, Language.TR] = Language.EN,
def trip_find(
self,
lang: Literal[
Language.DE,
Language.DA,
Language.EN,
Language.ES,
Language.FR,
Language.HU,
Language.IT,
Language.NL,
Language.NO,
Language.PL,
Language.SV,
Language.TR,
] = Language.EN,
origin_id: Union[str, None] = None,
origin_id_ext: Union[str, None] = None,
origin_coord_lat: Union[str, float, None] = None,
origin_coord_lon: Union[str, float, None] = None,
origin_coord_name: Union[str, None] = None,
destination_id: Union[str, None] = None,
destination_id_ext: Union[str, None] = None,
destination_coord_lat: Union[str, float, None] = None,
destination_coord_lon: Union[str, float, None] = None,
destination_coord_name: Union[str, None] = None,
via: Union[str, None] = None,
via_id: Union[str, None] = None,
via_gis: Union[str, None] = None,
via_wait_time: int = 0,
avoid: Union[str, None] = None,
avoid_id: Union[str, None] = None,
change_time_percent: int = 100,
change_time_min: Union[int, None] = None,
change_time_max: Union[int, None] = None,
change_time_add: Union[int, None] = None,
change_max: Union[int, None] = None,
date: Union[str, datetime, None] = None,
time: Union[str, datetime, None] = None,
search_arrival: bool = False,
trips_after_time: Union[int, None] = None,
trips_before_time: Union[int, None] = None,
context: Union[str, None] = None,
passlist: bool = False,
operators: Union[str, list, None] = None,
lines: Union[str, list, None] = None,
lineids: Union[str, list, None] = None,
iv_include: bool = False,
iv_only: bool = False,
bike_carriage: bool = False,
passing_points: bool = False,
real_time_mode: Union[Literal[RealTimeMode.FULL, RealTimeMode.INFOS, RealTimeMode.OFF, RealTimeMode.REALTIME, RealTimeMode.SERVER_DEFAULT], None] = None,
real_time_mode: Union[
Literal[
RealTimeMode.FULL,
RealTimeMode.INFOS,
RealTimeMode.OFF,
RealTimeMode.REALTIME,
RealTimeMode.SERVER_DEFAULT,
],
None,
] = None,
include_earlier: bool = False,
ict_alternatives: bool = False,
tariff: Union[bool, None] = None,
messages: bool = False,
frequency: bool = True
frequency: bool = True,
) -> List[Trip]:
"""The trip service calculates a trip from a specified origin to a specified destination. These might be
stop/station IDs or coordinates based on addresses and points of interest validated by the location service or
@@ -629,84 +721,64 @@ class Client():
* List[Trip]: List of `Trip` objects. Empty list if none found.
"""
if real_time_mode == None:
real_time_mode = None
else:
real_time_mode = real_time_mode.code
real_time_mode = None if real_time_mode is None else real_time_mode.code
trips = []
trips_raw = raw_trip_find(
accessId=self.access_id,
lang=lang.code,
originId=origin_id,
originExtId=origin_id_ext,
originCoordLat=origin_coord_lat,
originCoordLong=origin_coord_lon,
originCoordName=origin_coord_name,
destId=destination_id,
destExtId=destination_id_ext,
destCoordLat=destination_coord_lat,
destCoordLong=destination_coord_lon,
destCoordName=destination_coord_name,
via=via,
viaId=via_id,
viaGis=via_gis,
viaWaitTime=via_wait_time,
avoid=avoid,
avoidId=avoid_id,
changeTimePercent=change_time_percent,
minChangeTime=change_time_min,
maxChangeTime=change_time_max,
addChangeTime=change_time_add,
maxChange=change_max,
date=date,
time=time,
searchForArrival=search_arrival,
numF=trips_after_time,
numB=trips_before_time,
context=context,
passlist=passlist,
operators=operators,
lines=lines,
lineids=lineids,
includeIv=iv_include,
ivOnly=iv_only,
bikeCarriage=bike_carriage,
showPassingPoints=passing_points,
rtMode=real_time_mode, # type: ignore
includeEarlier=include_earlier,
withICTAlternatives=ict_alternatives,
tariff=tariff,
trafficMessages=messages,
withFreq=frequency
withFreq=frequency,
)
find_exception(trips_raw)
if "Trip" in trips_raw:
for trip in trips_raw["Trip"]:
trips.append(Trip(trip))
trips.extend(Trip(trip) for trip in trips_raw["Trip"])
return trips
def trip_recon(self,
def trip_recon(
self,
context: Union[str, Trip],
date: Union[str, datetime, None] = None,
match_real_time: Union[bool, None] = None,
@@ -717,9 +789,18 @@ class Client():
departure_dev_upper: Union[int, None] = None,
passlist: bool = False,
passing_points: bool = False,
real_time_mode: Union[Literal[RealTimeMode.FULL, RealTimeMode.INFOS, RealTimeMode.OFF, RealTimeMode.REALTIME, RealTimeMode.SERVER_DEFAULT], None] = None,
real_time_mode: Union[
Literal[
RealTimeMode.FULL,
RealTimeMode.INFOS,
RealTimeMode.OFF,
RealTimeMode.REALTIME,
RealTimeMode.SERVER_DEFAULT,
],
None,
] = None,
tariff: Union[bool, None] = None,
messages: bool = False
messages: bool = False,
) -> List[Trip]:
"""Reconstructing a trip can be achieved using the reconstruction context provided by any trip result in the
`ctx_recon` attribute of `Trip` object. The result will be a true copy of the original trip search result given
@@ -746,10 +827,7 @@ class Client():
* List[Trip]: List of `Trip` objects. Empty list if none found.
"""
if real_time_mode == None:
real_time_mode = None
else:
real_time_mode = real_time_mode.code
real_time_mode = None if real_time_mode is None else real_time_mode.code
if isinstance(context, Trip):
context = context.ctx_recon
@@ -775,7 +853,6 @@ class Client():
find_exception(trips_raw)
if "Trip" in trips_raw:
for trip in trips_raw["Trip"]:
trips.append(Trip(trip))
trips.extend(Trip(trip) for trip in trips_raw["Trip"])
return trips

13
src/pyrmv/classes/gis.py Normal file
View File

@@ -0,0 +1,13 @@
from typing import Any, Mapping
from isodate import parse_duration
class Gis:
"""Gis object."""
def __init__(self, ref: str, route: Mapping[str, Any]):
self.ref = ref
self.dist = route["dist"]
self.duration = parse_duration(route["durS"])
self.geo = route["dirGeo"]

View File

@@ -0,0 +1,27 @@
from typing import Any, Mapping
from pyrmv.classes.message import Message
from pyrmv.classes.stop import Stop
from pyrmv.utility import ref_upgrade
class Journey:
"""Journey object."""
def __init__(self, data: Mapping[str, Any]):
self.stops = []
# Upgrade is temporarily used due to RMV API mismatch
# self.ref = data["ref"]
self.ref = ref_upgrade(data["ref"])
self.direction = data["Directions"]["Direction"][0]["value"]
self.direction_flag = data["Directions"]["Direction"][0]["flag"]
self.stops.extend(Stop(stop) for stop in data["Stops"]["Stop"])
self.messages = []
if "Messages" in data:
self.messages.extend(Message(message) for message in data["Messages"]["Message"])
def __str__(self) -> str:
return f"Journey with total of {len(self.stops)} stops and {len(self.messages)} messages heading {self.direction} ({self.direction_flag})"

29
src/pyrmv/classes/leg.py Normal file
View File

@@ -0,0 +1,29 @@
from typing import Any, Mapping
from isodate import parse_duration
from pyrmv.classes.gis import Gis
from pyrmv.classes.message import Message
from pyrmv.classes.stop import StopTrip
class Leg:
"""Trip leg object."""
def __init__(self, data: Mapping[str, Any]):
self.origin = StopTrip(data["Origin"])
self.destination = StopTrip(data["Destination"])
self.gis = (
None if "GisRef" not in data else Gis(data["GisRef"]["ref"], data["GisRoute"])
)
self.messages = []
self.index = data["idx"]
self.name = data["name"]
self.type = data["type"]
self.direction = data.get("direction")
self.number = data.get("number")
self.duration = parse_duration(data["duration"])
self.distance = data.get("dist")
if "Messages" in data:
self.messages.extend(Message(message) for message in data["Messages"]["Message"])

View File

@@ -1,25 +1,29 @@
from pyrmv.classes.Stop import Stop
from datetime import datetime
from typing import Any, Mapping
from isodate import parse_duration
class Url():
from pyrmv.classes.stop import Stop
class Url:
"""Traffic message channel url object."""
def __init__(self, data: dict) -> None:
def __init__(self, data: Mapping[str, Any]) -> None:
self.name = data["name"]
self.url = data["url"]
def __str__(self) -> str:
return f"{self.name}: {self.url}"
class Channel():
class Channel:
"""Traffic message channel object."""
def __init__(self, data: dict) -> None:
def __init__(self, data: Mapping[str, Any]) -> None:
self.name = data["name"]
url = []
for link in url:
url.append(Url(link))
url.extend(Url(link) for link in url)
self.url = url
self.time_start = datetime.strptime(data["validFromTime"], "%H:%M:%S")
self.date_start = datetime.strptime(data["validFromDate"], "%Y-%m-%d")
@@ -30,30 +34,17 @@ class Channel():
return f"{self.name}: from {self.time_start} {self.date_start} until {self.time_end} {self.date_end}"
class Message():
class Message:
"""Traffic message object."""
def __init__(self, data: dict) -> None:
def __init__(self, data: Mapping[str, Any]) -> None:
self.affected_stops = []
if "affectedStops" in data:
for stop in data["affectedStops"]["StopLocation"]:
self.affected_stops.append(Stop(stop))
if "validFromStop" in data:
self.valid_from_stop = Stop(data["validFromStop"])
else:
self.valid_from_stop = None
if "validToStop" in data:
self.valid_to_stop = Stop(data["validToStop"])
else:
self.valid_to_stop = None
self.valid_from_stop = (
None if "validFromStop" not in data else Stop(data["validFromStop"])
)
self.valid_to_stop = None if "validToStop" not in data else Stop(data["validToStop"])
self.channels = []
for channel in data["channel"]:
self.channels.append(Channel(channel))
self.channels.extend(Channel(channel) for channel in data["channel"])
self.id = data["id"]
self.active = data["act"]
self.head = data["head"]
@@ -74,11 +65,12 @@ class Message():
self.date_modified = datetime.strptime(data["modDate"], "%Y-%m-%d")
self.daily_start = datetime.strptime(data["dailyStartingAt"], "%H:%M:%S")
self.daily_duration = parse_duration(data["dailyDuration"])
self.base_type = data["baseType"] if "baseType" in data else None
if "baseType" in data:
self.base_type = data["baseType"]
else:
self.base_type = None
if "affectedStops" in data:
self.affected_stops.extend(
Stop(stop) for stop in data["affectedStops"]["StopLocation"]
)
def __str__(self) -> str:
return f"{self.base_type} message with priority {self.products} valid from {self.time_start.time()} {self.date_start.date()} until {self.time_end.time()} {self.date_end.date()}: {self.head} - {self.lead}"

32
src/pyrmv/classes/stop.py Normal file
View File

@@ -0,0 +1,32 @@
from datetime import datetime
class Stop:
"""Stop object."""
def __init__(self, data: dict):
self.name = data["name"]
self.id = data["id"]
self.ext_id = data.get("extId")
self.description = data.get("description")
self.lon = data["lon"]
self.lat = data["lat"]
self.route_index = data.get("routeIdx")
self.track_arrival = data.get("arrTrack")
self.track_departure = data.get("depTrack")
def __str__(self) -> str:
return f"Stop {self.name} at {self.lon}, {self.lat}"
class StopTrip(Stop):
"""Trip stop object. It's like a Stop object, but with a date and time."""
def __init__(self, data: dict):
self.type = data["type"]
self.date = datetime.strptime(data["date"], "%Y-%m-%d")
self.time = datetime.strptime(data["time"], "%H:%M:%S")
super().__init__(data)
def __str__(self) -> str:
return f"Stop {self.name} at {self.lon}, {self.lat} at {self.time.time()} {self.date.date()}"

View File

@@ -1,4 +1,4 @@
class Ticket():
class Ticket:
"""
{
"externalContent": {

View File

@@ -1,37 +1,28 @@
from pyrmv.classes.Leg import Leg
from pyrmv.classes.Stop import StopTrip
from isodate import parse_duration
class Trip():
from pyrmv.classes.leg import Leg
from pyrmv.classes.stop import StopTrip
class Trip:
"""Trip object."""
def __init__(self, data: dict):
self.raw_data = data
self.origin = StopTrip(data["Origin"])
self.destination = StopTrip(data["Destination"])
self.legs = []
for leg in data["LegList"]["Leg"]:
self.legs.append(Leg(leg))
self.legs.extend(Leg(leg) for leg in data["LegList"]["Leg"])
self.calculation = data["calculation"]
self.index = data["idx"]
self.id = data["tripId"]
self.ctx_recon = data["ctxRecon"]
self.duration = parse_duration(data["duration"])
if "rtDuration" in data:
self.real_time_duration = parse_duration(data["rtDuration"])
else:
self.real_time_duration = None
self.real_time_duration = (
None if "rtDuration" not in data else parse_duration(data["rtDuration"])
)
self.checksum = data["checksum"]
if "transferCount" in data:
self.transfer_count = data["transferCount"]
else:
self.transfer_count = 0
self.transfer_count = data.get("transferCount", 0)
def __str__(self) -> str:
return f"Trip from {self.origin.name} to {self.destination.name} lasting {self.duration} ({self.real_time_duration}) with {len(self.legs)} legs and {self.transfer_count} transfers"

View File

@@ -0,0 +1 @@
from .product import PRODUCTS

View File

@@ -1,10 +1,10 @@
from .product import Product
from .rt_mode import RealTimeMode
from .lang import Language
from .location_type import LocationType
from .selection_mode import SelectionMode
from .search_mode import SearchMode
from .filter_mode import FilterMode
from .board_type import BoardArrivalType, BoardDepartureType
from .aff_journey_mode import AffectedJourneyMode
from .aff_journey_stop_mode import AffectedJourneyStopMode
from .board_type import BoardArrivalType, BoardDepartureType
from .filter_mode import FilterMode
from .lang import Language
from .location_type import LocationType
from .product import Product
from .rt_mode import RealTimeMode
from .search_mode import SearchMode
from .selection_mode import SelectionMode

View File

@@ -1,6 +1,8 @@
from enum import auto
from .auto_name import AutoName
class AffectedJourneyMode(AutoName):
"""Enumeration used to declare types of HIM search modes."""

View File

@@ -1,6 +1,8 @@
from enum import auto
from .auto_name import AutoName
class AffectedJourneyStopMode(AutoName):
"""Enumeration used to declare types of affected journey stops return modes."""

View File

@@ -2,7 +2,9 @@
# https://github.com/pyrogram/pyrogram/blob/master/pyrogram/enums/auto_name.py
from enum import Enum
from pyrmv.const.product import PRODUCTS
from pyrmv.const import PRODUCTS
class AutoName(Enum):
def __init__(self, code) -> None:
@@ -14,6 +16,7 @@ class AutoName(Enum):
def __repr__(self):
return f"pyrmv.enums.{self}"
class AutoNameProduct(AutoName):
def __init__(self, code) -> None:
self.code = PRODUCTS[code]

View File

@@ -1,6 +1,8 @@
from enum import auto
from .auto_name import AutoName
class BoardArrivalType(AutoName):
"""Enumeration used to declare types of arrival board.
@@ -22,6 +24,7 @@ class BoardArrivalType(AutoName):
ARR_STATION = auto()
"Arrival board with all journeys at any masts of the requested station"
class BoardDepartureType(AutoName):
"""Enumeration used to declare types of departure board.

View File

@@ -1,6 +1,8 @@
from enum import auto
from .auto_name import AutoName
class FilterMode(AutoName):
"""Enumeration used to declare filters for nearby searches.

View File

@@ -1,24 +1,44 @@
from enum import auto
from .auto_name import AutoName
class Language(AutoName):
"""Enumeration used to declare locales as ISO-3166 codes (but only available in HAFAS ones)."""
DE = auto()
"German"
AR = auto()
"Arabic"
CA = auto()
"Catalan, Valencian"
DA = auto()
"Danish"
DE = auto()
"German"
EL = auto()
"Greek"
EN = auto()
"English"
ES = auto()
"Spanish"
FI = auto()
"Finnish"
FR = auto()
"French"
HI = auto()
"Hindi"
HR = auto()
"Croatian"
HU = auto()
"Hungarian"
@@ -34,8 +54,26 @@ class Language(AutoName):
PL = auto()
"Polish"
RU = auto()
"Russian"
SK = auto()
"Slovak"
SL = auto()
"Slovenian"
SV = auto()
"Swedish"
TL = auto()
"Tagalog"
TR = auto()
"Turkish"
UR = auto()
"Urdu"
ZH = auto()
"Chinese"

View File

@@ -1,6 +1,8 @@
from enum import auto
from .auto_name import AutoName
class LocationType(AutoName):
"""Enumeration used to declare types of location filter.

View File

@@ -1,6 +1,8 @@
from enum import auto
from .auto_name import AutoNameProduct
class Product(AutoNameProduct):
"""Enumeration used to declare types of transport."""
@@ -11,7 +13,7 @@ class Product(AutoNameProduct):
"InterCity (commonly abbreviated IC on timetables and tickets) is the classification applied to certain long-distance passenger train services in Europe. Such trains (in contrast to regional, local, or commuter trains) generally call at major stations only."
EC = auto()
"EuroCity, abbreviated as EC, is a cross-border train category within the European inter-city rail network. In contrast to trains allocated to the lower-level \"IC\" (InterCity) category, EC trains are international services that meet 20 criteria covering comfort, speed, food service, and cleanliness."
'EuroCity, abbreviated as EC, is a cross-border train category within the European inter-city rail network. In contrast to trains allocated to the lower-level "IC" (InterCity) category, EC trains are international services that meet 20 criteria covering comfort, speed, food service, and cleanliness.'
R = auto()
"Regional rail, also known as local trains and stopping trains, are passenger rail services that operate between towns and cities. These trains operate with more stops over shorter distances than inter-city rail, but fewer stops and faster service than commuter rail."

View File

@@ -1,6 +1,8 @@
from enum import auto
from .auto_name import AutoName
class RealTimeMode(AutoName):
"""Enumeration used to declare types of real-time traffic."""

View File

@@ -1,6 +1,8 @@
from enum import auto
from .auto_name import AutoName
class SearchMode(AutoName):
"""Enumeration used to declare types of HIM search modes."""

View File

@@ -1,6 +1,8 @@
from enum import auto
from .auto_name import AutoName
class SelectionMode(AutoName):
"""Enumeration used to declare location selection modes.

View File

@@ -0,0 +1,7 @@
from .api_errors import *
from .int_errors import *
from .not_ready import *
from .ps_errors import *
from .sot_errors import *
from .svc_errors import *
from .unknown_error import *

View File

@@ -2,32 +2,38 @@ class ApiAuthError(Exception):
"""
Access denied for accessId provided.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class ApiQuotaError(Exception):
"""
Quota exceeded for accessId provided.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class ApiTooManyRequests(Exception):
"""
Too many requests.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class ApiParamError(Exception):
"""Exception raised for errors in the input arguments.
@@ -42,12 +48,14 @@ class ApiParamError(Exception):
super().__init__(self.errorText)
def __str__(self):
return f'{self.errorCode} -> {self.errorText}'
return f"{self.errorCode} -> {self.errorText}"
class ApiFormatError(Exception):
"""
Response format not supported.
"""
def __init__(self):
super().__init__(self.__doc__)

View File

@@ -2,26 +2,31 @@ class IntError(Exception):
"""
Internal RMV server error.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class IntGatewayError(Exception):
"""
Communication error with RMV backend systems.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class IntTimeoutError(Exception):
"""
Timeout during service processing.
"""
def __init__(self):
super().__init__(self.__doc__)

View File

@@ -2,6 +2,7 @@ class NotReadyYetError(Exception):
"""
This method is not finished yet.
"""
def __init__(self):
super().__init__(self.__doc__)

View File

@@ -6,6 +6,7 @@ class PsIncorrectParamError(Exception):
forward/backward from the end/beginning of the
connection.
"""
def __init__(self):
super().__init__(self.__doc__)

View File

@@ -2,46 +2,55 @@ class SotAlreadyArrivedError(Exception):
"""
Trip already arrived.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SotNotStartedError(Exception):
"""
Trip not started.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SotCancelledError(Exception):
"""
Trip cancelled.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SotAllTrainsFilteredError(Exception):
"""
All trips filtered.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SotStayOnTripError(Exception):
"""
No change. Stay on trip.
"""
def __init__(self):
super().__init__(self.__doc__)

View File

@@ -12,132 +12,158 @@ class SvcParamError(Exception):
super().__init__(self.errorText)
def __str__(self):
return f'{self.errorCode} -> {self.errorText}'
return f"{self.errorCode} -> {self.errorText}"
class SvcLocationError(Exception):
"""
Location missing or invalid.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SvcLocationArrivalError(Exception):
"""
Arrival location missing or invalid.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SvcLocationDepartureError(Exception):
"""
Departure location missing or invalid.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SvcLocationViaError(Exception):
"""
Unknown change stop.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SvcLocationEqualError(Exception):
"""
Origin/destination or vias are equal.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SvcLocationNearError(Exception):
"""
Origin and destination are too close.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SvcDatetimeError(Exception):
"""
Date/time are missing or invalid.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SvcDatetimePeriodError(Exception):
"""
Date/time are not in timetable or allowed period.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SvcProductError(Exception):
"""
Product field missing or invalid.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SvcContextError(Exception):
"""
Context is invalid.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SvcNoResultError(Exception):
"""
No result found.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SvcSearchError(Exception):
"""
Unsuccessful search.
"""
def __init__(self):
super().__init__(self.__doc__)
def __str__(self):
return self.__doc__
class SvcNoMatchError(Exception):
"""
No match found.
"""
def __init__(self):
super().__init__(self.__doc__)

View File

@@ -12,4 +12,4 @@ class UnknownError(Exception):
super().__init__(self.errorText)
def __str__(self):
return f'{self.errorCode} -> {self.errorText}'
return f"{self.errorCode} -> {self.errorText}"

View File

@@ -1,6 +1,7 @@
from requests import get
from datetime import datetime, timedelta
from typing import Union
from requests import get
from xmltodict import parse as xmlparse
try:
@@ -8,8 +9,10 @@ try:
except ImportError:
from typing_extensions import Literal
# 2.25. Arrival Board (arrivalBoard)
def board_arrival(accessId: str,
def board_arrival(
accessId: str,
json: bool = True,
id: Union[str, None] = None,
extId: Union[str, None] = None,
@@ -25,7 +28,7 @@ def board_arrival(accessId: str,
attributes: Union[str, list, None] = None,
platforms: Union[str, list, None] = None,
passlist: bool = False,
boardType: Literal["ARR", "ARR_EQUIVS", "ARR_MAST", "ARR_STATION"] = "ARR"
boardType: Literal["ARR", "ARR_EQUIVS", "ARR_MAST", "ARR_STATION"] = "ARR",
) -> dict:
"""The arrival board can be retrieved by a call to the arrivalBoard service. This method will return the next
arrivals from a given point in time within a duration covered time span. The default duration size is 60 minutes.
@@ -58,12 +61,8 @@ def board_arrival(accessId: str,
* dict: Output from RMV. Dict will contain "errorCode" and "errorText" if exception occurs.
"""
if json:
headers = {"Accept": "application/json"}
else:
headers = {"Accept": "application/xml"}
payload = {}
headers = {"Accept": "application/json"} if json else {"Accept": "application/xml"}
for var, val in locals().items():
if str(var) == "date":
@@ -74,16 +73,12 @@ def board_arrival(accessId: str,
payload[str(var)] = val
elif str(var) == "time":
if val != None:
if isinstance(val, datetime):
payload[str(var)] = val.strftime("%H:%M")
else:
payload[str(var)] = val
payload[str(var)] = (
val.strftime("%H:%M") if isinstance(val, datetime) else val
)
elif str(var) == "duration":
if val != None:
if isinstance(val, timedelta):
payload[str(var)] = val.minutes # type: ignore
else:
payload[str(var)] = val
payload[str(var)] = val.minutes if isinstance(val, timedelta) else val
elif str(var) == "boardType":
if val != None:
payload["type"] = val.upper()
@@ -93,7 +88,4 @@ def board_arrival(accessId: str,
output = get("https://www.rmv.de/hapi/arrivalBoard", params=payload, headers=headers)
if json:
return output.json()
else:
return xmlparse(output.content)
return output.json() if json else xmlparse(output.content)

View File

@@ -1,6 +1,7 @@
from requests import get
from datetime import datetime, timedelta
from typing import Union
from requests import get
from xmltodict import parse as xmlparse
try:
@@ -8,8 +9,10 @@ try:
except ImportError:
from typing_extensions import Literal
# 2.24. Departure Board (departureBoard)
def board_departure(accessId: str,
def board_departure(
accessId: str,
json: bool = True,
id: Union[str, None] = None,
extId: Union[str, None] = None,
@@ -25,7 +28,7 @@ def board_departure(accessId: str,
attributes: Union[str, list, None] = None,
platforms: Union[str, list, None] = None,
passlist: bool = False,
boardType: Literal["DEP", "DEP_EQUIVS", "DEP_MAST", "DEP_STATION"] = "DEP"
boardType: Literal["DEP", "DEP_EQUIVS", "DEP_MAST", "DEP_STATION"] = "DEP",
) -> dict:
"""The separture board can be retrieved by a call to the departureBoard service. This method will return the
next departures (or less if not existing) from a given point in time within a duration covered time span. The
@@ -59,12 +62,8 @@ def board_departure(accessId: str,
* dict: Output from RMV. Dict will contain "errorCode" and "errorText" if exception occurs.
"""
if json:
headers = {"Accept": "application/json"}
else:
headers = {"Accept": "application/xml"}
payload = {}
headers = {"Accept": "application/json"} if json else {"Accept": "application/xml"}
for var, val in locals().items():
if str(var) == "date":
@@ -75,16 +74,12 @@ def board_departure(accessId: str,
payload[str(var)] = val
elif str(var) == "time":
if val != None:
if isinstance(val, datetime):
payload[str(var)] = val.strftime("%H:%M")
else:
payload[str(var)] = val
payload[str(var)] = (
val.strftime("%H:%M") if isinstance(val, datetime) else val
)
elif str(var) == "duration":
if val != None:
if isinstance(val, timedelta):
payload[str(var)] = val.minutes # type: ignore
else:
payload[str(var)] = val
payload[str(var)] = val.minutes if isinstance(val, timedelta) else val
elif str(var) == "boardType":
if val != None:
payload["type"] = val.upper()
@@ -94,7 +89,4 @@ def board_departure(accessId: str,
output = get("https://www.rmv.de/hapi/departureBoard", params=payload, headers=headers)
if json:
return output.json()
else:
return xmlparse(output.content)
return output.json() if json else xmlparse(output.content)

View File

@@ -1,17 +1,20 @@
from requests import get
from typing import OrderedDict, Union
from xmltodict import parse as xmlparse
from datetime import datetime
from typing import OrderedDict, Union
from pyrmv.utility.weekdays_bitmask import weekdays_bitmask
from requests import get
from xmltodict import parse as xmlparse
from pyrmv.utility import weekdays_bitmask
try:
from typing import Literal
except ImportError:
from typing_extensions import Literal
# 2.37. HIM Search (himsearch)
def him_search(accessId: str,
def him_search(
accessId: str,
json: bool = True,
dateB: Union[str, datetime, None] = None,
dateE: Union[str, datetime, None] = None,
@@ -44,7 +47,7 @@ def him_search(accessId: str,
affectedJourneyStopMode: Union[Literal["ALL", "IMP", "OFF"], None] = None,
orderBy: Union[str, list, None] = None,
minprio: Union[str, int, None] = None,
maxprio: Union[str, int, None] = None
maxprio: Union[str, int, None] = None,
) -> dict:
"""The himSearch will return a list of HIM messages if matched by the given criteria as well as affected
products if any.
@@ -91,26 +94,21 @@ def him_search(accessId: str,
* dict: Output from RMV. Dict will contain "errorCode" and "errorText" if exception occurs.
"""
if json:
headers = {"Accept": "application/json"}
else:
headers = {"Accept": "application/xml"}
payload = {}
headers = {"Accept": "application/json"} if json else {"Accept": "application/xml"}
for var, val in locals().items():
if str(var) in ["dateB", "dateE"]:
if str(var) in {"dateB", "dateE"}:
if val != None:
if isinstance(val, datetime):
payload[str(var)] = val.strftime("%Y-%m-%d")
else:
payload[str(var)] = val
elif str(var) in ["timeB", "timeE"]:
elif str(var) in {"timeB", "timeE"}:
if val != None:
if isinstance(val, datetime):
payload[str(var)] = val.strftime("%H:%M")
else:
payload[str(var)] = val
payload[str(var)] = (
val.strftime("%H:%M") if isinstance(val, datetime) else val
)
elif str(var) == "weekdays":
if val != None:
if isinstance(val, OrderedDict):
@@ -132,7 +130,4 @@ def him_search(accessId: str,
output = get("https://www.rmv.de/hapi/himsearch", params=payload, headers=headers)
if json:
return output.json()
else:
return xmlparse(output.content)
return output.json() if json else xmlparse(output.content)

View File

@@ -1,6 +1,7 @@
from datetime import datetime
from requests import get
from typing import Union
from requests import get
from xmltodict import parse as xmlparse
try:
@@ -8,8 +9,10 @@ try:
except ImportError:
from typing_extensions import Literal
# 2.26. Journey Detail (journeyDetail)
def journey_detail(accessId: str,
def journey_detail(
accessId: str,
id: str,
json: bool = True,
date: Union[str, datetime, None] = None,
@@ -21,7 +24,7 @@ def journey_detail(accessId: str,
fromIdx: Union[int, None] = None,
toId: Union[str, None] = None,
toIdx: Union[int, None] = None,
baim: bool = False
baim: bool = False,
) -> dict:
"""The journey_detail method will deliver information about the complete route of a vehicle. The journey
identifier is part of a trip or departureBoard response. It contains a list of all stops/stations of this journey
@@ -49,12 +52,8 @@ def journey_detail(accessId: str,
* dict: Output from RMV. Dict will contain "errorCode" and "errorText" if exception occurs.
"""
if json:
headers = {"Accept": "application/json"}
else:
headers = {"Accept": "application/xml"}
payload = {}
headers = {"Accept": "application/json"} if json else {"Accept": "application/xml"}
for var, val in locals().items():
if str(var) == "rtMode":
@@ -66,7 +65,4 @@ def journey_detail(accessId: str,
output = get("https://www.rmv.de/hapi/journeyDetail", params=payload, headers=headers)
if json:
return output.json()
else:
return xmlparse(output.content)
return output.json() if json else xmlparse(output.content)

View File

@@ -1,5 +1,6 @@
from requests import get
from typing import Union
from requests import get
from xmltodict import parse as xmlparse
try:
@@ -7,11 +8,15 @@ try:
except ImportError:
from typing_extensions import Literal
# 2.4. Location Search by Coordinate (location.nearbystops)
def stop_by_coords(accessId: str,
def stop_by_coords(
accessId: str,
originCoordLat: Union[str, float],
originCoordLong: Union[str, float],
lang: Literal["de", "da", "en", "es", "fr", "hu", "it", "nl", "no", "pl", "sv", "tr"] = "en",
lang: Literal[
"de", "da", "en", "es", "fr", "hu", "it", "nl", "no", "pl", "sv", "tr"
] = "en",
json: bool = True,
radius: Union[int, float] = 1000,
maxNo: int = 10,
@@ -20,7 +25,7 @@ def stop_by_coords(accessId: str,
products: Union[int, None] = None,
meta: Union[str, None] = None,
sattributes: Union[str, list, None] = None,
sinfotexts: Union[str, list, None] = None
sinfotexts: Union[str, list, None] = None,
) -> dict:
"""The location.nearbystops service returns a list of stops around a given center coordinate (within a
radius of 1000m). The returned results are ordered by their distance to the center coordinate.
@@ -46,12 +51,8 @@ def stop_by_coords(accessId: str,
* dict: Output from RMV. Dict will contain "errorCode" and "errorText" if exception occurs.
"""
if json:
headers = {"Accept": "application/json"}
else:
headers = {"Accept": "application/xml"}
payload = {}
headers = {"Accept": "application/json"} if json else {"Accept": "application/xml"}
for var, val in locals().items():
if str(var) == "stopType":
@@ -64,9 +65,8 @@ def stop_by_coords(accessId: str,
if val != None:
payload[str(var)] = val
output = get("https://www.rmv.de/hapi/location.nearbystops", params=payload, headers=headers)
output = get(
"https://www.rmv.de/hapi/location.nearbystops", params=payload, headers=headers
)
if json:
return output.json()
else:
return xmlparse(output.content)
return output.json() if json else xmlparse(output.content)

View File

@@ -1,5 +1,6 @@
from requests import get
from typing import Union
from requests import get
from xmltodict import parse as xmlparse
try:
@@ -7,10 +8,14 @@ try:
except ImportError:
from typing_extensions import Literal
# 2.3. Location Search by Name (location.name)
def stop_by_name(accessId: str,
def stop_by_name(
accessId: str,
inputString: str,
lang: Literal["de", "da", "en", "es", "fr", "hu", "it", "nl", "no", "pl", "sv", "tr"] = "en",
lang: Literal[
"de", "da", "en", "es", "fr", "hu", "it", "nl", "no", "pl", "sv", "tr"
] = "en",
json: bool = True,
maxNo: int = 10,
stopType: Literal["A", "ALL", "AP", "P", "S", "SA", "SP"] = "ALL",
@@ -23,7 +28,7 @@ def stop_by_name(accessId: str,
meta: Union[str, None] = None,
stations: Union[str, list, None] = None,
sattributes: Union[str, list, None] = None,
filterMode: Literal["DIST_PERI", "EXCL_PERI", "SLCT_PERI"] = "DIST_PERI"
filterMode: Literal["DIST_PERI", "EXCL_PERI", "SLCT_PERI"] = "DIST_PERI",
) -> dict:
"""The location.name service can be used to perform a pattern matching of a user input and to retrieve a list
of possible matches in the journey planner database. Possible matches might be stops/stations, points of
@@ -57,12 +62,8 @@ def stop_by_name(accessId: str,
* dict: Output from RMV. Dict will contain "errorCode" and "errorText" if exception occurs.
"""
if json:
headers = {"Accept": "application/json"}
else:
headers = {"Accept": "application/xml"}
payload = {}
headers = {"Accept": "application/json"} if json else {"Accept": "application/xml"}
for var, val in locals().items():
if str(var) == "inputString":
@@ -80,7 +81,4 @@ def stop_by_name(accessId: str,
output = get("https://www.rmv.de/hapi/location.name", params=payload, headers=headers)
if json:
return output.json()
else:
return xmlparse(output.content)
return output.json() if json else xmlparse(output.content)

View File

@@ -1,6 +1,7 @@
from datetime import datetime
from requests import get
from typing import List, Union
from requests import get
from xmltodict import parse as xmlparse
try:
@@ -8,119 +9,97 @@ try:
except ImportError:
from typing_extensions import Literal
# 2.12. Trip Search (trip)
def trip_find(accessId: str,
lang: Literal["de", "da", "en", "es", "fr", "hu", "it", "nl", "no", "pl", "sv", "tr"] = "en",
json: bool = True,
def trip_find(
accessId: str,
lang: Literal[
"de", "da", "en", "es", "fr", "hu", "it", "nl", "no", "pl", "sv", "tr"
] = "en",
json: bool = True,
originId: Union[str, None] = None,
originExtId: Union[str, None] = None,
originCoordLat: Union[str, float, None] = None,
originCoordLong: Union[str, float, None] = None,
originCoordName: Union[str, None] = None,
destId: Union[str, None] = None,
destExtId: Union[str, None] = None,
destCoordLat: Union[str, float, None] = None,
destCoordLong: Union[str, float, None] = None,
destCoordName: Union[str, None] = None,
via: Union[str, None] = None,
viaId: Union[str, None] = None,
viaWaitTime: int = 0,
avoid: Union[str, None] = None,
avoidId: Union[str, None] = None,
viaGis: Union[str, None] = None,
changeTimePercent: int = 100,
minChangeTime: Union[int, None] = None,
maxChangeTime: Union[int, None] = None,
addChangeTime: Union[int, None] = None,
maxChange: Union[int, None] = None,
date: Union[str, datetime, None] = None,
time: Union[str, datetime, None] = None,
searchForArrival: bool = False,
numF: Union[int, None] = None,
numB: Union[int, None] = None,
context: Union[str, None] = None,
poly: bool = False,
polyEnc: Literal["DLT", "GPA", "N"] = "N",
passlist: bool = False,
products: Union[int, None] = None,
operators: Union[str, list, None] = None,
attributes: Union[str, list, None] = None,
sattributes: Union[str, list, None] = None,
fattributes: Union[str, list, None] = None,
lines: Union[str, list, None] = None,
lineids: Union[str, list, None] = None,
avoidPaths: Union[List[Literal["SW", "EA", "ES", "RA", "CB"]], None] = None,
originWalk: Union[str, list, None] = None,
originBike: Union[str, list, None] = None,
originCar: Union[str, list, None] = None,
originTaxi: Union[str, list, None] = None,
originPark: Union[str, list, None] = None,
originMeta: Union[str, list, None] = None,
destWalk: Union[str, list, None] = None,
destBike: Union[str, list, None] = None,
destCar: Union[str, list, None] = None,
destTaxi: Union[str, list, None] = None,
destPark: Union[str, list, None] = None,
destMeta: Union[str, list, None] = None,
totalWalk: Union[str, list, None] = None,
totalBike: Union[str, list, None] = None,
totalCar: Union[str, list, None] = None,
totalTaxi: Union[str, list, None] = None,
totalMeta: Union[str, list, None] = None,
gisProducts: Union[str, None] = None,
includeIv: bool = False,
ivOnly: bool = False,
mobilityProfile: Union[str, None] = None,
bikeCarriage: bool = False,
bikeCarriageType: Union[Literal["SINGLEBIKES", "SMALLGROUPS", "LARGEGROUPS"], None] = None,
bikeCarriageType: Union[
Literal["SINGLEBIKES", "SMALLGROUPS", "LARGEGROUPS"], None
] = None,
sleepingCar: bool = False,
couchetteCoach: bool = False,
showPassingPoints: bool = False,
baim: bool = False,
eco: bool = False,
ecoCmp: bool = False,
ecoParams: Union[str, None] = None,
rtMode: Union[Literal["FULL", "INFOS", "OFF", "REALTIME", "SERVER_DEFAULT"], None] = None,
unsharp: bool = False,
trainFilter: Union[str, None] = None,
economic: bool = False,
groupFilter: Union[str, None] = None,
blockingList: Union[str, None] = None,
blockedEdges: Union[str, None] = None,
trainComposition: bool = False,
includeEarlier: bool = False,
withICTAlternatives: bool = False,
tariff: Union[bool, None] = None,
trafficMessages: bool = False,
travellerProfileData: Union[str, None] = None,
withFreq: bool = True
withFreq: bool = True,
) -> dict:
"""The trip service calculates a trip from a specified origin to a specified destination. These might be
stop/station IDs or coordinates based on addresses and points of interest validated by the location service or
@@ -219,12 +198,8 @@ def trip_find(accessId: str,
* dict: Output from RMV. Dict will contain "errorCode" and "errorText" if exception occurs.
"""
if json:
headers = {"Accept": "application/json"}
else:
headers = {"Accept": "application/xml"}
payload = {}
headers = {"Accept": "application/json"} if json else {"Accept": "application/xml"}
for var, val in locals().items():
if str(var) == "date":
@@ -235,10 +210,9 @@ def trip_find(accessId: str,
payload[str(var)] = val
elif str(var) == "time":
if val != None:
if isinstance(val, datetime):
payload[str(var)] = val.strftime("%H:%M")
else:
payload[str(var)] = val
payload[str(var)] = (
val.strftime("%H:%M") if isinstance(val, datetime) else val
)
elif str(var) == "rtMode":
if val != None:
payload["rtMode"] = val.upper()
@@ -248,7 +222,4 @@ def trip_find(accessId: str,
output = get("https://www.rmv.de/hapi/trip", params=payload, headers=headers)
if json:
return output.json()
else:
return xmlparse(output.content)
return output.json() if json else xmlparse(output.content)

View File

@@ -1,6 +1,7 @@
from datetime import datetime
from requests import get
from typing import Union
from requests import get
from xmltodict import parse as xmlparse
try:
@@ -8,8 +9,9 @@ try:
except ImportError:
from typing_extensions import Literal
# 2.17. Reconstruction (recon)
def trip_recon(accessId: str,
def trip_recon(
accessId: str,
ctx: str,
json: bool = True,
poly: bool = False,
@@ -38,7 +40,7 @@ def trip_recon(accessId: str,
ecoParams: Union[str, None] = None,
tariff: Union[bool, None] = None,
trafficMessages: Union[bool, None] = None,
travellerProfileData: Union[str, None] = None
travellerProfileData: Union[str, None] = None,
) -> dict:
"""Reconstructing a trip can be achieved using the reconstruction context provided by any trip result in the
ctxRecon attribute of Trip element. The result will be a true copy of the original trip search result given
@@ -82,12 +84,8 @@ def trip_recon(accessId: str,
* dict: Output from RMV. Dict will contain "errorCode" and "errorText" if exception occurs.
"""
if json:
headers = {"Accept": "application/json"}
else:
headers = {"Accept": "application/xml"}
payload = {}
headers = {"Accept": "application/json"} if json else {"Accept": "application/xml"}
for var, val in locals().items():
if str(var) == "date":
@@ -102,7 +100,4 @@ def trip_recon(accessId: str,
output = get("https://www.rmv.de/hapi/recon", params=payload, headers=headers)
if json:
return output.json()
else:
return xmlparse(output.content)
return output.json() if json else xmlparse(output.content)

View File

@@ -0,0 +1,3 @@
from .find_exception import find_exception
from .journey_ref_converter import ref_upgrade
from .weekdays_bitmask import weekdays_bitmask

View File

@@ -1,9 +1,34 @@
from pyrmv.errors.api_errors import ApiAuthError, ApiFormatError, ApiParamError, ApiQuotaError, ApiTooManyRequests
from pyrmv.errors.int_errors import IntError, IntGatewayError, IntTimeoutError
from pyrmv.errors.ps_errors import PsIncorrectParamError
from pyrmv.errors.sot_errors import SotAllTrainsFilteredError, SotAlreadyArrivedError, SotCancelledError, SotNotStartedError, SotStayOnTripError
from pyrmv.errors.svc_errors import SvcContextError, SvcDatetimeError, SvcDatetimePeriodError, SvcLocationArrivalError, SvcLocationDepartureError, SvcLocationEqualError, SvcLocationError, SvcLocationNearError, SvcLocationViaError, SvcNoMatchError, SvcNoResultError, SvcProductError, SvcSearchError
from pyrmv.errors.unknown_error import UnknownError
from pyrmv.errors import (
ApiAuthError,
ApiFormatError,
ApiParamError,
ApiQuotaError,
ApiTooManyRequests,
IntError,
IntGatewayError,
IntTimeoutError,
PsIncorrectParamError,
SotAllTrainsFilteredError,
SotAlreadyArrivedError,
SotCancelledError,
SotNotStartedError,
SotStayOnTripError,
SvcContextError,
SvcDatetimeError,
SvcDatetimePeriodError,
SvcLocationArrivalError,
SvcLocationDepartureError,
SvcLocationEqualError,
SvcLocationError,
SvcLocationNearError,
SvcLocationViaError,
SvcNoMatchError,
SvcNoResultError,
SvcProductError,
SvcSearchError,
UnknownError,
)
def find_exception(data: dict):
"""Scan returned dict for errorCode from RMV.
@@ -16,7 +41,6 @@ def find_exception(data: dict):
* Any: Formatted as "errorCode -> errorText" if ApiParamError and UnknownError or as a single massage for others.
"""
if "errorCode" in data:
if data["errorCode"] == "API_AUTH":
raise ApiAuthError()

View File

@@ -0,0 +1,26 @@
def ref_upgrade(ref: str) -> str:
"""This function converts older journey refs to the newer ones.
### WARNING
This function will be deprecated as soon as RMV updates their API
### Args:
* ref (`str`): Old ref like this one: `2|#VN#1#ST#1700765441#PI#0#ZI#160749#TA#0#DA#241123#1S#3004646#1T#2228#LS#3006907#LT#2354#PU#80#RT#1#CA#S30#ZE#S1#ZB# S1#PC#3#FR#3004646#FT#2228#TO#3006907#TT#2354#`
### Raises:
* `KeyError`: Some required keys are not found in the ref provided
### Returns:
* `str`: Ref of the new type
"""
items = "|".join(ref.split("|")[1:]).strip("#").split("#")
result = {items[i]: items[i + 1] for i in range(0, len(items), 2)}
for required in ["VN", "ZI", "TA", "PU"]:
if required not in result:
raise KeyError(
f"Required key {required} in the old journey ref is not found during conversion to the newer journey ref"
)
return "|".join([result["VN"], result["ZI"], result["TA"], result["PU"]])

View File

@@ -10,12 +10,9 @@ def weekdays_bitmask(data: OrderedDict[str, bool]) -> str:
### Returns:
* str: _description_
"""
output = ""
if len(data) != 7:
raise ValueError("OrderedDict must be formatted as follows: OrderedDict(Monday=bool, Tuesday=bool, Wednesday=bool, Thursday=bool, Friday=bool, Saturday=bool, Sunday=bool)")
for day in data:
if data[day]:
output += "1"
else:
output += "0"
return output
raise ValueError(
"OrderedDict must be formatted as follows: OrderedDict(Monday=bool, Tuesday=bool, Wednesday=bool, Thursday=bool, Friday=bool, Saturday=bool, Sunday=bool)"
)
return "".join("1" if data[day] else "0" for day in data)

82
test.py
View File

@@ -1,82 +0,0 @@
from datetime import datetime, timedelta
from os import makedirs
from typing import Any
from ujson import loads, dumps, JSONDecodeError
from test_colors import *
import pyrmv
try:
with open("test_key.json", mode="r", encoding="utf-8") as file:
key = loads(file.read())["key"]
file.close()
except FileNotFoundError:
key = input("File 'test_key.json' does not exist. Enter your RMV API key to continue: ").strip()
key_save = input("To skip entering your key every time I can save your apikey here. Should I do that? (Y/n) > ").strip().lower()
if key_save == "y":
try:
with open("test_key.json", mode="w", encoding="utf-8") as file:
file.write(dumps({"key": key}))
file.close()
except Exception as exp:
print(f"Could not save 'test_key.json' with your apikey due to {exp}", flush=True)
else:
print("To skip entering your key every time create file 'test_key.json' with your apikey as a value for 'key' dict key.", flush=True)
def test(name: str, data: Any, raw: bool = False) -> None:
makedirs("tests", exist_ok=True)
if isinstance(data, dict):
if "errorCode" in data:
print(f"{BBLACK}[{BRED}ER{BBLACK}] {RESET}Test of {YELLOW}{name} {RESET}did not go well. ErrorCode is {BRED}{data['errorCode']}{RESET}. Check {CYAN}tests/{name}.json {RESET}for more information.", flush=True)
else:
print(f"{BBLACK}[{BGREEN}OK{BBLACK}] {RESET}Test of {YELLOW}{name} {RESET}seems to be alright.", flush=True)
if raw:
try:
with open(f"tests/{name}.json", "w", encoding="utf-8") as file:
file.write(dumps(data, indent=4, ensure_ascii=False))
file.close()
except JSONDecodeError:
print(f"{BBLACK}[{BRED}ER{BBLACK}] {RESET}Test of {YELLOW}{name} {RESET}did not go well. ErrorCode is {BRED}{data['errorCode']}{RESET}. Check {CYAN}tests/{name}.json {RESET}for more information.", flush=True)
else:
with open(f"tests/{name}.txt", "w", encoding="utf-8") as file:
if isinstance(data, list):
list_2 = []
for item in data:
list_2.append(str(item)+"\n")
file.writelines(list_2)
else:
file.write(str(data))
file.close()
if data in ["", "\n"]:
print(f"{BBLACK}[{BRED}ER{BBLACK}] {RESET}Test of {YELLOW}{name} {RESET}did not go well. ErrorCode is {BRED}{data['errorCode']}{RESET}. Check {CYAN}tests/{name}.txt {RESET}for more information.", flush=True) # type: ignore
else:
print(f"{BBLACK}[{BGREEN}OK{BBLACK}] {RESET}Test of {YELLOW}{name} {RESET}is not empty, so might be fine. Check {CYAN}tests/{name}.txt {RESET}for more information.", flush=True)
client = pyrmv.Client(key)
test("raw_board_arrival", pyrmv.raw.board_arrival(key, id="A=1@O=Offenbach (Main)-Tempelsee Wilhelm-Schramm-Straße@X=8783648@Y=50083822@U=80@L=3008012@", maxJourneys=5), raw=True)
test("raw_board_departure", pyrmv.raw.board_departure(key, id="A=1@O=Offenbach (Main)-Tempelsee Wilhelm-Schramm-Straße@X=8783648@Y=50083822@U=80@L=3008012@", maxJourneys=5), raw=True)
test("raw_him_search", pyrmv.raw.him_search(key, dateE=datetime.now()+timedelta(days=10), timeE=datetime.now()+timedelta(days=10), minprio=2, trainnames=["S9"]), raw=True)
test("raw_journey_detail", pyrmv.raw.journey_detail(key, id="2|#VN#1#ST#1664906549#PI#0#ZI#12709#TA#0#DA#61022#1S#3008007#1T#1248#LS#3008043#LT#1323#PU#80#RT#1#CA#1aE#ZE#101#ZB#Bus 101 #PC#6#FR#3008007#FT#1248#TO#3008043#TT#1323#"), raw=True)
test("raw_stop_by_name", pyrmv.raw.stop_by_name(key, "Groß Karben", maxNo=3), raw=True)
test("raw_stop_by_coords", pyrmv.raw.stop_by_coords(key, 50.131140, 8.733362, radius=300, maxNo=3), raw=True)
test("raw_trip_find", pyrmv.raw.trip_find(key, originCoordLat="50.084659", originCoordLong="8.785948", destCoordLat=50.1233048, destCoordLong=8.6129742, trafficMessages=True, numF=3), raw=True)
test("raw_trip_recon", pyrmv.raw.trip_recon(key, ctx="¶HKI¶G@F$A=2@O=50.084659, 8.785948@X=8785948@Y=50084659@u=0@a=128@$A=1@O=Offenbach (Main)-Tempelsee Wilhelm-Schramm-Straße@L=3008012@a=128@$202210061243$202210061247$$$1$$$$$$§T$A=1@O=Offenbach (Main)-Tempelsee Wilhelm-Schramm-Straße@L=3008012@a=128@$A=1@O=Offenbach (Main)-Zentrum Marktplatz/Frankf. Straße@L=3002510@a=128@$202210061247$202210061300$Bus 101 $$1$$$$$$§W$A=1@O=Offenbach (Main)-Zentrum Marktplatz/Frankf. Straße@L=3002510@a=128@$A=1@O=Offenbach (Main)-Zentrum Marktplatz@L=3011265@a=128@$202210061300$202210061304$$$1$$$$$$§T$A=1@O=Offenbach (Main)-Zentrum Marktplatz@L=3011265@a=128@$A=1@O=Frankfurt (Main) Taunusanlage@L=3000011@a=128@$202210061306$202210061319$ S2$$1$$$$$$§T$A=1@O=Frankfurt (Main) Taunusanlage@L=3000011@a=128@$A=1@O=Frankfurt (Main) Rödelheim Bahnhof@L=3001217@a=128@$202210061322$202210061333$ S5$$1$$$$$$§G@F$A=1@O=Frankfurt (Main) Rödelheim Bahnhof@L=3001217@a=128@$A=2@O=50.123304, 8.612974@X=8612974@Y=50123304@u=0@a=128@$202210061333$202210061344$$$1$$$$$$¶GP¶ft@0@2000@120@1@100@1@@0@@@@@false@0@-1@0@-1@-1@$f@$f@$f@$f@$f@$§bt@0@2000@120@1@100@1@@0@@@@@false@0@-1@0@-1@-1@$f@$f@$f@$f@$f@$§tt@0@2000@120@1@100@1@@0@@@@@false@0@-1@0@-1@-1@$f@$f@$f@$f@$f@$§¶KRCC¶#VE#1#"), raw=True)
test("board_arrival", client.board_arrival("A=1@O=Frankfurt (Main) Taunusanlage@X=8668765@Y=50113478@U=80@L=3000011@", journeys_max=5))
test("board_departure", client.board_departure("A=1@O=Frankfurt (Main) Taunusanlage@X=8668765@Y=50113478@U=80@L=3000011@", journeys_max=5, retrieve_stops=False))
test("him_search", client.him_search(date_end=datetime.now()+timedelta(days=10), priority_min=2, train_names=["S9"]))
test("journey_detail", client.journey_detail("2|#VN#1#ST#1664906549#PI#0#ZI#12709#TA#0#DA#61022#1S#3008007#1T#1248#LS#3008043#LT#1323#PU#80#RT#1#CA#1aE#ZE#101#ZB#Bus 101 #PC#6#FR#3008007#FT#1248#TO#3008043#TT#1323#", real_time_mode=pyrmv.enums.RealTimeMode.FULL))
test("stop_by_coords", client.stop_by_coords(50.131140, 8.733362, radius=300, max_number=3))
test("stop_by_id", client.stop_by_id("A=1@O=Offenbach (Main)-Zentrum Marktplatz/Frankf. Straße@X=8764456@Y=50105181@U=80@L=3002510@"))
test("stop_by_name", client.stop_by_name("Groß Karben", max_number=3))
test("trip_find", client.trip_find(origin_coord_lat="50.084659", origin_coord_lon="8.785948", destination_coord_lat=50.1233048, destination_coord_lon=8.6129742, messages=True))
test("trip_recon", client.trip_recon( client.trip_find(origin_coord_lat="50.084659", origin_coord_lon="8.785948", destination_coord_lat=50.1233048, destination_coord_lon=8.6129742, messages=True, trips_after_time=1)[0] ))

View File

@@ -1,19 +0,0 @@
RESET = '\u001b[0m'
BLACK = '\u001b[30m'
RED = '\u001b[31m'
GREEN = '\u001b[32m'
YELLOW = '\u001b[33m'
BLUE = '\u001b[34m'
MAGENTA = '\u001b[35m'
CYAN = '\u001b[36m'
WHITE = '\u001b[37m'
BBLACK = '\u001b[30;1m'
BRED = '\u001b[31;1m'
BGREEN = '\u001b[32;1m'
BYELLOW = '\u001b[33;1m'
BBLUE = '\u001b[34;1m'
BMAGENTA = '\u001b[35;1m'
BCYAN = '\u001b[36;1m'
BWHITE = '\u001b[37;1m'
ULINE = '\u001b[4m'
REVERSE = '\u001b[7m'

36
tests/conftest.py Normal file
View File

@@ -0,0 +1,36 @@
from os import environ
from typing import List
import pytest
from pyrmv import Client
@pytest.fixture()
def api_token() -> str:
return environ.get("RMV_TOKEN")
@pytest.fixture()
def api_client(api_token: str) -> Client:
return Client(api_token)
@pytest.fixture()
def sample_stop_id() -> str:
return "A=1@O=Frankfurt (Main) Taunusanlage@X=8668765@Y=50113478@U=80@L=3000011@"
@pytest.fixture()
def sample_journey_id() -> str:
return "1|12709|0|80"
@pytest.fixture()
def sample_origin() -> List[str]:
return ["50.084659", "8.785948"]
@pytest.fixture()
def sample_destination() -> List[float]:
return [50.1233048, 8.6129742]

82
tests/test_client.py Normal file
View File

@@ -0,0 +1,82 @@
from datetime import datetime, timedelta
from typing import List
import pytest
from pyrmv import Client, enums
from pyrmv.classes import Journey, Message, Stop, Trip
from pyrmv.classes.board import BoardArrival, BoardDeparture
def test_board_arrival(api_client: Client, sample_stop_id: str):
assert isinstance(
api_client.board_arrival(id=sample_stop_id, journeys_max=3), BoardArrival
)
def test_board_departure(api_client: Client, sample_stop_id: str):
assert isinstance(
api_client.board_departure(id=sample_stop_id, journeys_max=3), BoardDeparture
)
def test_him_search(api_client: Client):
assert isinstance(
api_client.him_search(date_end=datetime.now() + timedelta(days=10))[0], Message
)
def test_journey_detail(api_client: Client, sample_journey_id: str):
assert (
api_client.journey_detail(
sample_journey_id,
real_time_mode=enums.RealTimeMode.FULL,
),
Journey,
)
def test_stop_by_coords(api_client: Client, sample_origin: List[str]):
assert isinstance(
api_client.stop_by_coords(sample_origin[0], sample_origin[1], max_number=3)[0], Stop
)
def test_stop_by_id(api_client: Client, sample_stop_id: str):
assert isinstance(api_client.stop_by_id(sample_stop_id), Stop)
def test_trip_find(
api_client: Client, sample_origin: List[str], sample_destination: List[float]
):
assert isinstance(
api_client.trip_find(
origin_coord_lat=sample_origin[0],
origin_coord_lon=sample_origin[1],
destination_coord_lat=sample_destination[0],
destination_coord_lon=sample_destination[1],
messages=True,
)[0],
Trip,
)
def test_trip_recon(
api_client: Client, sample_origin: List[str], sample_destination: List[float]
):
assert isinstance(
api_client.trip_recon(
api_client.trip_find(
origin_coord_lat=sample_origin[0],
origin_coord_lon=sample_origin[1],
destination_coord_lat=sample_destination[0],
destination_coord_lon=sample_destination[1],
messages=True,
)[0],
)[0],
Trip,
)
def test_stop_by_name(api_client: Client):
assert isinstance(api_client.stop_by_name("Hauptwache", max_number=1)[0], Stop)

7
tests/test_raw.py Normal file
View File

@@ -0,0 +1,7 @@
import pytest
from pyrmv.raw import stop_by_name
def test_stop_by_name(api_token: str):
assert isinstance(stop_by_name(api_token, "Hauptwache", maxNo=1), dict)

23
tox.ini Normal file
View File

@@ -0,0 +1,23 @@
[tox]
minversion = 3.8.0
envlist = py38, py39, py310, py311
isolated_build = true
[gh-actions]
python =
3.8: py38
3.9: py39
3.10: py310
3.11: py311
[testenv]
setenv =
PYTHONPATH = {toxinidir}
passenv =
RMV_TOKEN
deps =
-r{toxinidir}/requirements/_.txt
-r{toxinidir}/requirements/dev.txt
commands =
pytest --basetemp={envtmpdir} --cov=pyrmv --cov-report term-missing

View File

@@ -2,4 +2,38 @@
Welcome to the project's Wiki.
Conceptional there are two different types of methods. Normal and raw ones. Raw methods are only beautiful variant of HTTP requests, nothing except for that actually. Normal ones are meant to be used as objects. You can still provide strings here and there, but basic concept is focused on objects usage.
This module aims to provide eased access to RMV's OpenData API endpoints and enable some home-projects to show the data about public transit state at the moment. As for now, async is not supported and is not necessarily planned in the future, so bigger projects should consider this before using this module in the first place.
## Basic concepts behind
So the module `pyrmv` has two options to choose from when it comes to the usage:
1. Using higher-level methods of the class [pyrmv.Client](https://git.end-play.xyz/profitroll/PythonRMV/wiki/Client). These methods provide pythonic objects, can throw exceptions and are overall pretty neat.
2. Using raw functions from [pyrmv.raw](https://git.end-play.xyz/profitroll/PythonRMV/wiki/Raw-Functions). These functions are basically a small interface for HTTP requests that happen in the background and have little to no processing behind the scenes.
Your preferred variant depends on the use case, but usually higher-level methods of the Client should be the match.
## Objects
This module does **not** use the [FPTF](https://github.com/public-transport/friendly-public-transport-format) because it aims to give full access to the API RMV provides, thus objects Client gives you will contain a bit more information.
These objects are implemented in pyrmv:
* [BoardArrival](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.BoardArrival)
* [BoardDeparture](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.BoardDeparture)
* [LineArrival](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.LineArrival)
* [LineDeparture](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.LineDeparture)
* [Gis](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Gis)
* [Journey](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Journey)
* [Leg](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Leg)
* [Channel](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Channel)
* [Message](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Message)
* [Url](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Url)
* [Stop](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Stop)
* [StopTrip](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.StopTrip)
* [Trip](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Trip)
* ~~[Ticket](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Ticket)~~ (WIP)
## Note for bigger projects
As you may already know, bigger projects like [Öffi](https://gitlab.com/oeffi/oeffi) and [DB Navigator](https://play.google.com/store/apps/details?id=de.hafas.android.db) use NVV's API instead of RMV's for navigation and public transit querying. Reasons behind this are clear as day and from perspective of pyrmv it's highly recommended you also choose that way, because RMV loves to change or break their API in unexpected places and does not cooperate with developers well enough when they do so.

1
wiki/Raw-Functions.md Normal file
View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

View File

@@ -1,15 +1,21 @@
## [Home](https://git.end-play.xyz/profitroll/PythonRMV/wiki)
# [Home](https://git.end-play.xyz/profitroll/PythonRMV/wiki)
### Classes
* [Client](https://git.end-play.xyz/profitroll/PythonRMV/wiki/Client)
## Classes
### Methods
* [board_arrival](https://git.end-play.xyz/profitroll/PythonRMV/wiki/board_arrival)
* [board_departure](https://git.end-play.xyz/profitroll/PythonRMV/wiki/board_departure)
* [him_search](https://git.end-play.xyz/profitroll/PythonRMV/wiki/him_search)
* [journey_detail](https://git.end-play.xyz/profitroll/PythonRMV/wiki/journey_detail)
* [stop_by_coords](https://git.end-play.xyz/profitroll/PythonRMV/wiki/stop_by_coords)
* [stop_by_id](https://git.end-play.xyz/profitroll/PythonRMV/wiki/stop_by_id)
* [stop_by_name](https://git.end-play.xyz/profitroll/PythonRMV/wiki/stop_by_name)
* [trip_find](https://git.end-play.xyz/profitroll/PythonRMV/wiki/trip_find)
* [trip_recon](https://git.end-play.xyz/profitroll/PythonRMV/wiki/trip_recon)
* [Client](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Client)
## Methods
* [board_arrival](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Client.board_arrival)
* [board_departure](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Client.board_departure)
* [him_search](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Client.him_search)
* [journey_detail](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Client.journey_detail)
* [stop_by_coords](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Client.stop_by_coords)
* [stop_by_id](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Client.stop_by_id)
* [stop_by_name](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Client.stop_by_name)
* [trip_find](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Client.trip_find)
* [trip_recon](https://git.end-play.xyz/profitroll/PythonRMV/wiki/classes.Client.trip_recon)
## Raw functions
* [List of raw functions](https://git.end-play.xyz/profitroll/PythonRMV/wiki/Raw-Functions)

View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

1
wiki/classes/Channel.md Normal file
View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

View File

@@ -1,5 +1,6 @@
# PythonRMV Client
You are now viewing the main class of the module, its heart. The main Client class, all available normal methods can be found here.
# Client
You are now viewing the main class of the module, all available higher-level methods can be found here.
This page is about the Client class, which exposes high-level methods for an easy access to the API.
@@ -11,14 +12,14 @@ client = Client("SampleAPIKey")
print(client.stop_by_id("A=1@O=Offenbach (Main)-Zentrum Marktplatz\/Frankf. Straße@X=8764456@Y=50105181@U=80@L=3002510@"))
```
# Details
## `class pyrmv.Client`
## Details
### Parameters
* access_key (`str`) Access ID for identifying the requesting client. Get your key on [RMV website](https://opendata.rmv.de/site/start.html).
### Methods:
### Methods
* [board_arrival](https://git.end-play.xyz/profitroll/PythonRMV/wiki/board_arrival) -> `BoardArrival`
* [board_departure](https://git.end-play.xyz/profitroll/PythonRMV/wiki/board_departure) -> `BoardDeparture`
* [him_search](https://git.end-play.xyz/profitroll/PythonRMV/wiki/him_search) -> `List[Message]`

View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

1
wiki/classes/Gis.md Normal file
View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

1
wiki/classes/Journey.md Normal file
View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

1
wiki/classes/Leg.md Normal file
View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

1
wiki/classes/Message.md Normal file
View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

1
wiki/classes/Stop.md Normal file
View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

1
wiki/classes/StopTrip.md Normal file
View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

1
wiki/classes/Ticket.md Normal file
View File

@@ -0,0 +1 @@
Class is not available yet. Documentation will appear here after the class is finished.

1
wiki/classes/Trip.md Normal file
View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.

1
wiki/classes/Url.md Normal file
View File

@@ -0,0 +1 @@
Docs are not available yet. Please, use the linting in your IDE until the documentation is there.