Library / SDK
eskerda/pybikes avatar
eskerda/pybikes

pybikes: one Python interface for bike sharing data

bike sharing + python = pybikes

609 stars179 forksPythonAGPL-3.0

At a glance

What is it?
pybikes scrapes bike sharing systems from many operators and APIs behind a single set of classes, and it is the library that powers the CityBikes project. Its own design puts the operator differences in JSON data files, which is what makes adding a new system a scaffolding exercise rather than a rewrite.
Who is it for?
pybikes suits a statistics, research or open data project that needs historical and live station data from many bike sharing operators through one interface, and it suits a contributor who wants to add a system rather than maintain a fork. It is a poor fit for a production service with a hard availability requirement, because the library depends on scraping third party sites whose markup changes without notice.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The library behind the CityBikes map

pybikes provides a set of tools to scrape bike sharing data from different websites and APIs, with the stated goal of providing a coherent and generalised set of classes and methods to access that information. It is distributed for statistics and data sharing projects, and more importantly it powers the CityBikes project, whose site is at citybik.es and whose documentation is at docs.citybik.es.

That framing tells you the audience. This is a research and open data library rather than an application framework. The value is in the consistency: whatever the operator's site looks like, whether it is a JSON endpoint, an XML feed or an HTML page, you get back the same station objects.

The implementation is a set of classes plus a pack of data files that provide instances for all the different systems. The Python package metadata puts the licence at AGPL-3.0 with the licence file at `LICENSE.txt`, requires Python 3.9 or newer, and marks the development status as Beta. The runtime dependencies are `requests`, `lxml`, `shapely`, `cssselect` and `importlib_resources` for Python versions below 3.9. The version number itself is dynamic, taken from the git history through setuptools-scm, and the repository has no GitHub releases, so the commit history is the version record.

Instances are described by JSON data files

The entry point is a factory function that takes a system name and returns a ready instance. The data for each system lives in its own JSON file, which is why the Capital Bikeshare instance comes from a file called bixi.json:

python
>>> import pybikes

# Capital BikeShare instantiation data is in bixi.json file
>>> capital_bikeshare = pybikes.get('capital-bikeshare')

# The instance contains all possible metadata regarding this system
>>> print(capital_bikeshare.meta)

That meta dictionary holds the descriptive facts about the system: the name, the city and its coordinates, the system type such as Bixi, the operating company and the country. It is static configuration, not live data, which is exactly the kind of thing that belongs in a data file.

Live data arrives when you call `update`, which retrieves the list of stations:

python
# The update method retrieves the list of stations
>>> print(len(capital_bikeshare.stations))
0
>>> capital_bikeshare.update()
>>> print(len(capital_bikeshare.stations))
191

Note that the station list is empty before the first update, which is worth remembering if you write code that assumes a system is already populated. Each station object carries the number of bikes available, the number of free docks, the latitude and longitude, and an `extra` dictionary with whatever the operator returned, such as the terminal name, the install and removal dates, whether the station is locked or temporary, and the latest update timestamp.

Some operators need credentials, and the keys come from the environment

A number of systems require an API key to work, the documentation says, giving Digitranist as an example. The factory accepts an extra API key parameter for those cases:

python
>>> key = "This is not an API key"
>>> helsinki = pybikes.get('citybikes-helsinki', key=key)

The same pattern applies to operators that need more than one credential. The test documentation shows that systems requiring authorisation keys expect them as environment variables, for example:

bash
PYBIKES_DIGITRANSIT='some-api-key'
PYBIKES_DEUTSCHEBAHN_CLIENT_ID='some-client-id'
PYBIKES_DEUTSCHEBAHN_CLIENT_SECRET='some-client-secret'

# or if using an .env file
# source .env

make test-update

The Deutsche Bahn entry is the more demanding case, with a client id and a client secret rather than a single token, which tells you that for some providers the library is doing a real OAuth style exchange rather than passing a key through. The repository also ships a `keys.example.py` at the root, which is the template for such credentials.

For anyone building on the library, the practical pattern is that secrets belong in the environment and never in the data files, since the data files are shared between every user of a given system definition.

The scraper object is where sessions and proxies live

How the information is retrieved can be adjusted through the `PyBikesScraper` class in the utils module. The class uses the Requests library internally and exists to allow session reuse, plus the usual niceties such as using a proxy:

python
>>> scraper = pybikes.utils.PyBikesScraper()
>>> scraper.enableProxy()
>>> scraper.setProxies({
        "http" : "127.0.0.1:8118",
        "https": "127.0.0.1:8118"
    })
>>> scraper.headers['Foo'] = 'bar'
>>> capital_bikeshare.update(scraper)

That is a small but important detail in the design. The `update` call takes the scraper as an argument, so the transport concerns stay outside the system classes, and a caller can share one session across many systems instead of opening a new connection per request.

The alternative to the factory is documented too: instances can be generated by passing the right arguments to the desired class directly, which is what you do when the data file does not fit your case. The example shows a BixiSystem constructed with a tag, a root URL and a meta dictionary.

