RemoteRF 2.3.0__tar.gz → 2.3.1__tar.gz

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.
Files changed (96) hide show
  1. {remoterf-2.3.0/src/remoterf.egg-info → remoterf-2.3.1}/PKG-INFO +1 -1
  2. {remoterf-2.3.0 → remoterf-2.3.1}/pyproject.toml +1 -1
  3. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/core/app.py +13 -4
  4. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/core/grpc_client.py +9 -1
  5. {remoterf-2.3.0 → remoterf-2.3.1/src/remoterf.egg-info}/PKG-INFO +1 -1
  6. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_device_routes.py +16 -6
  7. {remoterf-2.3.0 → remoterf-2.3.1}/LICENSE +0 -0
  8. {remoterf-2.3.0 → remoterf-2.3.1}/README.md +0 -0
  9. {remoterf-2.3.0 → remoterf-2.3.1}/setup.cfg +0 -0
  10. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/__init__.py +0 -0
  11. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/__init__.py +0 -0
  12. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/grpc/__init__.py +0 -0
  13. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/grpc/deployment_capabilities.proto +0 -0
  14. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/grpc/deployment_capabilities_pb2.py +0 -0
  15. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/grpc/deployment_capabilities_pb2_grpc.py +0 -0
  16. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/grpc/grpc.proto +0 -0
  17. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/grpc/grpc_pb2.py +0 -0
  18. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/grpc/grpc_pb2_grpc.py +0 -0
  19. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/grpc/v2_codec.py +0 -0
  20. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/tos_notice.txt +0 -0
  21. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/utils/__init__.py +0 -0
  22. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/utils/ansi_codes.py +0 -0
  23. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/utils/api_token.py +0 -0
  24. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/utils/banner.py +0 -0
  25. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/utils/list_string.py +0 -0
  26. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/common/utils/process_arg.py +0 -0
  27. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/config/__init__.py +0 -0
  28. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/config/cert_fetcher.py +0 -0
  29. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/config/config.py +0 -0
  30. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/core/__init__.py +0 -0
  31. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/core/acc_login.py +0 -0
  32. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/core/banner_note.txt +0 -0
  33. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/core/direct_path.py +0 -0
  34. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/core/dynamic_v2_transport.py +0 -0
  35. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/core/grpc_acc.py +0 -0
  36. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/core/secure_channel.py +0 -0
  37. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/core/v2_errors.py +0 -0
  38. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/deployment/__init__.py +0 -0
  39. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/deployment/backend.py +0 -0
  40. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/deployment/direct.py +0 -0
  41. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/deployment/homes.py +0 -0
  42. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/deployment/http.py +0 -0
  43. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/deployment/state.py +0 -0
  44. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/__init__.py +0 -0
  45. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/_virtual.py +0 -0
  46. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/adalm_pluto/__init__.py +0 -0
  47. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/adalm_pluto/adalm_pluto_remote.py +0 -0
  48. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/dynamic_device.py +0 -0
  49. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/dynamic_v2.py +0 -0
  50. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/hackrf/__init__.py +0 -0
  51. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/hackrf/hackrf_remote.py +0 -0
  52. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/rtl_sdr/__init__.py +0 -0
  53. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/rtl_sdr/rtl_sdr_remote.py +0 -0
  54. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/sim_sdr/__init__.py +0 -0
  55. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/sim_sdr/sim_sdr_remote.py +0 -0
  56. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/support/__init__.py +0 -0
  57. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/support/ti_mmwave.py +0 -0
  58. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/support/uhd.py +0 -0
  59. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/support/uhd_dsp.py +0 -0
  60. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/support/uhd_v2.py +0 -0
  61. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/ti_mmwave/__init__.py +0 -0
  62. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/ti_mmwave/ti_mmwave_remote.py +0 -0
  63. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/usrp/__init__.py +0 -0
  64. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/usrp/usrp_remote.py +0 -0
  65. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/drivers/usrp/usrp_remote.pyi +0 -0
  66. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/remoterf_cli.py +0 -0
  67. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf/version.py +0 -0
  68. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf.egg-info/SOURCES.txt +0 -0
  69. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf.egg-info/dependency_links.txt +0 -0
  70. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf.egg-info/entry_points.txt +0 -0
  71. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf.egg-info/requires.txt +0 -0
  72. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf.egg-info/top_level.txt +0 -0
  73. {remoterf-2.3.0 → remoterf-2.3.1}/src/remoterf.py +0 -0
  74. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_banner.py +0 -0
  75. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_cli_login_fresh.py +0 -0
  76. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_config_address.py +0 -0
  77. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_deployment_accounts.py +0 -0
  78. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_direct_path_fixed.py +0 -0
  79. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_direct_path_partner.py +0 -0
  80. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_direct_path_race.py +0 -0
  81. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_dynamic_device_codegen.py +0 -0
  82. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_dynamic_v2.py +0 -0
  83. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_federated_devices.py +0 -0
  84. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_fetch_idl_fallback.py +0 -0
  85. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_finish_prompt.py +0 -0
  86. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_gate_i_direct_path.py +0 -0
  87. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_grpc_client_target.py +0 -0
  88. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_homes.py +0 -0
  89. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_native_account_credentials.py +0 -0
  90. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_native_devices.py +0 -0
  91. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_partner_dynamic_v2.py +0 -0
  92. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_pluto_virtual.py +0 -0
  93. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_process_arg.py +0 -0
  94. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_remoterf_cli.py +0 -0
  95. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_ti_mmwave.py +0 -0
  96. {remoterf-2.3.0 → remoterf-2.3.1}/tests/test_upgrade_rescue.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoterf
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
4
  Summary: A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.
