Package: pybuses-entities
PyBuses-EntitiesData classes to be used as part of a project that work with a public transport system, with Stops and Buses.While focused on Buses, PyBuses-Entities can be used with almost any public transport system that works
on a similar manner.The project relies on two basic classes:Stop: a physical place where Buses stop to pick and leave passengersBus: a moving vehicle that has a certain route of Stops and stop at themThis project is mainly developed for thePython-VigoBusAPIandVigoBus-TelegramBot,
two projects that work together (being the first an intermediate API, and the second one a Telegram bot backend
that consume data from that API).
In this public transport system, buses stop at Stops, and for each Stop we can fetch a List of Buses
with their remaining time until arrival.The interesting part about these classes is that they can generate a JSON output, which can be served through our API,
and then converted back to a class instance in our client.RequirementsPython >= 3.6pydanticInstallationpipinstallpybuses-entitiesChangelog0.0.2:add options to remove empty lists & empty dicts on get_dict() method of entities (True by default)add "source" field to AdvancedStop and BusesResultadd new sorting methods for Stops: sort by stopid, sort by distanceadd/improve docstrings0.0.1- initial releaseObject definitionStop and Bus objects inherit from pydantic. When creating a new instance of a class, its parameters
must be passed as kwargs.
Extra kwargs parameters passed are ignored, so these classes work well with JSON as input and output data.Both Stop and Bus classes have two superior class levels that inherit from their base classes, providing
more advanced attributes.Custom classes can be created in a similar manner to suit the needs of each particular project
and public transport system, inheriting from these base classes.StopAttributesstopid: unique identifier of this Stop (required, int)name: canonical, human-readable name for this Stop, usually its location (required, str)lat,lon: location of this Stop as latitude & longitude coordinates (optional, float)AdvancedStopcreated: when this Stop definition was created or registered for the first time (optional, datetime/str/int/float)updated: when this Stop definition was updated for the last time (optional, datetime/str/int/float)1original_name: when the Stop name was fetched from an external data source and changed by us,
we can keep its original name on this attribute (optional, str)1tags: extra tags for this Stop (optional, list of str)extra_names: optional additional names for this Stop, e.g. translation of the original names or
alternative names that users commonly use (optional, list of str)distance: distance from this Stop to a certain point. Useful, for example, when giving a list of
which Stops exist near a certain location (optional, float)source: name of the data source this data was fetched from (optional, str)1timestamps can be: datetime object, str representation, or int/float epoch timestampExamplesClassic object declaration:frompybusentimportStopstop_1=Stop(stopid=1,name="The Red Keep")stop_6960=Stop(stopid=6960,name="Praza do Rei",lat=42.235056274709,lon=-8.72675751435639)JSON/kwargs declaration (AdvancedStop example):frompybusentimportAdvancedStopstop_data={"stopid":6660,"name":"Porta do Sol","lat":42.2379152928961,"lon":-8.72484658707515,"created":1529137067,"updated":1560673067,"original_name":"Porta Do Sol, s/n",# 1"tags":["center","centre""zentrum","center","casco viejo","old town"],# 2"extra_names":["puerta del sol","sun gate"],# 3"distance":25.38}stop=AdvancedStop(**stop_data)1the original_name could be the name given by the original data source that provided the Stop information,
and we changed to a better-looking format2since this Stop is located at the town centre, we used tags like "center", "centre";
it is also near the old town, so used tags like "old town"3what the heck is "sun gate" supposed to mean? a literal translation from spanish "puerta del sol",
don't do this useless translations in production ?)BusAttributesbusid: unique identifier for this bus (optional, str) - if not provided, a busid is autogenerated
as the MD5 sum of the line and routeline: bus line (required, str)route: bus route (required, str)time: remaining time for the bus to arrive to a certain Stop (optional, int/float/datetime/timedelta)1AdvancedBusarrival: when the bus will arrive to a certain Stop (optional, int/float/datetime/timedelta)12departure: when the bus will departure from a certain Stop (optional, int/float/datetime/timedelta)12stops: a list of Stops this bus will stop at (optional, list of Stop objects)1bus remaining/arrival/departure times can be specified as relative times
(how time from now is left until it arrives/departures), or absolute times (datetime object or int/float epoch timestamp).
Relative times can be set as int, float or timedelta, depending on the way to represent this time.2when the transport system differentiates between arrivals and departures (e.g. many train networks),
the AdvancedBusarrival&departuretimes are used INSTEAD oftimeExamplesClassic object declaration:fromdatetimeimportdatetimefrompybusentimportBus# Bus 15C (Mohawk Avenue - Bunker Hill Street) will arrive in 5 minutesbus=Bus(line="15C",route="Mohawk Avenue - Bunker Hill Street",time=5)# Bus 21 (Algonquin) will arrive in 1 minute and 30 secondsbus=Bus(line=21,route="Algonquin",time=timedelta(minutes=1,seconds=30))print(type(bus.line))# Line is casted to string although declared as int# Bus 5B (Dukes - Bohan) will arrive at 14h52# Creating a datetime object can be a bit more tricky since it requires the date# Get today date and replace time with the arrival timedt=datetime.now().replace(hour=14,minute=52,second=0,microsecond=0)bus=Bus(line="5B",route="Dukes - Bohan",time=dt)# It can be easier if your data source provides an epoch timestampepoch=1529137067dt=datetime.fromtimestamp(epoch)bus=Bus(line="5B",route="Dukes - Bohan",time=dt)JSON/kwargs declaration (AdvancedBus example):frompybusentimportAdvancedBusbus_data={"line":5,"route":"Middle Park - East Holland","arrival":5,"departure":7}bus=AdvancedBus(**bus_data)Static bus declaration with its Stops route (AdvancedBus example):frompybusentimportAdvancedBus# Define the route of the buses Line 5, Route 'Middle Park - East Holland'bus=AdvancedBus(line="5",route="Middle Park - East Holland",stops=[Stop(stopid=1231,name="Middle Park East"),Stop(stopid=1232,name="Middle Park West"),Stop(stopid=1233,name="Lancaster Avenue"),Stop(stopid=1234,name="East Holland South"),Stop(stopid=1235,name="East Holland North")])BusesResultThis class is used when we want to return a more contextualized List of Buses. BusesResult do not inherit from Bus.
The following fields can be useful to give context to the Buses result:Attributesbuses: the List of Buses returnedmore_buses_available: boolean that can be set if the data source reported that more buses were available,
but we only fetched the first N results - e.g. because the data source is split into pages (optional, bool)stop: when the data source returns Stop info alongside its realtime Buses list, we can return it (optional, Stop)source: name of the data source this data was fetched from (optional, str)ExamplesfrompybusentimportBusesResultresult=BusesResult(buses=[Bus(line="5",route="Middle Park - East Holland",time=3),Bus(line="15C",route="Dukes - Bohan",time=9),Bus(line="5",route="Middle Park - East Holland",time=48),],stop=Stop(stopid=12812,name="Middle Park (Entrance)"),more_buses_available=True)Custom classesStop, AdvancedStop, Bus and AdvancedBus can be inherited to create custom classes, with new attributes
or changing the base attributes.Attributes that involve certain logic could have trouble if not designed specifically to support different datatypes
or being optional when designed to be required.For example, you can skip the Line or Route on a Bus and the Stop ID will still be auto-generated because this scenario
was contemplated.
If not, instantiating a Bus without Line/Route would not be possible, since both methods would be required in order
to generate the Stop ID.Examples"My bus network has no Line numbers, only the Route - I will create a custom Bus class without the Line
(more like, the Line is not required)"frompybusentimportBusclassMyBus(Bus):line:Optional[str]"On my bus network, buses have a flag to know if they have special sits for reduced mobility passengers
(False by default)"frompybusentimportBusclassMyBus(Bus):reduced_mobility:bool=False"On my bus network, the Stop IDs have letters on it, so it must be a string"frompybusentimportBusclassMyStop(AdvancedStop):stopid:strExceptionsA set of exceptions based on Stops and Buses is defined. They include no logic.PyBusesException: base exception for all the custom exceptions (inherit BaseException)StopException: base exception for all the Stop related exceptionsStopNotFound: raised when a Stop is not found on a certain data source, but that data source cannot
confirm that the stop not exists physically. E.g. if we keep a local storage of Stops and a Stop is not found
on it, it could be that it was not saved there, but it might physically exist.StopNotExist: raised when we are sure a Stop not physically exists. E.g. if we ask a remote API for
a Stop and the API replies that it does not exist.BusException: base exception for all the Bus related exceptions