{"id":14985274,"url":"https://github.com/firstof9/py-gasbuddy","last_synced_at":"2026-05-26T02:10:44.737Z","repository":{"id":211313315,"uuid":"728766227","full_name":"firstof9/py-gasbuddy","owner":"firstof9","description":"Python library for GasBuddy","archived":false,"fork":false,"pushed_at":"2026-05-21T18:32:27.000Z","size":572,"stargazers_count":10,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-05-23T19:31:28.795Z","etag":null,"topics":["home-assistant","home-automation","python3"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/firstof9.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"firstof9","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":"firstof9","thanks_dev":null,"custom":null}},"created_at":"2023-12-07T16:40:44.000Z","updated_at":"2026-05-21T18:32:25.000Z","dependencies_parsed_at":"2024-01-17T19:00:15.534Z","dependency_job_id":"4e851e1f-37c5-43d0-af0a-f6995b630b1a","html_url":"https://github.com/firstof9/py-gasbuddy","commit_stats":{"total_commits":87,"total_committers":3,"mean_commits":29.0,"dds":0.1839080459770115,"last_synced_commit":"e21ef1a29104c2b35172daf8463db18ff4e5ca8f"},"previous_names":["firstof9/py-gasbuddy"],"tags_count":34,"template":false,"template_full_name":null,"purl":"pkg:github/firstof9/py-gasbuddy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/firstof9%2Fpy-gasbuddy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/firstof9%2Fpy-gasbuddy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/firstof9%2Fpy-gasbuddy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/firstof9%2Fpy-gasbuddy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/firstof9","download_url":"https://codeload.github.com/firstof9/py-gasbuddy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/firstof9%2Fpy-gasbuddy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33500474,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-25T14:31:05.219Z","status":"online","status_checked_at":"2026-05-26T02:00:06.821Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["home-assistant","home-automation","python3"],"created_at":"2024-09-24T14:10:38.279Z","updated_at":"2026-05-26T02:10:44.731Z","avatar_url":"https://github.com/firstof9.png","language":"Python","funding_links":["https://github.com/sponsors/firstof9","https://buymeacoffee.com/firstof9"],"categories":[],"sub_categories":[],"readme":"![Codecov branch](https://img.shields.io/codecov/c/github/firstof9/py-gasbuddy/main?style=flat-square)\n![GitHub commit activity (branch)](https://img.shields.io/github/commit-activity/m/firstof9/py-gasbuddy?style=flat-square)\n![GitHub last commit](https://img.shields.io/github/last-commit/firstof9/py-gasbuddy?style=flat-square)\n![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/firstof9/py-gasbuddy?style=flat-square)\n\n# py-gasbuddy\n\nPython async library for retrieving gas station data from the GasBuddy GraphQL API.\n\nProvides three query methods covering single-station detail (prices, hours, amenities, offers), location-based station search, and a price-service query that returns prices plus regional trend data for nearby stations.\n\n---\n\n## Installation\n\n```bash\npip install py-gasbuddy\n```\n\n---\n\n## Quick Start\n\n```python\nimport asyncio\nfrom py_gasbuddy import GasBuddy\n\nasync def main():\n    gb = GasBuddy(station_id=205033)\n    data = await gb.price_lookup()\n    print(data[\"name\"], data[\"regular_gas\"][\"price\"])\n\nasyncio.run(main())\n```\n\n---\n\n## GasBuddy Class\n\n```python\nGasBuddy(\n    station_id: int | None = None,\n    solver_url: str | None = None,\n    cache_file: str = \"\",\n    timeout: int = 60000,\n    session: aiohttp.ClientSession | None = None,\n)\n```\n\n| Parameter | Description |\n|---|---|\n| `station_id` | GasBuddy station ID — required for `price_lookup()` |\n| `solver_url` | URL of a [FlareSolver](https://github.com/FlareSolverr/FlareSolverr) instance for Cloudflare bypass |\n| `cache_file` | Path for the CSRF-token cache file (default: `py_gasbuddy/gasbuddy_cache`) |\n| `timeout` | Request timeout in milliseconds (default: 60000) |\n| `session` | Optional `aiohttp.ClientSession` to reuse — caller manages lifecycle |\n\n### Cloudflare / FlareSolver\n\nGasBuddy's homepage is protected by Cloudflare. The library extracts a CSRF token from the HTML before making GraphQL requests. If the direct fetch fails, pass a `solver_url` pointing to a running FlareSolver instance:\n\n```python\ngb = GasBuddy(station_id=205033, solver_url=\"http://localhost:8191/v1\")\n```\n\nTokens are cached on disk to avoid redundant solver calls across restarts.\n\n---\n\n## Methods\n\n### `price_lookup() → StationPrice`\n\nReturns full detail for a single station including prices, hours, amenities, brand, and offers. Requires `station_id` to be set at construction time.\n\n```python\ngb = GasBuddy(station_id=205033)\ndata = await gb.price_lookup()\n\n# Station metadata\nprint(data[\"name\"])           # \"Wawa\"\nprint(data[\"phone\"])          # \"610-555-0100\"\nprint(data[\"open_status\"])    # \"open\"\nprint(data[\"star_rating\"])    # 4.2\nprint(data[\"ratings_count\"])  # 38\nprint(data[\"pay_status\"])     # True (GasBuddy Pay available)\nprint(data[\"fuels\"])          # [\"regular_gas\", \"midgrade_gas\", \"premium_gas\"]\n\n# Address\nprint(data[\"address\"][\"line1\"])    # \"123 Main St\"\nprint(data[\"address\"][\"locality\"]) # \"Phoenixville\"\nprint(data[\"address\"][\"region\"])   # \"PA\"\n\n# Brands\nif data[\"brands\"]:\n    print(data[\"brands\"][0][\"name\"])     # \"Shell\"\n    print(data[\"brands\"][0][\"imageUrl\"]) # logo URL\n\n# Hours\nprint(data[\"hours\"][\"status\"])       # \"open\"\nprint(data[\"hours\"][\"openingHours\"]) # \"24 Hours\"\n\n# Prices (credit and cash)\nreg = data[\"regular_gas\"]\nprint(reg[\"price\"])           # 3.27  (credit price)\nprint(reg[\"cash_price\"])      # 3.17  (None if not reported)\nprint(reg[\"formatted_price\"]) # \"$3.27\"\nprint(reg[\"last_updated\"])    # \"2024-09-06T09:54:05Z\"\nprint(reg[\"credit\"])          # reporter nickname\n\n# Emergency status (fuel/power outage reports)\nprint(data[\"emergency_status\"][\"hasGas\"][\"reportStatus\"])  # None or \"has_gas\"\n\n# Loyalty offers\nfor offer in data[\"offers\"]:\n    print(offer[\"id\"], offer[\"types\"], offer[\"use\"])\n\n# Amenities\nfor amenity in data[\"amenities\"]:\n    print(amenity[\"name\"])  # \"ATM\", \"Restrooms\", etc.\n```\n\n**Fuel product keys** are dynamic and match whatever the station reports:\n`regular_gas`, `midgrade_gas`, `premium_gas`, `diesel`, `e85`, `e15`, etc.\n\n---\n\n### `location_search(lat, lon, zipcode, brand_id, fuel, cursor) → LocationSearchResult`\n\nReturns a dict with `results` (list of `StationSummary`) and optionally `next_cursor` for pagination. Pass either `lat`+`lon` or `zipcode`.\n\n```python\n# By ZIP code\nresult = await GasBuddy().location_search(zipcode=85396)\n\n# By GPS coordinates\nresult = await GasBuddy().location_search(lat=33.465, lon=-112.505)\n\n# Filter to diesel-only stations (fuel ID 4)\nresult = await GasBuddy().location_search(zipcode=85396, fuel=4)\n\n# Filter to a specific brand (38 = Costco)\nresult = await GasBuddy().location_search(zipcode=85396, brand_id=38)\n\nfor s in result[\"results\"]:\n    print(s[\"station_id\"], s[\"name\"], s[\"distance\"])\n    print(s[\"address\"][\"locality\"], s[\"address\"][\"region\"])\n    print(s[\"fuels\"])\n\n# Paginate\ncursor = result.get(\"next_cursor\")\nif cursor:\n    page2 = await GasBuddy().location_search(zipcode=85396, cursor=cursor)\n```\n\n**`StationSummary` fields:**\n\n| Field | Type | Description |\n|---|---|---|\n| `station_id` | `str` | GasBuddy station identifier |\n| `name` | `str` | Station/brand display name |\n| `address` | `Address` | `line1`, `line2`, `locality`, `region`, `postalCode`, `country` |\n| `brands` | `list[Brand]` | Brand name, `brandId`, logo `imageUrl` |\n| `distance` | `float \\| None` | Distance from search point (miles) |\n| `star_rating` | `float \\| None` | Community star rating |\n| `ratings_count` | `int \\| None` | Number of ratings |\n| `fuels` | `list[str]` | Fuel products sold |\n| `price_unit` | `str \\| None` | `\"dollars_per_gallon\"` or `\"cents_per_liter\"` |\n\n---\n\n### `price_lookup_service(lat, lon, zipcode, limit, brand_id, fuel, cursor) → PriceServiceResult`\n\nReturns prices for nearby stations plus regional trend data. Pass either `lat`+`lon` or `zipcode`. `limit` defaults to 5.\n\n```python\nresult = await GasBuddy().price_lookup_service(zipcode=85396, limit=10)\n\nfor station in result[\"results\"]:\n    print(station[\"name\"], station[\"distance\"])\n    if \"regular_gas\" in station:\n        print(station[\"regular_gas\"][\"price\"])\n        print(station[\"regular_gas\"][\"formatted_price\"])\n\n# Regional trends\nfor trend in result.get(\"trend\", []):\n    print(trend[\"area\"], trend[\"average_price\"], trend[\"lowest_price\"])\n\n# Paginate\ncursor = result.get(\"next_cursor\")\nif cursor:\n    page2 = await GasBuddy().price_lookup_service(zipcode=85396, cursor=cursor)\n```\n\nResults have the same structure as `StationPrice` (see `price_lookup` above) minus station-only fields like `amenities`, `hours`, `offers`, and `pay_status`.\n\n---\n\n## Fuel Types\n\n| Filter ID | Product key | Description |\n|---|---|---|\n| 1 | `regular_gas` | Regular (85-87 Octane) |\n| 2 | `midgrade_gas` | Mid-Grade (89 Octane) |\n| 3 | `premium_gas` | Premium (91-93 Octane) |\n| 4 | `diesel` | Diesel |\n| 5 | `e85` | E85 |\n| 12 | `unl88` | Unleaded 88 (E15) |\n\nThe **filter ID** is the integer passed to the `fuel` parameter on `location_search` and `price_lookup_service`. The **product key** is the string used as a dictionary key in `StationPrice` results (e.g. `data[\"regular_gas\"]`). Both are also available as `FUEL_FILTER_IDS` and `FUEL_PRODUCTS` in `py_gasbuddy.consts`.\n\n---\n\n## Data Models\n\nAll return types are `TypedDict` subclasses defined in `py_gasbuddy.models`.\n\n### `PriceNode`\n\n```python\nclass PriceNode(TypedDict):\n    credit: str | None          # reporter nickname\n    price: float | None         # credit price (None = not reported)\n    cash_price: float | None    # cash price (None = not reported)\n    last_updated: str | None    # ISO 8601 timestamp\n    formatted_price: str | None # \"$3.27\" or \"131.9¢\"\n    deal_price: float | None    # price after GasBuddy Pay discount (None if no offer or no price)\n```\n\n`deal_price` is computed from the `pwgbDiscount` field returned by the GasBuddy GraphQL API, which covers the Flash Deal and GasBuddy+ Member Savings components. The Card Savings Boost (available to GasBuddy+ members who select Fleet Card at the pump) is sourced from the mobile REST API and is not included here, so `deal_price` may be slightly higher than what the GasBuddy app displays for GasBuddy+ members.\n\n### `TrendData`\n\n```python\nclass TrendData(TypedDict):\n    area: str            # \"Arizona\" or \"United States\"\n    average_price: float # today's average\n    lowest_price: float  # today's lowest (0 if not available)\n```\n\n---\n\n## Error Handling\n\n| Exception | Raised when |\n|---|---|\n| `MissingSearchData` | `location_search` or `price_lookup_service` called without coordinates or ZIP |\n| `LibraryError` | Network or parse error; check logs for details |\n| `APIError` | GasBuddy's GraphQL returned an errors field |\n\n```python\nfrom py_gasbuddy.exceptions import APIError, LibraryError, MissingSearchData\n\ntry:\n    data = await gb.price_lookup()\nexcept LibraryError:\n    print(\"Network or token error\")\nexcept APIError:\n    print(\"GasBuddy API error\")\n```\n\n---\n\n## Cache Management\n\n```python\n# Clear the cached CSRF token (forces a fresh fetch on next request)\nawait gb.clear_cache()\n```\n\n---\n\n## Examples\n\nSee the [`examples/`](examples/) directory for runnable scripts:\n\n| Script | Description |\n|---|---|\n| `price-lookup.py` | Full station detail by station ID |\n| `price-lookup-service.py` | Nearby prices + trends by ZIP or GPS |\n| `location-search.py` | Station list (no prices) by ZIP or GPS |\n| `ev-chargers.py` | EV charger locations and status (CLI) |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffirstof9%2Fpy-gasbuddy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffirstof9%2Fpy-gasbuddy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffirstof9%2Fpy-gasbuddy/lists"}