5
5
  Author-email: Ethan Ge <ethoGalaxy@gmail.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "remoterf"
7
- version = "2.3.0"
7
+ version = "2.3.1"
8
8
  description = "A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts."
9
9
  readme = "README.md"
10
10
  license = "GPL-3.0-or-later"
@@ -13,7 +13,7 @@
13
13
  # You should have received a copy of the GNU General Public License
14
14
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
15
 
16
- from remoterf.core.grpc_client import addr as server_addr, handle_admin_command
16
+ from remoterf.core.grpc_client import addr as server_addr, handle_admin_command, _key
17
17
  from . import *
18
18
  from ..common.utils import *
19
19
 
@@ -146,9 +146,16 @@ def _routable(token):
146
146
 
147
147
 
148
148
  def _name_and_handle(name):
149
- """A listed device name, 'Pluto SDR [PlutoA]' -> ('Pluto SDR', 'PlutoA')."""
149
+ """A listed device name -> (name, handle). The handle is the name keyed,
150
+ 'Pluto SDR' -> 'Pluto_SDR' (None when the lab can't address it), unless a
151
+ 1.2.0 lab tags it: 'Pluto SDR [PlutoA]'."""
150
152
  m = _HANDLE_TAG.match(name)
151
- return (m["name"], m["handle"]) if m else (name, None)
153
+ if m:
154
+ return m["name"], m["handle"]
155
+ h = _key(name)
156
+ # The lab's is_device_handle (core 0.19.0; this client pins 0.16.0).
157
+ ok = h and not h.startswith(("rfg_", "bridge-")) and not any(c in "@:" or c.isspace() or not c.isprintable() for c in h)
158
+ return name, h if ok else None
152
159
 
153
160
 
154
161
  def _print_reserved(where, when, token, handle, logged_in_to, install, no_handle=_NO_HANDLE):
@@ -519,7 +526,9 @@ def _render_destinations(entries):
519
526
 
520
527
 
521
528
  def _handle_tag(dev):
