repeaterbook 0.2.0__py3-none-any.whl → 0.2.2__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.
repeaterbook/models.py CHANGED
@@ -165,6 +165,11 @@ EmergencyJSON: TypeAlias = Literal[
165
165
  ServiceTypeJSON: TypeAlias = Literal["GMRS"]
166
166
 
167
167
 
168
+ # RepeaterBook has some variability between North America vs ROW exports.
169
+ # In practice, fields can appear/disappear (e.g. NA includes County/ARES/... and
170
+ # omits Region; ROW can include extra keys like "sponsor").
171
+ #
172
+ # Keep this TypedDict intentionally permissive for runtime robustness.
168
173
  RepeaterJSON = TypedDict(
169
174
  "RepeaterJSON",
170
175
  {
@@ -179,6 +184,11 @@ RepeaterJSON = TypedDict(
179
184
  "Region": str | None,
180
185
  "State": str,
181
186
  "Country": str,
187
+ "County": str,
188
+ "ARES": str,
189
+ "RACES": str,
190
+ "SKYWARN": str,
191
+ "CANWARN": str,
182
192
  "Lat": str,
183
193
  "Long": str,
184
194
  "Precise": ZeroOneJSON,
@@ -206,7 +216,9 @@ RepeaterJSON = TypedDict(
206
216
  "System Fusion": YesNoJSON,
207
217
  "Notes": str,
208
218
  "Last Update": str,
219
+ "sponsor": object,
209
220
  },
221
+ total=False,
210
222
  )
211
223
 
212
224
 
repeaterbook/queries.py CHANGED
@@ -16,7 +16,7 @@ from typing import TYPE_CHECKING, NamedTuple
16
16
 
17
17
  from haversine import haversine # type: ignore[import-untyped]
18
18
  from loguru import logger
19
- from sqlmodel import or_
19
+ from sqlmodel import and_, or_
20
20
 
21
21
  from repeaterbook.models import Repeater
22
22
  from repeaterbook.utils import Radius, square_bounds
@@ -24,17 +24,16 @@ from repeaterbook.utils import Radius, square_bounds
24
24
  if TYPE_CHECKING: # pragma: no cover
25
25
  from collections.abc import Iterable
26
26
 
27
- from sqlalchemy.sql._typing import _ColumnExpressionArgument
28
27
  from sqlalchemy.sql.elements import ColumnElement
29
28
 
30
29
 
31
- def square(radius: Radius) -> tuple[_ColumnExpressionArgument[bool] | bool, ...]:
30
+ def square(radius: Radius) -> ColumnElement[bool]:
32
31
  """Return a query for repeaters within a given square.
33
32
 
34
33
  Note: This is a square, not a circle. Use `filter_radius` afterwards.
35
34
  """
36
35
  bounds = square_bounds(radius=radius)
37
- return (
36
+ return and_(
38
37
  Repeater.latitude >= bounds.south,
39
38
  Repeater.latitude <= bounds.north,
40
39
  Repeater.longitude >= bounds.west,
repeaterbook/services.py CHANGED
@@ -107,6 +107,7 @@ USE_MAP: Final = {
107
107
  "OPEN": Use.OPEN,
108
108
  "PRIVATE": Use.PRIVATE,
109
109
  "CLOSED": Use.CLOSED,
110
+ "": Use.OPEN, # Some export payloads include empty Use; treat as OPEN.
110
111
  }
111
112
 
112
113
  STATUS_MAP: Final = {
@@ -125,54 +126,90 @@ def parse_date(date_str: str) -> date:
125
126
 
126
127
 
127
128
  def json_to_model(j: RepeaterJSON, /) -> Repeater:
128
- """Converts a JSON object to a Repeater model."""
129
+ """Converts a JSON object to a Repeater model.
130
+
131
+ RepeaterBook export payloads vary slightly between endpoints.
132
+
133
+ - `exportROW.php` may include extra keys like `sponsor`.
134
+ - `export.php` (North America) includes keys like `County`/`ARES`/… and may omit
135
+ `Region`.
136
+
137
+ This function should be resilient to those differences.
138
+ """
139
+
140
+ def s(key: str) -> str:
141
+ v = j.get(key, "")
142
+ if v is None:
143
+ return ""
144
+ return str(v)
145
+
146
+ def b(key: str, *, default: bool = False) -> bool:
147
+ """Parse RepeaterBook boolean-ish fields.
148
+
149
+ RepeaterBook uses a mix of "Yes"/"No" strings and 1/0 ints.
150
+ Missing/unknown values fall back to `default`.
151
+ """
152
+ return BOOL_MAP.get(j.get(key), default)
153
+
129
154
  return Repeater.model_validate(
130
155
  Repeater(
131
- state_id=j["State ID"],
132
- repeater_id=j["Rptr ID"],
133
- frequency=j["Frequency"],
134
- input_frequency=j["Input Freq"],
135
- pl_ctcss_uplink=j["PL"] or None,
136
- pl_ctcss_tsq_downlink=j["TSQ"] or None,
137
- location_nearest_city=j["Nearest City"],
138
- landmark=j["Landmark"] or None,
139
- region=j["Region"],
140
- state=j["State"],
141
- country=j["Country"],
142
- latitude=j["Lat"],
143
- longitude=j["Long"],
144
- precise=BOOL_MAP[j["Precise"]],
145
- callsign=j["Callsign"],
146
- use_membership=USE_MAP[j["Use"]],
147
- operational_status=STATUS_MAP[j["Operational Status"]],
148
- allstar_node=j["AllStar Node"],
149
- echolink_node=str(j["EchoLink Node"]) or None,
150
- irlp_node=j["IRLP Node"] or None,
151
- wires_node=j["Wires Node"] or None,
152
- analog_capable=BOOL_MAP[j["FM Analog"]],
153
- fm_bandwidth=j["FM Bandwidth"].replace(" kHz", "") or None,
154
- dmr_capable=BOOL_MAP[j["DMR"]],
155
- dmr_color_code=j["DMR Color Code"] or None,
156
- dmr_id=str(j["DMR ID"]) or None,
157
- d_star_capable=BOOL_MAP[j["D-Star"]],
158
- nxdn_capable=BOOL_MAP[j["NXDN"]],
159
- apco_p_25_capable=BOOL_MAP[j["APCO P-25"]],
160
- p_25_nac=j["P-25 NAC"] or None,
161
- m17_capable=BOOL_MAP[j["M17"]],
162
- m17_can=j["M17 CAN"] or None,
163
- tetra_capable=BOOL_MAP[j["Tetra"]],
164
- tetra_mcc=j["Tetra MCC"] or None,
165
- tetra_mnc=j["Tetra MNC"] or None,
166
- yaesu_system_fusion_capable=BOOL_MAP[j["System Fusion"]],
167
- notes=j["Notes"] or None,
168
- last_update=parse_date(j["Last Update"]),
156
+ state_id=s("State ID"),
157
+ repeater_id=int(j.get("Rptr ID", 0) or 0),
158
+ frequency=s("Frequency"),
159
+ input_frequency=s("Input Freq"),
160
+ pl_ctcss_uplink=s("PL") or None,
161
+ pl_ctcss_tsq_downlink=s("TSQ") or None,
162
+ location_nearest_city=s("Nearest City"),
163
+ landmark=s("Landmark") or None,
164
+ region=j.get("Region"),
165
+ country=s("Country") or None,
166
+ county=s("County") or None,
167
+ state=s("State") or None,
168
+ latitude=s("Lat"),
169
+ longitude=s("Long"),
170
+ precise=BOOL_MAP[j.get("Precise", 0)],
171
+ callsign=s("Callsign") or None,
172
+ use_membership=USE_MAP.get(s("Use"), Use.OPEN),
173
+ operational_status=(
174
+ STATUS_MAP[s("Operational Status")]
175
+ if s("Operational Status")
176
+ else Status.UNKNOWN
177
+ ),
178
+ ares=s("ARES") or None,
179
+ races=s("RACES") or None,
180
+ skywarn=s("SKYWARN") or None,
181
+ canwarn=s("CANWARN") or None,
182
+ allstar_node=s("AllStar Node") or None,
183
+ echolink_node=s("EchoLink Node") or None,
184
+ irlp_node=s("IRLP Node") or None,
185
+ wires_node=s("Wires Node") or None,
186
+ analog_capable=b("FM Analog", default=False),
187
+ fm_bandwidth=s("FM Bandwidth").replace(" kHz", "") or None,
188
+ dmr_capable=b("DMR", default=False),
189
+ dmr_color_code=s("DMR Color Code") or None,
190
+ dmr_id=s("DMR ID") or None,
191
+ d_star_capable=b("D-Star", default=False),
192
+ nxdn_capable=b("NXDN", default=False),
193
+ apco_p_25_capable=b("APCO P-25", default=False),
194
+ p_25_nac=s("P-25 NAC") or None,
195
+ m17_capable=b("M17", default=False),
196
+ m17_can=s("M17 CAN") or None,
197
+ tetra_capable=b("Tetra", default=False),
198
+ tetra_mcc=s("Tetra MCC") or None,
199
+ tetra_mnc=s("Tetra MNC") or None,
200
+ yaesu_system_fusion_capable=b("System Fusion", default=False),
201
+ notes=s("Notes") or None,
202
+ last_update=parse_date(s("Last Update")),
169
203
  )
170
204
  )
171
205
 
172
206
 
173
207
  @attrs.frozen
174
208
  class RepeaterBookAPI:
175
- """RepeaterBook API client."""
209
+ """RepeaterBook API client.
210
+
211
+ Must read https://www.repeaterbook.com/wiki/doku.php?id=api before using.
212
+ """
176
213
 
177
214
  base_url: URL = attrs.Factory(lambda: URL("https://repeaterbook.com"))
178
215
  app_name: str = "RepeaterBook Python SDK"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: repeaterbook
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Python utility to work with data from RepeaterBook.
5
5
  Project-URL: homepage, https://github.com/MicaelJarniac/repeaterbook
6
6
  Project-URL: source, https://github.com/MicaelJarniac/repeaterbook
@@ -112,6 +112,10 @@ Python utility to work with data from RepeaterBook.
112
112
 
113
113
  Read RepeaterBook's official [API documentation](https://www.repeaterbook.com/wiki/doku.php?id=api) for more information.
114
114
 
115
+ ## Related Projects
116
+ - https://github.com/MicaelJarniac/opengd77
117
+ - https://github.com/MicaelJarniac/ogdrb
118
+
115
119
  ## See Also
116
120
  - https://github.com/afourney/hamkit/tree/main/packages/repeaterbook
117
121
  - https://github.com/desertblade/OpenGD77-Repeaterbook
@@ -0,0 +1,11 @@
1
+ repeaterbook/__init__.py,sha256=EGIRvLGcrzX5ZD8ZoU13XyL825rFS9zPNRS1RSQwilg,251
2
+ repeaterbook/database.py,sha256=oAqCKGWjD4bQk62rsHTZQO0ESzn7UDrmNg78TZVznjY,1954
3
+ repeaterbook/models.py,sha256=a3-_t6Q6h3kvbOvPiqwMFUN_ZgNMthtuUgjyXEvjv0w,6868
4
+ repeaterbook/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ repeaterbook/queries.py,sha256=1VgGNviH5NDCBjc4Q1rt3ORmbrzRsdL2_X_yxGc86DA,3100
6
+ repeaterbook/services.py,sha256=lb7KZxwM5pCA7tms4LTYh-I0XRG0sq-Uogd_3FCmS8U,11480
7
+ repeaterbook/utils.py,sha256=QI5aWci8b3GAIi7l3PfT51sg4TTlwzoHtamNKTGkaaI,1453
8
+ repeaterbook-0.2.2.dist-info/METADATA,sha256=vZ9RrQFIqUWBj2mPhTH0GHj5IKGBAxO3NFhXrYaYXT0,7930
9
+ repeaterbook-0.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
10
+ repeaterbook-0.2.2.dist-info/licenses/LICENSE,sha256=TtbMt69RbQyifR_It2bTHKdlLR1Dj6x2A5y_oLOyoVk,1071
11
+ repeaterbook-0.2.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,11 +0,0 @@
1
- repeaterbook/__init__.py,sha256=EGIRvLGcrzX5ZD8ZoU13XyL825rFS9zPNRS1RSQwilg,251
2
- repeaterbook/database.py,sha256=oAqCKGWjD4bQk62rsHTZQO0ESzn7UDrmNg78TZVznjY,1954
3
- repeaterbook/models.py,sha256=iKvrMadYdsP1Zi-xqzChHmlT4lL36Crt93xK2q5M814,6422
4
- repeaterbook/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- repeaterbook/queries.py,sha256=eUS714SY7wbGZY6DyOEAnE27zkOIw9HuKteO2bBU7YY,3186
6
- repeaterbook/services.py,sha256=qct31P_4nV5_rfeGs6bDfIs_ur5heR0CF4HbTNy2GEM,10195
7
- repeaterbook/utils.py,sha256=QI5aWci8b3GAIi7l3PfT51sg4TTlwzoHtamNKTGkaaI,1453
8
- repeaterbook-0.2.0.dist-info/METADATA,sha256=rskdgxik9LIFmRFfTFqJ1ahQCYdTO4ZMe5ID_6eWShw,7824
9
- repeaterbook-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
- repeaterbook-0.2.0.dist-info/licenses/LICENSE,sha256=TtbMt69RbQyifR_It2bTHKdlLR1Dj6x2A5y_oLOyoVk,1071
11
- repeaterbook-0.2.0.dist-info/RECORD,,