pax_global_header00006660000000000000000000000064146413471470014525gustar00rootroot0000000000000052 comment=00aa4511cc65a88cecb999314e9baf76c4637ff4 ViViDboarder-py_nextbusnext-00aa451/000077500000000000000000000000001464134714700174575ustar00rootroot00000000000000ViViDboarder-py_nextbusnext-00aa451/.gitattributes000066400000000000000000000005721464134714700223560ustar00rootroot00000000000000# Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain ViViDboarder-py_nextbusnext-00aa451/.github/000077500000000000000000000000001464134714700210175ustar00rootroot00000000000000ViViDboarder-py_nextbusnext-00aa451/.github/workflows/000077500000000000000000000000001464134714700230545ustar00rootroot00000000000000ViViDboarder-py_nextbusnext-00aa451/.github/workflows/publish.yaml000066400000000000000000000006761464134714700254170ustar00rootroot00000000000000--- name: Publish Python Package on: release: types: [published] permissions: contents: read jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v3 with: python-version: '3.x' - name: Upload env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | make upload ViViDboarder-py_nextbusnext-00aa451/.github/workflows/test.yaml000066400000000000000000000004711464134714700247210ustar00rootroot00000000000000--- name: Tests on: push: branches: [master] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v3 with: python-version: '3.x' - name: Run tests run: | make test ViViDboarder-py_nextbusnext-00aa451/.gitignore000066400000000000000000000026061464134714700214530ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # dotenv .env # virtualenv .venv venv/ ENV/ # Spyder project settings .spyderproject # Rope project settings .ropeproject # ========================= # Operating System Files # ========================= # Windows # ========================= # Windows thumbnail cache files Thumbs.db ehthumbs.db ehthumbs_vista.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk ViViDboarder-py_nextbusnext-00aa451/.pre-commit-config.yaml000066400000000000000000000013651464134714700237450ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: debug-statements language_version: python3 - id: check-merge-conflict - id: name-tests-test exclude: tests/(common.py|util.py|(helpers|integration/factories)/(.+).py) - repo: https://github.com/asottile/reorder_python_imports rev: v3.13.0 hooks: - id: reorder-python-imports args: - --py3-plus - repo: https://github.com/psf/black rev: 24.4.2 hooks: - id: black - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.10.1 hooks: - id: mypy additional_dependencies: - types-requests ViViDboarder-py_nextbusnext-00aa451/LICENSE000066400000000000000000000020551464134714700204660ustar00rootroot00000000000000MIT License Copyright (c) 2018 Pierre Maris Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ViViDboarder-py_nextbusnext-00aa451/Makefile000066400000000000000000000036421464134714700211240ustar00rootroot00000000000000ENV := env .PHONY: default default: test # Creates virtualenv $(ENV): python3 -m venv $(ENV) $(ENV)/bin/pip install -e . # Install wheel for building packages $(ENV)/bin/wheel: $(ENV) $(ENV)/bin/pip install wheel # Install twine for uploading packages $(ENV)/bin/twine: $(ENV) $(ENV)/bin/pip install twine # Install pre-commit and other devenv items $(ENV)/bin/pre-commit: $(ENV) $(ENV)/bin/pip install -r ./requirements-dev.txt # Installs dev requirements to virtualenv .PHONY: devenv devenv: $(ENV)/bin/pre-commit # Generates a small build env for building and uploading dists .PHONY: build-env build-env: $(ENV)/bin/twine $(ENV)/bin/wheel # Runs tests .PHONY: test test: $(ENV) $(ENV)/bin/pre-commit # $(ENV)/bin/tox $(ENV)/bin/pre-commit run --all-files # Builds wheel for package to upload .PHONY: build build: $(ENV)/bin/wheel $(ENV)/bin/python setup.py sdist $(ENV)/bin/python setup.py bdist_wheel # Verify that the python version matches the git tag so we don't push bad shas .PHONY: verify-tag-version verify-tag-version: $(ENV)/bin/wheel $(eval TAG_NAME = $(shell [ -n "$(DRONE_TAG)" ] && echo $(DRONE_TAG) || git describe --tags --exact-match)) test "v$(shell $(ENV)/bin/python setup.py -V)" = "$(TAG_NAME)" # Uses twine to upload to pypi .PHONY: upload upload: verify-tag-version build $(ENV)/bin/twine $(ENV)/bin/twine upload dist/* # Uses twine to upload to test pypi .PHONY: upload-test upload-test: verify-tag-version build $(ENV)/bin/twine $(ENV)/bin/twine upload --repository-url https://test.pypi.org/legacy/ dist/* # Cleans all build, runtime, and test artifacts .PHONY: clean clean: rm -fr ./build ./py_nextbus.egg-info find . -name '*.pyc' -delete find . -name '__pycache__' -delete # Cleans dist and env .PHONY: dist-clean dist-clean: clean rm -fr ./dist $(ENV) # Install pre-commit hooks .PHONY: install-hooks install-hooks: $(ENV)/bin/pre-commit $(ENV)/bin/pre-commit install -f --install-hooks ViViDboarder-py_nextbusnext-00aa451/README.md000066400000000000000000000006031464134714700207350ustar00rootroot00000000000000# py_nextbusnext ## This is forked from py_nextbus for maintanence _It is no longer API compatible to the upstream_ A minimalistic Python 3 client to get routes and predictions from the NextBus API. Installation --- Install with pip: `pip install py-nextbusnext` Usage --- ``` >>> import py_nextbus >>> client = py_nextbus.NextBusClient() >>> agencies = client.get_agencies() ``` ViViDboarder-py_nextbusnext-00aa451/gen_mock.py000066400000000000000000000011351464134714700216130ustar00rootroot00000000000000from py_nextbus import NextBusClient from tests.mock_responses import TEST_AGENCY_ID from tests.mock_responses import TEST_ROUTE_ID from tests.mock_responses import TEST_STOP_ID client = NextBusClient() agencies = client.agencies() print("Agencies:") print(agencies) routes = client.routes(TEST_AGENCY_ID) print("\nRoutes:") print(routes) route_details = client.route_details(TEST_ROUTE_ID, TEST_AGENCY_ID) print("\nRoute Details:") print(route_details) predictions = client.predictions_for_stop( TEST_STOP_ID, TEST_ROUTE_ID, agency_id=TEST_AGENCY_ID ) print("\nPredictions:") print(predictions) ViViDboarder-py_nextbusnext-00aa451/py_nextbus/000077500000000000000000000000001464134714700216575ustar00rootroot00000000000000ViViDboarder-py_nextbusnext-00aa451/py_nextbus/__init__.py000066400000000000000000000000771464134714700237740ustar00rootroot00000000000000from .client import NextBusClient # NOQA name = "py_nextbus" ViViDboarder-py_nextbusnext-00aa451/py_nextbus/client.py000066400000000000000000000103461464134714700235130ustar00rootroot00000000000000from __future__ import annotations import json import logging import re from time import time from typing import Any from typing import cast from typing import NamedTuple import requests from requests.exceptions import HTTPError LOG = logging.getLogger() API_KEY_RE = re.compile(r"api_key.*key=([a-z0-9]+)") class NextBusError(Exception): pass class NextBusHTTPError(HTTPError, NextBusError): def __init__(self, message: str, http_err: HTTPError): self.__dict__.update(http_err.__dict__) self.message = message class NextBusValidationError(ValueError, NextBusError): """Error with missing fields for a NextBus request.""" class NextBusFormatError(ValueError, NextBusError): """Error with parsing a NextBus response.""" class NextBusAuthError(NextBusError): """Error with authentication to the NextBus API.""" class RouteStop(NamedTuple): route_tag: str stop_tag: str | int def __str__(self) -> str: return f"{self.route_tag}|{self.stop_tag}" @classmethod def from_dict(cls, legacy_dict: dict[str, str]) -> RouteStop: return cls(legacy_dict["route_tag"], legacy_dict["stop_tag"]) class NextBusClient: referer = "https://retro.umoiq.com/" base_url = "https://retro.umoiq.com/api/pub/v1" def __init__( self, agency_id: str | None = None, ) -> None: self.agency_id = agency_id self.api_key: str | None = None self.headers = { "Accept": "application/json, text/javascript, */*; q=0.01", "Referer": self.referer, } def agencies(self) -> list[dict[str, Any]]: result = self._get("agencies") return cast(list[dict[str, Any]], result) def routes(self, agency_id: str | None = None) -> list[dict[str, Any]]: if not agency_id: agency_id = self.agency_id result = self._get(f"agencies/{agency_id}/routes") return cast(list[dict[str, Any]], result) def route_details( self, route_id: str, agency_id: str | None = None ) -> dict[str, Any] | str: """Includes stops and directions.""" agency_id = agency_id or self.agency_id if not agency_id: raise NextBusValidationError("Agency ID is required") result = self._get(f"agencies/{agency_id}/routes/{route_id}") return cast(dict[str, Any], result) def predictions_for_stop( self, stop_id: str | int, route_id: str, direction_id: str | None = None, agency_id: str | None = None, ) -> list[dict[str, Any]]: agency_id = agency_id or self.agency_id if not agency_id: raise NextBusValidationError("Agency ID is required") params: dict[str, Any] = {"coincident": True} if direction_id: params["direction"] = direction_id result = self._get( f"agencies/{agency_id}/routes/{route_id}/stops/{stop_id}/predictions", params, ) return cast(list[dict[str, Any]], result) def _fetch_api_key(self) -> str: response = requests.get(self.referer) response.raise_for_status() key_search = API_KEY_RE.search(response.text) if not key_search: raise NextBusValidationError("Could not find API key on page") api_key = key_search.group(1) return api_key def _get( self, endpoint: str, params: dict[str, Any] | None = None ) -> dict[str, Any] | list[dict[str, Any]]: if params is None: params = {} if not self.api_key: self.api_key = self._fetch_api_key() params["key"] = self.api_key params["timestamp"] = int(time() * 1000) try: url = f"{self.base_url}/{endpoint}" LOG.debug("GET %s", url) response = requests.get(url, params=params, headers=self.headers) response.raise_for_status() return response.json() except HTTPError as exc: if exc.response.status_code == 401: self.api_key = None raise NextBusHTTPError("Error from the NextBus API", exc) from exc except json.decoder.JSONDecodeError as exc: raise NextBusFormatError("Failed to parse JSON from request") from exc ViViDboarder-py_nextbusnext-00aa451/requirements-dev.txt000066400000000000000000000000521464134714700235140ustar00rootroot00000000000000tox pre-commit mypy==1.10.1 black==24.4.2 ViViDboarder-py_nextbusnext-00aa451/setup.py000066400000000000000000000017671464134714700212040ustar00rootroot00000000000000from setuptools import find_packages from setuptools import setup with open("README.md", "r") as readme: long_description = readme.read() setup( name="py_nextbusnext", version="2.0.3", author="ViViDboarder", description="Minimalistic Python client for the NextBus public API for real-time transit " "arrival data", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/vividboarder/py_nextbus", packages=find_packages( exclude=[ "build", "dist", "tests", ] ), python_requires=">=3.9", install_requires=[ "requests", ], classifiers=[ "Programming Language :: Python :: 3.9 ", "Programming Language :: Python :: 3.10 ", "Programming Language :: Python :: 3.11 ", "Programming Language :: Python :: 3.12 ", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], ) ViViDboarder-py_nextbusnext-00aa451/tests/000077500000000000000000000000001464134714700206215ustar00rootroot00000000000000ViViDboarder-py_nextbusnext-00aa451/tests/__init__.py000066400000000000000000000000001464134714700227200ustar00rootroot00000000000000ViViDboarder-py_nextbusnext-00aa451/tests/client_test.py000066400000000000000000000001511464134714700235050ustar00rootroot00000000000000from __future__ import annotations import unittest.mock if __name__ == "__main__": unittest.main() ViViDboarder-py_nextbusnext-00aa451/tox.ini000066400000000000000000000003271464134714700207740ustar00rootroot00000000000000[tox] envlist = py{38, 39, 310, 311, 312, 3} ; envlist = py38,py39,py310,py311,py312} skip_missing_interpreters = true [testenv] description = Run unit tests commands = python -m unittest discover -p "*_test.py"