The proxy support also has a second use. Scraping a provider from a different network than the one your users are on is sometimes the difference between a rate limit and a ban.

Adding a system is a scaffolding command

The contribution path is unusually well trodden. A new bike share system can be scaffolded by running:

bash
$ python -m utils.scaffold example

================================================
Here is your 'Example' implementation

System: pybikes/example.py
Data: pybikes/data/example.json

Run tests by:
$ pytest -k Example

Visualize result:
$ make map! T_FLAGS+='-k Example'

Happy hacking :)
================================================

The output tells you the three things you need to touch: a class file in the package, a data file next to the others, and the commands to run the tests and to see a map of the result. The same names recur in the rest of the documentation, which makes the workflow easy to pick up.

Tests are split between unit tests and integration tests against the supported sources, and the split is done with pytest markers. `make test` runs the unit tests, and the Makefile shows it excludes the markers `update` and `changes`. `make test-update` runs the integration tests that hit the network, and `make test-changes` runs the `changes` marker set.

A single system can be tested by name, and pytest-xdist is recommended to spread the network calls across cores:

bash
pytest -k gbfs
pytest -k gbfs -n auto

When you want the Makefile targets and extra pytest arguments together, the flags are appended to the `T_FLAGS` variable, for example `make test-update T_FLAGS+='-n 10 -k gbfs'`.

Integration tests produce a geojson report

The integration test run can generate a JSON report file with all the extracted data stored as geojson, and the documentation notes that further useful reports can be built from that file, such as a summary of the overall health of the library or a map visualisation of all the information.

The Makefile shows how that works. The `report` target runs the update tests with the json-report plugin and writes to `report/report.json` by default, with the file name overridable through the `R_FILE` variable. From there, a summary is produced by `./utils/report.py report/report.json`, a map by the same script with the `utils/map.tpl.html` template, and a GitHub flavoured summary with the `utils/github-summary.tpl.md` template. The `report!` and `map!` targets run a clean first.

Two things stand out for anyone planning to use this in continuous integration. The geojson report is a machine readable snapshot of what every system returned, so it doubles as a health check across all the providers rather than a per-project artifact. And the GitHub summary template exists because the project itself uses that report to keep track of which providers still work, which is the clearest signal of what this library really is: a wide, brittle surface with a monitoring story attached.

AGPL terms and a project that tracks its providers

The licence is AGPL-3.0, declared in the project metadata with the licence file `LICENSE.txt` at the root. For a library that people import into scripts, that is the one choice in this project with consequences beyond the code. If you run pybikes internally as part of a service, the AGPL obligations around network use are the part to look at carefully, and if you ship something that embeds it, the source obligations reach further than a permissive licence would. That is a legal question rather than an engineering one, but the file to read is `LICENSE.txt`.

Everything else about the project is open and friendly. Development happens on the Matrix channel `#citybikes:matrix.org`, the readme invites people to dive into the codebase or check the issues list, and the documentation site has a page for adding a new system.

On maintenance, the last push was on 2026-10-20, so the project is being worked on now even though it publishes no GitHub releases. The badge at the top of the readme points at a test workflow, and the Makefile has separate targets for the update and changes test sets, which is how the maintainers see which providers have drifted.

Editorial conclusion

pybikes suits a statistics, research or open data project that needs historical and live station data from many bike sharing operators through one interface, and it suits a contributor who wants to add a system rather than maintain a fork. It is a poor fit for a production service with a hard availability requirement, because the library depends on scraping third party sites whose markup changes without notice. Before adopting it, run `make test-update` to see which providers currently respond, keep provider keys in environment variables such as `PYBIKES_DIGITRANSIT`, and read the AGPL-3.0 terms in `LICENSE.txt` before you link it into a service you distribute.

Frequently asked questions

How do I install pybikes?

Install from PyPI with `pip install pybikes`, directly from GitHub with `pip install git+https://github.com/eskerda/pybikes.git`, or from a cloned source tree with `pip install .`. Python 3.9 or newer is required.

How do I get data for a specific bike sharing system?

Call the factory with the system name, for example `pybikes.get('capital-bikeshare')`, which returns an instance whose metadata comes from a JSON data file in the package. Call `update()` on it to retrieve the list of stations.

Which bike sharing systems need an API key?

Some systems require authentication, with Digitranist given as an example, and those are passed a key to the factory with the `key` parameter. The test documentation shows that such keys are expected as environment variables such as `PYBIKES_DIGITRANSIT`, and that the Deutsche Bahn system expects a client id and a client secret.

How do I add support for a new bike sharing system?

Run `python -m utils.scaffold example`, which creates a class file in the package and a matching data file, and prints the commands to run the tests and to visualise the result. The documentation site has a page on adding a new system.

How do I check which providers still work?

Run the integration tests with `make test-update`, which hits the network. The same run can produce a json report with all extracted data as geojson, and the Makefile has targets for a summary and a map built from that report file.

Official sources

  1. eskerda/pybikes on GitHub
  2. Issues
  3. License: AGPL-3.0
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/eskerda-pybikes.svg)](https://hysenlabs.com/projects/eskerda-pybikes)