Skip to content

Commit 5bf7e75

Browse files
authored
Merge pull request #16 from CerebusOSS/dev
Misc design improvements
2 parents 2efa1de + 6d6d4cb commit 5bf7e75

File tree

19 files changed

+434
-103
lines changed

19 files changed

+434
-103
lines changed
Lines changed: 14 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,26 @@
1-
name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI
1+
name: Publish Python 🐍 distributions 📦 to PyPI
22

33
on:
4-
workflow_dispatch: {}
5-
push:
6-
branches:
7-
- main
8-
pull_request:
9-
branches:
10-
- main
11-
types:
12-
- closed
4+
release:
5+
types: [published]
6+
workflow_dispatch:
137

148
jobs:
15-
build-n-publish:
16-
name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
9+
build:
10+
name: build and upload release to PyPI
1711
runs-on: ubuntu-latest
18-
environment:
19-
name: release
12+
environment: "release"
2013
permissions:
2114
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
15+
2216
steps:
2317
- uses: actions/checkout@v4
2418

25-
- name: Set up Python
26-
uses: actions/setup-python@v5
27-
with:
28-
python-version: "3.9"
29-
30-
- name: Install dependencies
31-
run: |
32-
python -m pip install --upgrade pip
33-
pip install pytest
34-
pip install -e .
35-
36-
- name: Test with pytest
37-
run: |
38-
pytest
39-
40-
- name: Install pypa/build
41-
run: >-
42-
pip install build --user
43-
44-
- name: Build a source tarball and a binary wheel
45-
run: >-
46-
python3 -m build --sdist --wheel --outdir dist/ .
19+
- name: Install uv
20+
uses: astral-sh/setup-uv@v2
4721

48-
- name: Publish distribution 📦 to Test PyPI
49-
uses: pypa/gh-action-pypi-publish@release/v1
50-
with:
51-
repository-url: https://test.pypi.org/legacy/
52-
skip-existing: true
22+
- name: Build Package
23+
run: uv build
5324

54-
- name: Publish distribution 📦 to PyPI
55-
if: startsWith(github.ref, 'refs/tags/v')
56-
uses: pypa/gh-action-pypi-publish@release/v1
25+
- name: Publish package distributions to PyPI
26+
run: uv publish

