bear-tools 0.2.0__tar.gz → 0.2.2__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 (42) hide show
  1. {bear_tools-0.2.0 → bear_tools-0.2.2}/PKG-INFO +1 -1
  2. {bear_tools-0.2.0 → bear_tools-0.2.2}/pyproject.toml +1 -1
  3. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/cereal/__init__.py +0 -5
  4. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/cereal/client.py +2 -2
  5. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/cereal/serial_manager.py +10 -8
  6. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/cereal/server.py +1 -1
  7. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/enhanced_enum.py +2 -2
  8. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/macos_utils.py +42 -2
  9. {bear_tools-0.2.0 → bear_tools-0.2.2}/LICENSE +0 -0
  10. {bear_tools-0.2.0 → bear_tools-0.2.2}/README.md +0 -0
  11. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/__init__.py +0 -0
  12. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/cereal/mods/__init__.py +0 -0
  13. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/cereal/mods/base.py +0 -0
  14. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/cereal/tokenizer.py +0 -0
  15. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/dict_utils.py +0 -0
  16. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/enhanced_int_enum.py +0 -0
  17. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/example_protocol.yaml +0 -0
  18. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/fsm/README.md +0 -0
  19. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/fsm/__init__.py +0 -0
  20. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/fsm/fsm.py +0 -0
  21. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/linting_utils.py +0 -0
  22. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/lumberjack/README.md +0 -0
  23. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/lumberjack/__init__.py +0 -0
  24. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/lumberjack/callback_config.py +0 -0
  25. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/lumberjack/color.py +0 -0
  26. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/lumberjack/log_level.py +0 -0
  27. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/lumberjack/logger.py +0 -0
  28. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/markdown_utils.py +0 -0
  29. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/misc_utils.py +0 -0
  30. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/network_utils.py +0 -0
  31. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/os_utils.py +0 -0
  32. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/publisher/__init__.py +0 -0
  33. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/publisher/listener.py +0 -0
  34. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/publisher/publisher.py +0 -0
  35. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/py.typed +0 -0
  36. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/spreadsheet_utils.py +0 -0
  37. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/string_utils.py +0 -0
  38. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/thread_utils.py +0 -0
  39. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/time_utils.py +0 -0
  40. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/transport_protocol.py +0 -0
  41. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/type_defs.py +0 -0
  42. {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/yaml_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bear-tools
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: An assortment of QA/Automation related tools
5
5
  License: MIT
6
6
  Author: Sean Foley
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "bear-tools"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "An assortment of QA/Automation related tools"
5
5
  authors = ["Sean Foley <sean.foley.engr@gmail.com>"]
6
6
  license = "MIT"
@@ -1,5 +1,3 @@
1
- from pathlib import Path
2
-
3
1
  SERVER_ADDRESS: str = 'localhost'
4
2
  SERVER_PORT_BASE: int = 14441
5
3
 
@@ -8,6 +6,3 @@ SERVER_COMMAND_CODE_SHUTDOWN: str = '__shutdown'
8
6
  SERVER_COMMAND_GET_CONFIG: str = '__config'
9
7
  SERVER_COMMAND_ANNOTATE: str = '__annotate:'
10
8
  END_OF_MESSAGE: bytes = b'\x00\x00\x00' # All network messages terminated with this detectable blob
11
-
12
- package_dir: Path = Path(__file__).parent.absolute()
13
- config_path: Path = Path(package_dir / 'config.yaml')
@@ -330,7 +330,7 @@ class CerealClient(threading.Thread):
330
330
  are visually distinct.
331
331
 
332
332
  :param text: Message (command) to send
333
- :param expect_output: If True, return camera output up to {timeout_sec} after sending the message
333
+ :param expect_output: If True, return device output up to {timeout_sec} after sending the message
334
334
  :param timeout_sec: How long to wait for expected output to start+stop before returning whatever we have
335
335
  :return: If {expect_output}, a list[str] containing output observed a result of sending {text}; otherwise, []
336
336
  """
@@ -345,7 +345,7 @@ class CerealClient(threading.Thread):
345
345
  Used internally by :meth:`send` and :meth:`annotate`.
346
346
 
347
347
  :param text: Message (command) to send
348
- :param expect_output: If True, return camera output up to {timeout_sec} after sending the message
348
+ :param expect_output: If True, return device output up to {timeout_sec} after sending the message
349
349
  :param timeout_sec: How long to wait for expected output to start+stop before returning whatever we have
350
350
  :return: If {expect_output}, a list[str] containing output observed a result of sending {text}; otherwise, []
351
351
  """
@@ -337,6 +337,7 @@ class SerialManager(threading.Thread):
337
337
  self.__talking_stick = threading.Lock()
338
338
  self.__stop_event = threading.Event()
339
339
  self.__processing_input = False # input to this system from the serial device
340
+ self.__line_buffer: str = '' # Holds trailing partial line across read() calls
340
341
 
341
342
  self.device: serial.Serial | None = None
342
343
  self.listeners: list[SerialListener] = []
@@ -447,22 +448,23 @@ class SerialManager(threading.Thread):
447
448
  """
448
449
  Generator Method
449
450
 
450
- Split raw bytes from the serial device into lines, prepending timestamps to each
451
+ Split raw bytes from the serial device into lines, prepending timestamps to each.
452
+ Incomplete trailing fragments are buffered in ``__line_buffer`` and prepended to the
453
+ next chunk so that log lines are never split across two timestamps.
451
454
 
452
455
  :param raw: Raw bytes read from the serial device
453
456
  """
454
457
 
455
- text = raw.decode(encoding='utf-8', errors='ignore')
458
+ text = self.__line_buffer + raw.decode(encoding='utf-8', errors='ignore')
459
+ self.__line_buffer = ''
456
460
  lines = text.split('\n')
457
461
 
458
- for line in lines[:-1]: # last line may be incomplete
459
- timestamp = f'[{get_timestamp()}] ' if self.add_timestamps else ''
460
- yield f'{timestamp}{line}\n'
462
+ # Last element is either '' (if text ended with \n) or an incomplete fragment
463
+ self.__line_buffer = lines.pop()
461
464
 
462
- # Keep the last (possibly incomplete) fragment for the caller to handle
463
- if lines[-1]:
465
+ for line in lines:
464
466
  timestamp = f'[{get_timestamp()}] ' if self.add_timestamps else ''
465
- yield f'{timestamp}{lines[-1]}\n'
467
+ yield f'{timestamp}{line}\n'
466
468
 
467
469
 
468
470
  def run(self) -> None:
@@ -80,7 +80,7 @@ class CerealServer(socketserver.ThreadingTCPServer):
80
80
  """
81
81
  Initializer
82
82
 
83
- :param name: Nickname for the server (e.g. 'RTOS', 'Linux')
83
+ :param name: Nickname for the server (e.g. 'DeviceA', 'DeviceB')
84
84
  :param address: The ipaddress or host name on which the server should listen
85
85
  :param port: The port number that the server communicates through
86
86
  :param device_path: The path to a serial device
@@ -38,7 +38,7 @@ class EnhancedEnum(Enum):
38
38
  """Return the member whose value equals `value`, or None if not found."""
39
39
  try:
40
40
  return cls(value)
41
- except ValueError:
41
+ except (ValueError, TypeError):
42
42
  return None
43
43
 
44
44
  @classmethod
@@ -46,7 +46,7 @@ class EnhancedEnum(Enum):
46
46
  """Return the member name for `value`, or None if not found."""
47
47
  try:
48
48
  return cls(value).name
49
- except ValueError:
49
+ except (ValueError, TypeError):
50
50
  return None
51
51
 
52
52
 
@@ -70,6 +70,46 @@ def get_current_ssid() -> str | None:
70
70
  return get_current_ssid_macos15()
71
71
 
72
72
 
73
+ def get_ssid_for_interface(interface: str) -> str | None:
74
+ """
75
+ Get the SSID currently joined on a specific Wi-Fi interface
76
+
77
+ :param interface: The wireless interface to query (e.g. 'en0', 'en1')
78
+ :return: The SSID joined on `interface` if any; None otherwise
79
+ """
80
+
81
+ macos_version: str = platform.mac_ver()[0]
82
+ if macos_version.startswith('14'):
83
+ return get_current_ssid_macos14_and_older(interface=interface)
84
+ return get_current_ssid_macos15(interface=interface)
85
+
86
+
87
+ def get_local_ip_for_ssid(ssid: str, max_interface_index: int = 9) -> str | None:
88
+ """
89
+ Find this Mac's local IPv4 address on the NIC currently joined to a given SSID
90
+
91
+ Iterates `en0` through `en<max_interface_index>` looking for an interface that reports the
92
+ requested SSID via `networksetup -getairportnetwork` (or its macOS 15+ equivalent), then
93
+ returns the IPv4 address bound to that interface via `ipconfig getifaddr`.
94
+
95
+ :param ssid: The SSID to match against
96
+ :param max_interface_index: Highest `en<N>` index to probe (default 9)
97
+ :return: The IPv4 address as a string if found; None otherwise
98
+ """
99
+
100
+ for index in range(max_interface_index + 1):
101
+ interface = f'en{index}'
102
+ if get_ssid_for_interface(interface) != ssid:
103
+ continue
104
+ try:
105
+ output: str = subprocess.check_output(['ipconfig', 'getifaddr', interface]).decode().strip()
106
+ except subprocess.CalledProcessError:
107
+ continue
108
+ if output:
109
+ return output
110
+ return None
111
+
112
+
73
113
  if __name__ == '__main__':
74
- ssid: str | None = get_current_ssid()
75
- print(f'ssid: "{ssid}"')
114
+ _ssid: str | None = get_current_ssid()
115
+ print(f'ssid: "{_ssid}"')
File without changes
File without changes