522
- return f" [{dev['handle']}]" if dev.get("handle") else ""
529
+ """' [handle]' only when it isn't just the name."""
530
+ h = dev.get("handle")
531
+ return f" [{h}]" if h and _key(dev.get("display_name") or "") != _key(h) else ""
523
532
 
524
533
 
525
534
  def _federated_devices():
@@ -14,6 +14,7 @@
14
14
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
15
 
16
16
  import os
17
+ import re
17
18
  import sys
18
19
  import threading
19
20
  from dataclasses import dataclass
@@ -421,8 +422,15 @@ def _lab(t, x, dep, connection):
421
422
  return DeviceRoute(unmap_arg(results["Token"]), kind, connection, res_id=res_id)
422
423
 
423
424
 
425
+ def _key(s):
426
+ """A device name as a handle: 'pluto A', 'pluto_A' and 'pluto__A' are one.
427
+ Only for comparing names; never rewrite a string that may be a token."""
428
+ return re.sub(r"[\s_]+", "_", s).strip("_")
429
+
430
+
424
431
  def _global_handle(x, dep):
425
432
  """A handle on a device Global lists for this user, as its ref (None: no match)."""
433
+ key = _key(x)
426
434
  try:
427
435
  backend = federated_backend()
428
436
  dep_id = _deployment_id(dep)
@@ -430,7 +438,7 @@ def _global_handle(x, dep):
430
438
  return None
431
439
  matches = [
432
440
  d for d in backend.devices()["devices"]
433
- if d.get("deployment_id") == dep_id and d.get("handle") == x
441
+ if key and d.get("deployment_id") == dep_id and _key(d.get("handle") or "") == key
434
442
  ]
435
443
  if len(matches) > 1:
436
444
  raise RuntimeError(f"{x}@{dep}: more than one device has that handle")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoterf
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
4
  Summary: A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.
5
5
  Author-email: Ethan Ge <ethoGalaxy@gmail.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -182,6 +182,7 @@ def test_a_handle_is_exchanged_once_at_the_active_lab(monkeypatch, routes):
182
182
  @pytest.mark.parametrize("reply, wire, printed", [
183
183
  ({"Token": "Zm9vYmFy"}, "Zm9vYmFy", False), # not a handle: the lab hands it back
184
184
  ({"Token": "SuperTok_3_force"}, "SuperTok_3_force", False), # <super token>@<lab>: same path
185
+ ({"Token": "Zm9v__YmFy"}, "Zm9v__YmFy", False), # a token is never keyed
185
186
  ({"Token": "PlutoA", "why": "no active reservation of yours on PlutoA right now"}, "PlutoA", True),
186
187
  ({"ace": "No matching server function to call: device_token"}, "PlutoA", False), # old server
187
188
  ])
