apyefa 0.0.7__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of apyefa might be problematic. Click here for more details.

@@ -3,18 +3,28 @@ import logging
3
3
  from voluptuous import Any, Optional, Required, Schema
4
4
 
5
5
  from apyefa.commands.command import Command
6
-
7
- from ..data_classes import CoordFormat
6
+ from apyefa.data_classes import CoordFormat, Jorney
8
7
 
9
8
  _LOGGER = logging.getLogger(__name__)
10
9
 
11
10
 
12
11
  class CommandTrip(Command):
13
- def __init__(self) -> None:
14
- super().__init__("XML_TRIP_REQUEST2", "trip")
12
+ def __init__(self, format: str) -> None:
13
+ super().__init__("XML_TRIP_REQUEST2", format)
15
14
 
16
15
  def parse(self, data: dict):
17
- raise NotImplementedError
16
+ data = self._get_parser().parse(data)
17
+
18
+ journeys = data.get("journeys", [])
19
+
20
+ _LOGGER.info(f"{len(journeys)} journey(s) found")
21
+
22
+ result = []
23
+
24
+ for jorney in journeys:
25
+ result.append(Jorney.from_dict(jorney))
26
+
27
+ return result
18
28
 
19
29
  def _get_params_schema(self) -> Schema:
20
30
  return Schema(
@@ -23,6 +33,8 @@ class CommandTrip(Command):
23
33
  Required("coordOutputFormat", default="WGS84"): Any(
24
34
  *[x.value for x in CoordFormat]
25
35
  ),
36
+ Required("locationServerActive", default="1"): Any("0", "1", 0, 1),
37
+ Required("itdTripDateTimeDepArr", default="dep"): Any("dep", "arr"),
26
38
  Required("type_origin", default="any"): Any("any", "coord"),
27
39
  Required("name_origin"): str,
28
40
  Required("type_destination", default="any"): Any("any", "coord"),
@@ -31,5 +43,14 @@ class CommandTrip(Command):
31
43
  Optional("name_via"): str,
32
44
  Optional("useUT"): Any("0", "1", 0, 1),
33
45
  Optional("useRealtime"): Any("0", "1", 0, 1),
46
+ Optional("deleteAssignedStops_origin"): Any("0", "1", 0, 1),
47
+ Optional("deleteAssignedStops_destination"): Any("0", "1", 0, 1),
48
+ Optional("genC"): Any("0", "1", 0, 1),
49
+ Optional("genP"): Any("0", "1", 0, 1),
50
+ Optional("genMaps"): Any("0", "1", 0, 1),
51
+ Optional("allInterchangesAsLegs"): Any("0", "1", 0, 1),
52
+ Optional("calcOneDirection"): Any("0", "1", 0, 1),
53
+ Optional("changeSpeed"): str,
54
+ Optional("coordOutputDistance"): Any("0", "1", 0, 1),
34
55
  }
35
56
  )
apyefa/data_classes.py CHANGED
@@ -52,7 +52,7 @@ class TransportType(IntEnum):
52
52
  REGIONAL_BUS = 6 # Regionalbus
53
53
  EXPRESS_BUS = 7 # Schnellbus
54
54
  CABLE_RAIL = 8 # Seilbahn
55
- FERRY = 9 # Schief
55
+ FERRY = 9 # Schiff
56
56
  AST = 10 # Anruf-Sammel-Taxi
57
57
  SUSPENSION_RAIL = 11 # Schwebebahn
58
58
  AIRPLANE = 12 # Flugzeug
@@ -63,6 +63,8 @@ class TransportType(IntEnum):
63
63
  RAIL_REPLACEMENT_TRANSPORT = 17 # Schienenersatzverkehr
64
64
  SHUTTLE_TRAIN = 18 # Schuttlezug
65
65
  CITIZEN_BUS = 19 # Bürgerbus
66
+ UNKNOWN = 99 # TBD
67
+ FOOT_PATH = 100 # Fussweg
66
68
 
67
69
 
68
70
  class LocationFilter(IntEnum):
@@ -76,6 +78,19 @@ class LocationFilter(IntEnum):
76
78
  POST_CODES = 64
77
79
 
78
80
 
81
+ class PointTypeFilter(StrEnum):
82
+ ANY = "ANY"
83
+ BUS_POINT = "BUS_POINT"
84
+ ENTRANCE = "ENTRANCE"
85
+ GIS_AREA = "GIS_AREA"
86
+ GIS_POINT = "GIS_POINT"
87
+ LINE = "LINE"
88
+ POI_AREA = "POI_AREA"
89
+ POI_POINT = "POI_POINT"
90
+ STOP = "STOP"
91
+ STREET = "STREET"
92
+
93
+
79
94
  class LineRequestType(IntEnum):
80
95
  NONE = 0
81
96
  DEPARTURE_MONITOR = 1
@@ -90,12 +105,7 @@ class CoordFormat(StrEnum):
90
105
 
91
106
 
92
107
  # Validation schemas
93
- def IsLocationType(type: str):
94
- if type not in [x.value for x in LocationFilter]:
95
- raise ValueError
96
-
97
-
98
- SCHEMA_PROPERTIES = vol.Schema(
108
+ _SCHEMA_PROPERTIES = vol.Schema(
99
109
  {
100
110
  vol.Optional("stopId"): str,
101
111
  vol.Optional("downloads"): list,
@@ -106,54 +116,30 @@ SCHEMA_PROPERTIES = vol.Schema(
106
116
  extra=vol.ALLOW_EXTRA,
107
117
  )
108
118
 
109
- SCHEMA_LINE_PROPERTIES: Final = vol.Schema(
110
- {
111
- vol.Required("globalId"): str,
112
- vol.Required("isROP"): bool,
113
- vol.Required("isSTT"): bool,
114
- vol.Required("isTTB"): bool,
115
- vol.Required("lineDisplay"): str,
116
- vol.Required("timetablePeriod"): str,
117
- vol.Required("tripCode"): int,
118
- vol.Required("validity"): vol.Schema(
119
- {
120
- vol.Required("from"): vol.Date("%Y-%m-%d"),
121
- vol.Required("to"): vol.Date("%Y-%m-%d"),
122
- }
123
- ),
124
- }
125
- )
126
-
127
- SCHEMA_PRODUCT = vol.Schema(
119
+ _SCHEMA_PRODUCT = vol.Schema(
128
120
  {
129
- vol.Required("id"): int,
130
121
  vol.Required("class"): int,
131
- vol.Required("name"): str,
122
+ vol.Optional("id"): int,
123
+ vol.Optional("name"): str,
132
124
  vol.Optional("iconId"): int,
133
125
  }
134
126
  )
135
127
 
136
- SCHEMA_STOP = vol.Schema(
137
- {
138
- vol.Required("name"): str,
139
- vol.Required("type"): IsLocationType,
140
- vol.Optional("id"): str,
141
- }
142
- )
143
-
144
- SCHEMA_PARENT = vol.Schema(
128
+ _SCHEMA_PARENT = vol.Schema(
145
129
  {
146
130
  vol.Required("name"): str,
147
131
  vol.Required("type"): str,
148
132
  vol.Optional("id"): str,
149
133
  vol.Optional("isGlobalId"): vol.Boolean,
134
+ vol.Optional("coord"): list,
135
+ vol.Optional("niveau"): int,
150
136
  vol.Optional("disassembledName"): str,
151
137
  vol.Optional("parent"): vol.Self,
152
- vol.Optional("properties"): SCHEMA_PROPERTIES,
138
+ vol.Optional("properties"): _SCHEMA_PROPERTIES,
153
139
  }
154
140
  )
155
141
 
156
- SCHEMA_OPERATOR = vol.Schema(
142
+ _SCHEMA_OPERATOR = vol.Schema(
157
143
  {
158
144
  vol.Required("id"): str,
159
145
  vol.Required("name"): str,
@@ -161,7 +147,7 @@ SCHEMA_OPERATOR = vol.Schema(
161
147
  }
162
148
  )
163
149
 
164
- SCHEMA_LOCATION: Final = vol.Schema(
150
+ _SCHEMA_LOCATION: Final = vol.Schema(
165
151
  {
166
152
  vol.Required("name"): str,
167
153
  vol.Required("type"): vol.In([x.value for x in LocationType]),
@@ -169,32 +155,37 @@ SCHEMA_LOCATION: Final = vol.Schema(
169
155
  vol.Optional("disassembledName"): str,
170
156
  vol.Optional("coord"): list,
171
157
  vol.Optional("isGlobalId"): vol.Boolean,
158
+ vol.Optional("niveau"): int,
172
159
  vol.Optional("isBest"): vol.Boolean,
173
160
  vol.Optional("productClasses"): list[vol.Range(min=0, max=10)],
174
- vol.Optional("parent"): SCHEMA_PARENT,
161
+ vol.Optional("parent"): _SCHEMA_PARENT,
175
162
  vol.Optional("assignedStops"): [vol.Self],
176
- vol.Optional("properties"): SCHEMA_PROPERTIES,
163
+ vol.Optional("properties"): _SCHEMA_PROPERTIES,
177
164
  vol.Optional("matchQuality"): int,
165
+ vol.Optional("departureTimePlanned"): vol.Datetime("%Y-%m-%dT%H:%M:%S%z"),
166
+ vol.Optional("departureTimeEstimated"): vol.Datetime("%Y-%m-%dT%H:%M:%S%z"),
178
167
  },
179
168
  extra=vol.ALLOW_EXTRA,
180
169
  )
181
170
 
182
- SCHEMA_TRANSPORTATION: Final = vol.Schema(
171
+ _SCHEMA_TRANSPORTATION: Final = vol.Schema(
183
172
  {
184
- vol.Required("id"): str,
185
- vol.Required("name"): str,
186
- vol.Required("number"): str,
187
- vol.Required("product"): SCHEMA_PRODUCT,
173
+ vol.Required("product"): _SCHEMA_PRODUCT,
174
+ vol.Optional("id"): str,
175
+ vol.Optional("number"): str,
176
+ vol.Optional("name"): str,
188
177
  vol.Optional("description"): str,
189
- vol.Optional("operator"): SCHEMA_OPERATOR,
190
- vol.Optional("destination"): SCHEMA_LOCATION,
191
- vol.Optional("origin"): SCHEMA_LOCATION,
178
+ vol.Optional("operator"): _SCHEMA_OPERATOR,
179
+ vol.Optional("destination"): _SCHEMA_LOCATION,
180
+ vol.Optional("origin"): _SCHEMA_LOCATION,
192
181
  vol.Optional("properties"): dict,
193
182
  vol.Optional("disassembledName"): str,
194
- }
183
+ vol.Optional("coord"): list,
184
+ },
185
+ extra=vol.ALLOW_EXTRA,
195
186
  )
196
187
 
197
- SCHEMA_SYSTEM_INFO: Final = vol.Schema(
188
+ _SCHEMA_SYSTEM_INFO: Final = vol.Schema(
198
189
  {
199
190
  vol.Required("version"): str,
200
191
  vol.Required("ptKernel"): vol.Schema(
@@ -213,18 +204,45 @@ SCHEMA_SYSTEM_INFO: Final = vol.Schema(
213
204
  }
214
205
  )
215
206
 
216
- SCHEMA_DEPARTURE: Final = vol.Schema(
207
+ _SCHEMA_DEPARTURE: Final = vol.Schema(
217
208
  {
218
- vol.Required("location"): SCHEMA_LOCATION,
209
+ vol.Required("location"): _SCHEMA_LOCATION,
219
210
  vol.Required("departureTimePlanned"): vol.Datetime("%Y-%m-%dT%H:%M:%S%z"),
220
211
  vol.Optional("departureTimeEstimated"): vol.Datetime("%Y-%m-%dT%H:%M:%S%z"),
221
- vol.Required("transportation"): SCHEMA_TRANSPORTATION,
212
+ vol.Required("transportation"): _SCHEMA_TRANSPORTATION,
222
213
  vol.Optional("infos"): list,
223
214
  vol.Optional("hints"): list,
224
215
  },
225
216
  extra=vol.ALLOW_EXTRA,
226
217
  )
227
218
 
219
+ _SCHEMA_JORNEY: Final = vol.Schema(
220
+ {
221
+ vol.Optional("rating"): int,
222
+ vol.Required("isAdditional"): vol.Boolean,
223
+ vol.Required("interchanges"): int,
224
+ vol.Required("legs"): list,
225
+ },
226
+ extra=vol.ALLOW_EXTRA,
227
+ )
228
+
229
+ _SCHEMA_LEG: Final = vol.Schema(
230
+ {
231
+ vol.Required("duration"): int,
232
+ vol.Required("origin"): _SCHEMA_LOCATION,
233
+ vol.Required("destination"): _SCHEMA_LOCATION,
234
+ vol.Required("transportation"): _SCHEMA_TRANSPORTATION,
235
+ vol.Optional("stopSequence"): list,
236
+ vol.Optional("infos"): list,
237
+ vol.Optional("distance"): int,
238
+ vol.Optional("hints"): list,
239
+ vol.Optional("properties"): dict,
240
+ vol.Optional("isRealtimeControlled"): vol.Boolean,
241
+ vol.Optional("realtimeStatus"): list,
242
+ vol.Optional("footPathInfo"): list,
243
+ }
244
+ )
245
+
228
246
 
229
247
  @dataclass(frozen=True)
230
248
  class _Base:
@@ -249,7 +267,7 @@ class SystemInfo(_Base):
249
267
  valid_from: date
250
268
  valid_to: date
251
269
 
252
- _schema = SCHEMA_SYSTEM_INFO
270
+ _schema = _SCHEMA_SYSTEM_INFO
253
271
 
254
272
  @classmethod
255
273
  def from_dict(cls, data: dict) -> Self | None:
@@ -285,7 +303,7 @@ class Location(_Base):
285
303
  disassembled_name: str = field(repr=False, default="")
286
304
  match_quality: int = field(repr=False, default=0)
287
305
 
288
- _schema = SCHEMA_LOCATION
306
+ _schema = _SCHEMA_LOCATION
289
307
 
290
308
  @classmethod
291
309
  def from_dict(cls, data: dict) -> Self | None:
@@ -337,7 +355,7 @@ class Departure(_Base):
337
355
  infos: list[dict] = field(default_factory=[])
338
356
  hints: list[dict] = field(default_factory=[])
339
357
 
340
- _schema = SCHEMA_DEPARTURE
358
+ _schema = _SCHEMA_DEPARTURE
341
359
 
342
360
  @classmethod
343
361
  def from_dict(cls, data: dict) -> Self | None:
@@ -378,18 +396,47 @@ class Departure(_Base):
378
396
  )
379
397
 
380
398
 
399
+ @dataclass(frozen=True)
400
+ class Operator(_Base):
401
+ id: str
402
+ code: str
403
+ name: str
404
+
405
+ _schema = _SCHEMA_OPERATOR
406
+
407
+ @classmethod
408
+ def from_dict(cls, data: dict) -> Self | None:
409
+ if not data:
410
+ return None
411
+
412
+ if not isinstance(data, dict):
413
+ raise ValueError(f"Expected a dictionary, provided {type(data)}")
414
+
415
+ cls._schema(data)
416
+
417
+ return Operator(
418
+ data,
419
+ data.get("id"),
420
+ data.get("code"),
421
+ data.get("name"),
422
+ )
423
+
424
+
381
425
  @dataclass(frozen=True)
382
426
  class Line(_Base):
383
427
  id: str
384
428
  name: str
429
+ number: str
430
+ disassembled_name: str
385
431
  description: str
386
432
  product: TransportType
387
- operator: str
433
+ operator: Operator | None
388
434
  destination: Location
389
435
  origin: Location
390
436
  properties: dict = field(default_factory={})
437
+ coords: list[float] = field(default_factory=[])
391
438
 
392
- _schema = SCHEMA_TRANSPORTATION
439
+ _schema = _SCHEMA_TRANSPORTATION
393
440
 
394
441
  @classmethod
395
442
  def from_dict(cls, data: dict) -> Self | None:
@@ -403,25 +450,103 @@ class Line(_Base):
403
450
  cls._schema(data)
404
451
 
405
452
  id = data.get("id")
406
- name = data.get("number")
407
- # disassembled_name = data.get("disassembledName")
408
- # number = data.get("number")
453
+ name = data.get("name", None)
454
+ number = data.get("number", None)
455
+ disassembled_name = data.get("disassembledName", None)
409
456
  description = data.get("description")
410
457
  product = TransportType(data.get("product").get("class"))
411
- # operator = data.get("operator", None).get("name", None)
412
- operator = "None"
458
+ operator = Operator.from_dict(data.get("operator", None))
413
459
  destination = Location.from_dict(data.get("destination"))
414
460
  origin = Location.from_dict(data.get("origin"))
415
461
  properties = data.get("properties", {})
462
+ coords = data.get("coord", [])
416
463
 
417
464
  return Line(
418
465
  data,
419
466
  id,
420
467
  name,
468
+ number,
469
+ disassembled_name,
421
470
  description,
422
471
  product,
423
472
  operator,
424
473
  destination,
425
474
  origin,
426
475
  properties,
476
+ coords,
477
+ )
478
+
479
+
480
+ @dataclass(frozen=True)
481
+ class Leg(_Base):
482
+ duration: int
483
+ distance: int
484
+ origin: Location
485
+ destination: Location
486
+ transport: Line
487
+ stop_sequence: list[Location]
488
+ infos: list[dict] | None
489
+
490
+ _schema = _SCHEMA_LEG
491
+
492
+ @classmethod
493
+ def from_dict(cls, data: dict) -> Self | None:
494
+ if not data:
495
+ return None
496
+
497
+ if not isinstance(data, dict):
498
+ raise ValueError(f"Expected a dictionary, provided {type(data)}")
499
+
500
+ # validate data dictionary
501
+ cls._schema(data)
502
+
503
+ duration = data.get("duration", 0)
504
+ distance = data.get("distance", 0)
505
+ origin = Location.from_dict(data.get("origin"))
506
+ destination = Location.from_dict(data.get("destination"))
507
+ transport = Line.from_dict(data.get("transportation"))
508
+ stop_sequence = [Location.from_dict(x) for x in data.get("stopSequence", [])]
509
+ infos = data.get("infos")
510
+
511
+ return Leg(
512
+ data,
513
+ duration,
514
+ distance,
515
+ origin,
516
+ destination,
517
+ transport,
518
+ stop_sequence,
519
+ infos,
427
520
  )
521
+
522
+
523
+ @dataclass(frozen=True)
524
+ class Jorney(_Base):
525
+ rating: int
526
+ is_additional: bool
527
+ interchanges: int
528
+ legs: list[Leg] = field(default_factory=[])
529
+
530
+ _schema = _SCHEMA_JORNEY
531
+
532
+ @classmethod
533
+ def from_dict(cls, data: dict) -> Self | None:
534
+ if not data:
535
+ return None
536
+
537
+ if not isinstance(data, dict):
538
+ raise ValueError(f"Expected a dictionary, provided {type(data)}")
539
+
540
+ # validate data dictionary
541
+ cls._schema(data)
542
+
543
+ rating = data.get("rating", 0)
544
+ is_additional = data.get("isAdditional", False)
545
+ interchanges = data.get("interchanges", 0)
546
+ legs = data.get("legs", [])
547
+ _legs = []
548
+
549
+ for leg in legs:
550
+ _legs.append(Leg.from_dict(leg))
551
+
552
+ return Jorney(data, rating, is_additional, interchanges, _legs)
apyefa/helpers.py CHANGED
@@ -6,6 +6,15 @@ TZ_INFO = ZoneInfo("Europe/Berlin")
6
6
 
7
7
 
8
8
  def parse_datetime(date: str) -> datetime.datetime:
9
+ """
10
+ Parses a date string in ISO 8601 format and converts it to a timezone-aware datetime object.
11
+
12
+ Args:
13
+ date (str): The date string to parse in the format "%Y-%m-%dT%H:%M:%S%z".
14
+
15
+ Returns:
16
+ datetime.datetime: A timezone-aware datetime object if the input date is valid, otherwise None.
17
+ """
9
18
  if not date:
10
19
  return None
11
20
 
@@ -15,6 +24,15 @@ def parse_datetime(date: str) -> datetime.datetime:
15
24
 
16
25
 
17
26
  def parse_date(date: str) -> datetime.date:
27
+ """
28
+ Parses a date string in the format 'YYYY-MM-DD' and returns a datetime.date object.
29
+
30
+ Args:
31
+ date (str): The date string to parse.
32
+
33
+ Returns:
34
+ datetime.date: The parsed date object, or None if the input date string is empty.
35
+ """
18
36
  if not date:
19
37
  return None
20
38
 
@@ -22,10 +40,30 @@ def parse_date(date: str) -> datetime.date:
22
40
 
23
41
 
24
42
  def to_date(date: datetime.date) -> datetime.date:
43
+ """
44
+ Convert a datetime.date object to a string in the format 'YYYY-MM-DD'.
45
+
46
+ Args:
47
+ date (datetime.date): The date object to be converted.
48
+
49
+ Returns:
50
+ str: The date as a string in the format 'YYYY-MM-DD'.
51
+ """
25
52
  return datetime.datetime.strftime(date, "%Y-%m-%d")
26
53
 
27
54
 
28
55
  def is_datetime(date: str):
56
+ """
57
+ Check if the given string is in the format of a datetime.
58
+
59
+ The expected format is "YYYYMMDD HH:MM".
60
+
61
+ Args:
62
+ date (str): The date string to check.
63
+
64
+ Returns:
65
+ bool: True if the string is in the correct datetime format, False otherwise.
66
+ """
29
67
  if not isinstance(date, str) or not date:
30
68
  return False
31
69
 
@@ -41,6 +79,20 @@ def is_datetime(date: str):
41
79
 
42
80
 
43
81
  def is_date(date: str):
82
+ """
83
+ Check if the given string is a valid date in the format YYYYMMDD.
84
+
85
+ Args:
86
+ date (str): The date string to be validated.
87
+
88
+ Returns:
89
+ bool: True if the string is a valid date, False otherwise.
90
+
91
+ The function checks if the input string matches the pattern YYYYMMDD,
92
+ where YYYY is a four-digit year, MM is a two-digit month (01-12), and
93
+ DD is a two-digit day (01-31). It ensures that the month and day values
94
+ fall within the valid ranges.
95
+ """
44
96
  if not isinstance(date, str) or not date:
45
97
  return False
46
98
 
@@ -58,6 +110,15 @@ def is_date(date: str):
58
110
 
59
111
 
60
112
  def is_time(time: str):
113
+ """
114
+ Check if the given string is a valid time in HH:MM format.
115
+
116
+ Args:
117
+ time (str): The time string to validate.
118
+
119
+ Returns:
120
+ bool: True if the string is a valid time in HH:MM format, False otherwise.
121
+ """
61
122
  if not isinstance(time, str) or not time:
62
123
  return False
63
124
 
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.1
2
+ Name: apyefa
3
+ Version: 1.0.0
4
+ Summary: Python API for EFA(Elektronische Fahrplanauskunft) async requests
5
+ Author-email: Alex Jung <jungdevelop@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 Alex Jung
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ Project-URL: Homepage, https://github.com/alex-jung/apyefa
28
+ Project-URL: Documentation, https://github.com/alex-jung/apyefa
29
+ Project-URL: Repository, https://github.com/alex-jung/apyefa
30
+ Project-URL: Issues, https://github.com/alex-jung/apyefa/issues
31
+ Keywords: efa,public transport,traffic
32
+ Requires-Python: >=3.11
33
+ Description-Content-Type: text/markdown
34
+ License-File: LICENSE
35
+ Requires-Dist: aiohttp>=3.11.7
36
+ Requires-Dist: voluptuous>=0.15.2
37
+ Requires-Dist: tzdata>=2024.2
38
+ Provides-Extra: tests
39
+ Requires-Dist: coverage>=5.0.3; extra == "tests"
40
+ Requires-Dist: pytest-cov; extra == "tests"
41
+ Requires-Dist: pytest; extra == "tests"
42
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == "tests"
43
+ Requires-Dist: pytest-benchmark[histogram]>=3.2.1; extra == "tests"
44
+ Requires-Dist: requests>=2.32.3; extra == "tests"
45
+
46
+ # apyefa
47
+ [![Python package](https://github.com/alex-jung/apyefa/actions/workflows/python-package.yml/badge.svg)](https://github.com/alex-jung/apyefa/actions/workflows/python-package.yml)
48
+ [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
49
+
50
+ # Intro
51
+ **apyefa** is a python package used to asynchronously fetch public transit routing data via EFA interfaces like [efa.vgn](https://efa.vgn.de/vgnExt_oeffi/"). It can request itineraries for Bus/Trams/Subways etc. connections and return data in a human and machine readable format.
52
+
53
+ # Installation
54
+ You only need to install the **apyefa** package, for example using pip:
55
+ ``` bash
56
+ pip install apyefa
57
+ ```
58
+
59
+ # Restrictions
60
+ Currently the package supports only endpoints using [RapidJSON](https://rapidjson.org/) format. To check whether the endpoint supports this format, please call:
61
+ ``` bash
62
+ curl <EFA API URL>/XML_SYSTEMINFO_REQUEST?outputFormat=rapidJSON
63
+ e.g. curl https://bahnland-bayern.de/efa/XML_SYSTEMINFO_REQUEST?outputFormat=rapidJSON
64
+ ```
65
+ If API's answer looks like this, endpoint supports rapidJSON:
66
+ ```
67
+ {"version":"10.6.21.17","ptKernel":{"appVersion":"10.6.22.28 build 16.12.2024 11:14:57","dataFormat":"EFA10_06_01","dataBuild":"2024-12-31T00:54:55Z"},"validity":{"from":"2024-12-15","to":"2025-06-14"}}
68
+ ```
69
+
70
+ # Development setup
71
+ Create and activate virtual environment. Then install dependencies required by `apefa` package.
72
+ ``` bash
73
+ python3 -m venv .venv
74
+ source .venv/bin/activate
75
+ pip install .
76
+ ```
77
+
78
+ # apyefa functions
79
+ |Function Name |Description|
80
+ |----------------------------------------------------|-----------|
81
+ |[info()](https://github.com/alex-jung/apyefa/wiki/info)|Provides EFA endpoint system information|
82
+ |[locations_by_name()](https://github.com/alex-jung/apyefa/wiki/locations_by_name)|Search for locations by name with optional filters and limits|
83
+ |[location_by_coord()](https://github.com/alex-jung/apyefa/wiki/location_by_coord)|Search for location by it's coordinates|
84
+ |[list_lines()](https://github.com/alex-jung/apyefa/wiki/list_lines)|Retrieves a list of lines|
85
+ |[list_stops()](https://github.com/alex-jung/apyefa/wiki/list_stops)|Retrieves a list of stops|
86
+ |[trip()](https://github.com/alex-jung/apyefa/wiki/trip)|Calculates a trip between an origin and a destination locations|
87
+ |[departures_by_location()](https://github.com/alex-jung/apyefa/wiki/departures_by_location)|Fetches departures for a given location|
88
+ |[lines_by_name()](https://github.com/alex-jung/apyefa/wiki/lines_by_name)|Fetches lines by name|
89
+ |[lines_by_location()](https://github.com/alex-jung/apyefa/wiki/lines_by_location)|Fetches lines for a specific location|
90
+ |[line_stops()](https://github.com/alex-jung/apyefa/wiki/line_stops)|Retrieves the stops for a given line|
91
+ |[coord_bounding_box()](https://github.com/alex-jung/apyefa/wiki/coord_bounding_box)|Requests locations within a bounding box|
92
+ |[coord_radial()](https://github.com/alex-jung/apyefa/wiki/coord_radial)|Requests locations within a radius|
93
+ |[geo_object()](https://github.com/alex-jung/apyefa/wiki/geo_object)|Generates a sequence of coordinates and all passed stops of a provided line|
94
+
95
+
96
+ # Example
97
+ ``` python
98
+ import asyncio
99
+ from apyefa import EfaClient
100
+ from apyefa.data_classes import (
101
+ Location,
102
+ LocationFilter,
103
+ )
104
+
105
+ async def async_info(client: EfaClient):
106
+ info = await client.info()
107
+ print(info)
108
+
109
+ async def async_location_by_name(client: EfaClient):
110
+ stops: list[Location] = await client.locations_by_name(
111
+ "Plärrer", filters=[LocationFilter.STOPS], limit=20
112
+ )
113
+ for s in stops:
114
+ print(s)
115
+
116
+ async def main():
117
+ async with EfaClient("https://bahnland-bayern.de/efa/") as client:
118
+ await asyncio.gather(
119
+ async_info(client),
120
+ async_location_by_name(client),
121
+ )
122
+
123
+ if __name__ == "__main__":
124
+ asyncio.run(main())
125
+ ```