.github/workflows/python-tests.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Test package
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches:
8+
- main
9+
- dev
10+
workflow_dispatch:
11+
12+
jobs:
13+
build:
14+
strategy:
15+
matrix:
16+
python-version: [3.9, "3.10", "3.11", "3.12"]
17+
os:
18+
- "ubuntu-latest"
19+
- "windows-latest"
20+
- "macos-latest"
21+
runs-on: ${{matrix.os}}
22+
23+
steps:
24+
- uses: actions/checkout@v4
25+
26+
- name: Install uv
27+
uses: astral-sh/setup-uv@v2
28+
with:
29+
enable-cache: true
30+
cache-dependency-glob: "uv.lock"
31+
32+
- name: Set up Python ${{ matrix.python-version }}
33+
run: uv python install ${{ matrix.python-version }}
34+
35+
- name: Install the project
36+
run: uv sync --all-extras --dev
37+
38+
# - name: Lint
39+
# run:
40+
# uv tool run ruff check --output-format=github src
41+
42+
- name: Run tests
43+
run: uv run pytest tests

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,5 @@ cython_debug/
159159
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160160
.idea/
161161
# "I am become God." -Chad
162+
163+
src/pycbsdk/__version__.py

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ config = cbsdk.get_config(nsp_obj)
2525
print(config)
2626
```
2727

28-
You may also try the provided test script with `python -m pycbsdk.examples.print_rates` or via the shortcut: `pycbsdk_print_rates`.
28+
You may also try the provided test script with `python -m pycbsdk.examples.print_rates` or via the shortcut: `pycbsdk-rates`.
2929

3030
## Introduction
3131

docs/img/pycbsdk_design.svg

Lines changed: 4 additions & 0 deletions
Loading

pyproject.toml

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,42 @@
1-
[tool.poetry]
1+
[project]
22
name = "pycbsdk"
3-
version = "0.1.4"
43
description = "Pure Python interface to Blackrock Neurotech Cerebus devices"
5-
authors = ["Chadwick Boulay <chad@cbneurotech.com>"]
4+
authors = [
5+
{ name = "Chadwick Boulay", email = "chadwick.boulay@gmail.com" },
6+
]
67
license = "MIT"
78
readme = "README.md"
8-
packages = [
9-
{ include = "pycbsdk", from = "src" }
9+
requires-python = ">=3.9"
10+
dynamic = ["version"]
11+
dependencies = [
12+
"numpy",
13+
"aenum>=3.1.15",
14+
"ifaddr>=0.2.0",
1015
]
1116

12-
[tool.poetry.dependencies]
13-
python = ">=3.9,<3.13"
14-
numpy = "^1.26.4"
15-
aenum = "^3.1.15"
16-
ifaddr = "^0.2.0"
17+
[project.optional-dependencies]
18+
test = [
19+
"pytest>=8.3.3",
20+
]
1721

18-
[tool.poetry.group.dev.dependencies]
19-
typer = "^0.9.0"
20-
pytest = "^8.1.1"
22+
[project.scripts]
23+
pycbsdk-rates = "pycbsdk.examples.print_rates:main"
2124

2225
[build-system]
23-
requires = ["poetry-core"]
24-
build-backend = "poetry.core.masonry.api"
26+
requires = ["hatchling", "hatch-vcs"]
27+
build-backend = "hatchling.build"
28+
29+
[tool.hatch.version]
30+
source = "vcs"
2531

26-
[tool.poetry.scripts]
27-
ezmsg-monitor = "pycbsdk.examples.print_rates:main"
32+
[tool.hatch.build.hooks.vcs]
33+
version-file = "src/pycbsdk/__version__.py"
34+
35+
[tool.hatch.build.targets.wheel]
36+
packages = ["src/pycbsdk"]
37+
38+
[tool.uv]
39+
dev-dependencies = [
40+
"ruff>=0.6.8",
41+
"typer>=0.12.5",
42+
]

src/pycbsdk/__init__.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
import importlib.metadata
2-
3-
4-
__version__ = importlib.metadata.version("pycbsdk")
1+
from .__version__ import __version__ as __version__

src/pycbsdk/cbhw/device/base.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ def __init__(self, params: Params):
4242
_: [] for _ in CBChannelType
4343
}
4444
# Init config_callbacks as a defaultdict that will create an empty list on-the-fly for unseen keys.
45-
self.config_callbacks: defaultdict[
46-
CBPacketType, typing.List[CBPktCallBack]
47-
] = defaultdict(lambda: [])
45+
self.config_callbacks: defaultdict[CBPacketType, typing.List[CBPktCallBack]] = (
46+
defaultdict(lambda: [])
47+
)
4848
self._params = params
4949
self.packet_factory = CBPacketFactory(protocol=self._params.protocol)
5050
self.pkts_received = 0

src/pycbsdk/cbhw/device/nsp.py

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,10 @@
2626
import copy
2727
from ctypes import Structure, create_string_buffer
2828
import logging
29-
import queue
3029
import socket
3130
from collections.abc import Callable
32-
from enum import IntEnum, Flag, IntFlag
33-
from typing import Optional, Type
31+
from enum import IntEnum, IntFlag
32+
from typing import Optional
3433
import struct
3534
import threading
3635
import time
@@ -244,7 +243,7 @@ class LNCRate:
244243
def GetLNCRate(key) -> int:
245244
try:
246245
return LNCRate.lnc_rates[key]
247-
except KeyError as e:
246+
except KeyError:
248247
print("Error with LNC rate key.")
249248
return 0
250249

@@ -286,9 +285,7 @@ def __init__(self, params: Params, **kwargs):
286285
self._monitor_state["time"] = 1
287286

288287
# Placeholders for IO
289-
self._pkt_handler_thread = None
290288
self._sender_queue = None
291-
self._receiver_queue = None
292289
self._io_thread = None
293290

294291
self._register_basic_callbacks()
@@ -317,6 +314,10 @@ def __init__(self, params: Params, **kwargs):
317314
self._params.inst_port,
318315
)
319316

317+
# Start the packet handler thread. We don't expect it to receive any packets until `.connect()` is called.
318+
self._pkt_handler_thread = PacketHandlerThread(self)
319+
self._pkt_handler_thread.start()
320+
320321
@property
321322
def device_addr(self) -> tuple[str, int]:
322323
return self._device_addr
@@ -1121,7 +1122,7 @@ def set_transport(
11211122
event = self._config_events["sysrep"]
11221123
logger.debug(f"Attempting to set transport to {transport.upper()}")
11231124
if not self._send_packet(pkt, event=event, timeout=timeout):
1124-
logger.warning(f"Did not receive SYSREPTRANSPORT in expected timeout.")
1125+
logger.warning("Did not receive SYSREPTRANSPORT in expected timeout.")
11251126

11261127
def get_transport(self, force_refresh=False) -> int:
11271128
if force_refresh:
@@ -1136,18 +1137,13 @@ def reset(self) -> int:
11361137

11371138
# region IO
11381139
def connect(self, startup_sequence: bool = True) -> int:
1139-
self._receiver_queue = queue.SimpleQueue()
1140-
1141-
self._pkt_handler_thread = PacketHandlerThread(self._receiver_queue, self)
11421140
self._io_thread = CerebusDatagramThread(
1143-
self._receiver_queue,
1141+
self._pkt_handler_thread.receiver_queue,
11441142
self._local_addr,
11451143
self._device_addr,
11461144
self._params.protocol,
11471145
self._params.recv_bufsize,
11481146
)
1149-
1150-
self._pkt_handler_thread.start()
11511147
self._io_thread.start()
11521148
# _io_thread.start() returns immediately but takes a few moments until its send_q is created.
11531149
time.sleep(0.5)
@@ -1185,9 +1181,6 @@ def disconnect(self):
11851181
self._io_thread.join()
11861182
self._pkt_handler_thread.join()
11871183

1188-
del self._receiver_queue
1189-
self._receiver_queue = None
1190-
11911184
logger.info("Disconnected successfully.")
11921185

11931186
def _startup_sequence(self) -> CBError:
@@ -1254,7 +1247,7 @@ def _send_packet(
12541247
if event is not None:
12551248
res = event.wait(timeout=timeout)
12561249
if not res:
1257-
logger.debug(f"timeout expired waiting for event")
1250+
logger.debug("timeout expired waiting for event")
12581251
return False
12591252
return True
12601253

src/pycbsdk/cbhw/handler.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,19 @@ class PacketHandlerThread(threading.Thread):
2424
and should use atomic operations only.
2525
"""
2626

27-
def __init__(
28-
self, receiver_queue: queue.SimpleQueue, device: DeviceInterface, **kwargs
29-
):
27+
def __init__(self, device: DeviceInterface, **kwargs):
3028
super().__init__(**kwargs)
31-
self._recv_q = receiver_queue
29+
self._recv_q = queue.SimpleQueue()
3230
self._device = device
3331
self._continue = False
3432
self._packet_factory = CBPacketFactory(protocol=device._params.protocol)
3533
self._stop_event = threading.Event()
3634
self.daemon = True
3735

36+
@property
37+
def receiver_queue(self) -> queue.SimpleQueue:
38+
return self._recv_q
39+
3840
def run(self) -> None:
3941
last_group_time = -1
4042
last_group_data = None
@@ -118,6 +120,9 @@ def run(self) -> None:
118120
)
119121
self.warn_unhandled(pkt)
120122

123+
del self._recv_q
124+
self._recv_q = None
125+
121126
def stop(self):
122127
self._stop_event.set()
123128

0 commit comments

Comments
 (0)