@@ -191,6 +192,7 @@ def test_the_lab_decides_handle_or_token(monkeypatch, routes, capsys, reply, wir
191
192
  t = f"{reply.get('Token', 'PlutoA')}@ucla" if "why" not in reply else "PlutoA@ucla"
192
193
  for _ in range(2):
193
194
  grpc_client.rpc_client(function_name=GET, args={"a": map_arg(t)})
195
+ assert routes.calls[0] == ("ACC:device_token", {"un": "jdoe", "pw": "s3cret", "h": t[:-len("@ucla")]})
194
196
  assert [c for c in routes.calls if c[0] == GET] == [(GET, {"a": wire})] * 2
195
197
  err = capsys.readouterr().err
196
198
  if printed:
@@ -338,6 +340,12 @@ def test_a_global_user_handle_maps_to_the_listed_device(monkeypatch, routes):
338
340
  monkeypatch.setattr(grpc_client, "federated_backend", lambda: SimpleNamespace(devices=lambda: listing))
339
341
  monkeypatch.setattr(homes, "discover", lambda host: {"deployment_id": DEST})
340
342
  assert grpc_client.device_route("PlutoA@ucla") == grpc_client.DeviceRoute(REF, "global")
343
+ # The handle is the name: spaces and underscores are one.
344
+ assert grpc_client._key("pluto A") == grpc_client._key("pluto_A") == grpc_client._key(" pluto\tA ") \
345
+ == grpc_client._key("pluto__A") == "pluto_A"
346
+ listing["devices"][1]["handle"] = "pluto_A"
347
+ for typed in ("pluto A@ucla", "pluto__A@ucla"):
348
+ assert grpc_client.device_route(typed) == grpc_client.DeviceRoute(REF, "global")
341
349
  with pytest.raises(RuntimeError) as exc:
342
350
  grpc_client.device_route("PlutoB@ucla")
343
351
  assert str(exc.value) == _unreachable("ucla")
@@ -436,7 +444,7 @@ def test_a_lab_reservation_shows_the_token_and_the_handle(monkeypatch):
436
444
  account.install_reserved_driver.assert_called_once_with("Zm9vYmFy", 3)
437
445
 
438
446
 
439
- def test_a_device_without_a_handle_shows_only_the_token(monkeypatch):
447
+ def test_an_untagged_device_is_reached_by_its_name(monkeypatch):
440
448
  import datetime
441
449
  import remoterf.core.app as client_app
442
450
 
@@ -448,10 +456,9 @@ def test_a_device_without_a_handle_shows_only_the_token(monkeypatch):
448
456
  monkeypatch.setattr(client_app, "home_name", "ucla")
449
457
  client_app._native_reserve_slot(4, datetime.date(2026, 1, 1), (0, 1), "10:00", "11:00",
450
458
  device_label="USRP B210 (online)")
451
- assert "Reservation successful on USRP B210 @ucla for 2026-01-01 10:00-11:00." in lines
452
- assert _row(lines, "Token", "Zm9vYmFy@ucla") and not any("Device handle" in line for line in lines)
453
- assert any("This device has no handle" in line for line in lines)
454
- assert ' sdr = uhd.usrp.MultiUSRP(token="Zm9vYmFy@ucla")' in lines
459
+ assert "Reservation successful on USRP_B210 @ucla for 2026-01-01 10:00-11:00." in lines
460
+ assert _row(lines, "Token", "Zm9vYmFy@ucla") and _row(lines, "Device", "handle", "USRP_B210@ucla")
461
+ assert ' sdr = uhd.usrp.MultiUSRP(token="USRP_B210@ucla")' in lines
455
462
  # A failed driver install: the token is still shown, just no usage lines.
456
463
  lines.clear()
457
464
  account.install_reserved_driver.return_value = None
@@ -514,6 +521,7 @@ def test_a_global_listing_shows_the_handle():
514
521
 
515
522
  assert client_app._handle_tag({"handle": "PlutoA"}) == " [PlutoA]"
516
523
  assert client_app._handle_tag({}) == ""
524
+ assert client_app._handle_tag({"display_name": "pluto A", "handle": "pluto_A"}) == ""
517
525
 
518
526
 
519
527
  def test_install_after_a_global_reservation_uses_the_ref():
@@ -598,7 +606,9 @@ def test_a_handle_is_the_last_bracketed_word():
598
606
 
599
607
  assert client_app._name_and_handle("SimSDR-0 [Lab]A]") == ("SimSDR-0", "Lab]A")
600
608
  assert client_app._name_and_handle("Bench [x] [B1]") == ("Bench [x]", "B1")
601
- assert client_app._name_and_handle("Bench [a b]") == ("Bench [a b]", None)
609
+ assert client_app._name_and_handle("Bench [a b]") == ("Bench [a b]", "Bench_[a_b]")
610
+ assert client_app._name_and_handle("pluto A") == ("pluto A", "pluto_A")
611
+ assert [client_app._name_and_handle(n)[1] for n in ("a@b", "rfg_x", "bridge-x", " _ ")] == [None] * 4
602
612
 
603
613
 
604
614
  def test_a_shell_without_a_home_shows_the_profiles_lab(monkeypatch):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes