From efb01250c843c6480baaf281984bd7b9375e4ba0 Mon Sep 17 00:00:00 2001 From: Markus Rosenstihl Date: Sat, 17 Oct 2026 21:52:10 +0200 Subject: [PATCH] add type hints to Experiment.py and dac.py, set up pyrefly as dev dependency --- pyproject.toml | 12 ++++ src/damaris/experiments/Experiment.py | 88 ++++++++++++++------------- src/damaris/experiments/dac.py | 2 +- uv.lock | 27 ++++++++ 4 files changed, 85 insertions(+), 44 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 55fcbcb..25c1e05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,9 @@ maintainers = [ [project.scripts] DAMARIS3 = "damaris.__main__:main" +[dependency-groups] +dev = ["pyrefly"] + [tool.setuptools.packages.find] where = ["src"] @@ -50,6 +53,15 @@ version = {attr = "damaris.__version__"} [tool.setuptools.data-files] "share/python3-damaris/images" = ["src/damaris/gui/DAMARIS3.png", "src/damaris/gui/DAMARIS3.ico"] + +[tool.pyrefly] +project-includes = [ + "src/damaris/experiments/Experiment.py", + "src/damaris/experiments/dac.py", +] +search-path = ["src"] +python-version = "3.11" +python-platform = "linux" #"share/python3-damaris/doc" = ["doc/index.html"] #"share/python3-damaris/doc/reference-html" = ["doc/reference-html/*"] #"share/python3-damaris/doc/tutorial-html" = ["doc/tutorial-html/*"] diff --git a/src/damaris/experiments/Experiment.py b/src/damaris/experiments/Experiment.py index 9c0b954..f210b36 100644 --- a/src/damaris/experiments/Experiment.py +++ b/src/damaris/experiments/Experiment.py @@ -3,20 +3,20 @@ import types import numpy class StateBase(object): - def __init__(self): + def __init__(self) -> None: pass - def to_xml(self, indent = ""): + def to_xml(self, indent: str = "") -> str: return indent + "" class StateSimple(StateBase): - def __init__(self, time, content=None): + def __init__(self, time: float, content: object | None = None) -> None: super(StateSimple, self).__init__() if time < 0: raise AssertionError("time for state is negative!") self.time = time self.content = content - def to_xml(self, indent = ""): + def to_xml(self, indent: str = "") -> str: s = indent + '\n' @@ -24,14 +24,14 @@ class StateSimple(StateBase): s += indent + ' ' + str(self.content) + '\n' s += indent + '\n' return s - def __repr__(self): + def __repr__(self) -> str: return 'StateSimple(%s, %s)' % (self.time, repr(self.content)) class StateList(StateBase): - def __init__(self): + def __init__(self) -> None: super(StateList, self).__init__() - self.list = [] - def to_xml(self, indent = " "): + self.list: list[object] = [] + def to_xml(self, indent: str = " ") -> str: s = "" for k in self.list: if hasattr(k, "to_xml"): @@ -39,21 +39,21 @@ class StateList(StateBase): else: s += indent + str(k) return s - def append(self, val): + def append(self, val: object) -> None: self.list.append(val) class StateLoop(StateList): """Represents a loop in the state tree""" - def __init__(self, repeat): + def __init__(self, repeat: int) -> None: super(StateLoop, self).__init__() self.repeat = repeat - def to_xml(self, indent = ""): + def to_xml(self, indent: str = "") -> str: s = indent + ('\n' % self.repeat) s += super(StateLoop, self).to_xml(indent + " ") s += indent + '\n' return s - def __repr__(self): + def __repr__(self) -> str: return 'StateLoop(repeat=%d, %s)' \ % (self.repeat, repr(self.list)) @@ -78,32 +78,32 @@ class Experiment: :param list rf_gates: list of rf gates ttl channels len(rf_gates) == len(rf_sources) """ - job_id = 0 + job_id: int = 0 - def __init__(self, gating=None, rf_sources=[], rf_gates=[]): - self.job_id = Experiment.job_id + def __init__(self, gating: float | None = None, rf_sources: list[int] = [], rf_gates: list[int] = []) -> None: + self.job_id: int = Experiment.job_id Experiment.job_id += 1 - self.state_list = StateList() - self.list_stack = [] - self.description = { } - self.gating = gating + self.state_list: StateList = StateList() + self.list_stack: list[StateList] = [] + self.description: dict[str, object] = { } + self.gating: float | None = gating assert type(rf_sources) == type(list()), "rf_sources needs to be a list with the channels" assert type(rf_gates) == type(list()), "rf_gates needs to be a list with the channels" assert len(rf_gates) == len(rf_sources), "rf_sources and rf_gates must have equal number of entries" - self.rf_sources = rf_sources - self.rf_gates = rf_gates + self.rf_sources: list[int] = rf_sources + self.rf_gates: list[int] = rf_gates #for tracking the experiment length: #because loops are possible we need to track the length for each loop level - self.total_time=[0.0] + self.total_time: list[float] = [0.0] #and we need to know the number of iterations of the loops to multiply the state. - self.loop_iterations=[1] + self.loop_iterations: list[int] = [1] # Commands ------------------------------------------------------------------------------------- - def ttl_pulse(self, length, channel = None, value = None): + def ttl_pulse(self, length: float, channel: int | None = None, value: int | None = None) -> None: """ Creates a state with length **length** and switches requested bits of the pulse programmer to HIGH. @@ -151,7 +151,7 @@ class Experiment: self.total_time[-1] += length ## Same as ttl_pulse, but no *channel* keyword - def ttls(self, length = None, value = None): + def ttls(self, length: float | None = None, value: int | None = None) -> None: """ Same as ttl_pulse, but no *channel* keyword @@ -159,6 +159,7 @@ class Experiment: :param int value: lines to set (integer) :return: """ + assert value is not None, "ttls needs an integer value" the_value=int(value) s_content = '' % the_value if length is not None: @@ -168,7 +169,7 @@ class Experiment: self.state_list.append(s_content) - def rf_pulse(self, length=None, phase=0, source=0): + def rf_pulse(self, length: float | None = None, phase: float = 0, source: int = 0) -> None: """ Make an rf pulse, including gating and phase switching. Only possible if Experiment is configured. @@ -183,7 +184,7 @@ class Experiment: self.ttls(length=length, value=self.rf_gates[source]+self.rf_sources[source]) ## Beginning of a new state - def state_start(self, time): + def state_start(self, time: float) -> None: """ Starts a state in the pulse programs with duration *time*. This must be closed with :func:`state_end`. @@ -194,13 +195,13 @@ class Experiment: self.total_time[-1] += time ## End of *state_start* - def state_end(self): + def state_end(self) -> None: """ Closes a previous :func:`state_start` """ self.state_list.append('\n') - def wait(self, time, ttls=None, gating=False): + def wait(self, time: float, ttls: int | None = None, gating: bool = False) -> None: """ Waits for a specified amount of time without performing any actions. @@ -241,6 +242,7 @@ class Experiment: """ if gating: + assert self.gating is not None, "gating time must be set for gating=True" time -= self.gating if ttls is not None: s_content = '' % ttls @@ -251,7 +253,7 @@ class Experiment: self.total_time[-1] += time - def record(self, samples, frequency, timelength=None, sensitivity = None, ttls=None, channels = 3, offset = None, impedance = None): + def record(self, samples: int, frequency: float, timelength: float | None = None, sensitivity: float | list[float] | None = None, ttls: int | None = None, channels: int = 3, offset: int | list[int] | None = None, impedance: float | list[float] | None = None) -> None: """ Records data with a given number of samples, sampling frequency, and sensitivity. This command starts data acquisition from the ADC (Analog-to-Digital Converter). @@ -365,7 +367,7 @@ class Experiment: ## Create a loop on the pulse programmer. Loop contents can not change inside the loop. # @params iterations Number of loop iterations - def loop_start(self, iterations): + def loop_start(self, iterations: int) -> None: """ creates a loop of given number of iterations and has to be closed by loop_end(). Commands inside the loop can not change, i.e., the parameters are the same for each loop run. @@ -384,7 +386,7 @@ class Experiment: self.loop_iterations.append(iterations) ## End loop state - def loop_end(self): + def loop_end(self) -> None: """ Closes a l:func:`loop_start` statement. """ @@ -396,7 +398,7 @@ class Experiment: self.total_time[-1] += looptime*loopiterations - def set_frequency(self, frequency, phase, ttls=0): + def set_frequency(self, frequency: float, phase: float, ttls: int = 0) -> None: """ Sets the frequency and phase of the frequency source and optionally further channels. The state length is 2 µs. Stabilisation time depends on RF source. @@ -412,7 +414,7 @@ class Experiment: self.total_time[-1] += 2e-6 - def set_pfg(self, dac_value=None, length=None, shape=('rec', 0), trigger=4, is_seq=False): + def set_pfg(self, dac_value: int | None = None, length: float | None = None, shape: tuple[str, float] = ('rec', 0), trigger: int = 4, is_seq: bool = False) -> None: """ This sets the value for the PFG, it also sets it back to dac_value=0 automatically. If you don't wish to do so (i.e. line shapes) set is_seq=1 @@ -480,7 +482,7 @@ class Experiment: else: # don't know what to do raise SyntaxError("form is unknown: %s"%form) - def set_dac(self, dac_value, dac_id=1, length=None, is_seq=False, ttls=0): + def set_dac(self, dac_value: int, dac_id: int = 1, length: float | None = None, is_seq: bool = False, ttls: int = 0) -> None: """ Sets the value for the DAC (Digital-to-Analog Converter) and optionally additional TTL lines. This command is used to control analog output signals, such as pulsed field gradients. @@ -544,7 +546,7 @@ class Experiment: self.total_time[-1] += 42*9e-8 - def set_phase(self, phase, ttls=0): + def set_phase(self, phase: float, ttls: int = 0) -> None: """ Sets the phase of the RF source to the specified value. @@ -589,7 +591,7 @@ class Experiment: self.total_time[-1] += 0.5e-6 - def set_description(self, key, value): + def set_description(self, key: str, value: object) -> None: """ Sets a description key-value pair in the experiment description dictionary. @@ -629,7 +631,7 @@ class Experiment: print('Warning: Overwriting existing description "%s" = "%s" with "%s"' % (key, self.description[key], value)) self.description[key] = value - def set_pts_local(self): + def set_pts_local(self) -> None: """ Sets the PTS310/PTS500 frequency source to local mode. State length is 2 µs. @@ -653,7 +655,7 @@ class Experiment: return self.job_id - def write_xml_string(self): + def write_xml_string(self) -> str: """ Returns the current program as a string @@ -706,14 +708,14 @@ class Experiment: return xml_string - def write_quit_job(self): + def write_quit_job(self) -> str: """ Returns a xml quit-job :returns: XML quit-job """ return '\n' - def get_length(self): + def get_length(self) -> float: timelength = 0.0 #calculate the correct timelength also for unclosed loops, if there is no unclosed loop #the timelength of this experiment is self.total_time[-1]. @@ -723,13 +725,13 @@ class Experiment: return timelength class Quit(Experiment): - def write_xml_string(self): + def write_xml_string(self) -> str: return '\n'%self.job_id # /Public Methods ------------------------------------------------------------------------------ -def self_test(): +def self_test() -> None: e = Experiment() e.set_description("key", "value") e.set_frequency(85e6, 90, ttls=16) diff --git a/src/damaris/experiments/dac.py b/src/damaris/experiments/dac.py index 8fab0a3..6c8eef3 100644 --- a/src/damaris/experiments/dac.py +++ b/src/damaris/experiments/dac.py @@ -2,7 +2,7 @@ """ This module holds everything connected with the DAC and PFG """ -def conv(I_out=0): +def conv(I_out: float = 0) -> int: """ converts the demanded Output current in Integer """ diff --git a/uv.lock b/uv.lock index 09ed734..8bd5d7a 100644 --- a/uv.lock +++ b/uv.lock @@ -938,6 +938,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] +[[package]] +name = "pyrefly" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/3e/ea6893be167fa14307d7f9a924bed9ecbbbd91b01953890b9b3c58ed5359/pyrefly-1.4.0.tar.gz", hash = "sha256:bb57c004a4842cfe93f93aebb5203ca6209a5f0c8bda47e8bc289e70940fd910", size = 6962062, upload-time = "2026-10-15T16:54:11.819Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/17/dfce6611acc00cb26667e5e1011438cc510f3067f961e0f07f7a0c68f416/pyrefly-1.4.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:460cec39bc8743915e9c2c8c10ba926ad0b29b099d89c1859fb97e0059f42fa7", size = 15705624, upload-time = "2026-10-15T16:53:43.412Z" }, + { url = "https://files.pythonhosted.org/packages/a6/78/a7b96442123fa6ed733de0d1ea933e35d6fc6b7c9cf16e05279abb493493/pyrefly-1.4.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5a6370d686b6cbbac8869832f09a52012888371956fbc68d446a349bc85ccd43", size = 14978669, upload-time = "2026-10-15T16:53:46.546Z" }, + { url = "https://files.pythonhosted.org/packages/23/97/dfcfec6a9d8948ab4b2746f7e87558a82bffabb7a54c22e7d3ce4bdc1932/pyrefly-1.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f1b16923a6583a6764e8aec07787e2ef4df644c7030294cba71339783a9451c", size = 15438858, upload-time = "2026-10-15T16:53:48.904Z" }, + { url = "https://files.pythonhosted.org/packages/31/12/6fa414aa87e398d6ddc7145a5d993b0975fc3511198dbb72c47873653ab6/pyrefly-1.4.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:688ad4cb4d6e711d67026fdcfb37b599f86061efe11ba5704287330e7041a69e", size = 16852241, upload-time = "2026-10-15T16:53:51.496Z" }, + { url = "https://files.pythonhosted.org/packages/a8/73/2d7312262cf6481d6ad46eed55b8f2592c3cff60f95cc2f30a114cd1484e/pyrefly-1.4.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5f3c9dddb6f3128beb49cf4e0f5cb88d4d17f039008d8e0c2be653966acd234", size = 16751917, upload-time = "2026-10-15T16:53:54.39Z" }, + { url = "https://files.pythonhosted.org/packages/2c/65/b44a1cb23667bcbcdc88558cbef79fa9e07a1152c2a1da148036b1a2e0d1/pyrefly-1.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6951f51687304caebfedf13dd1a943aeafdbc95abcc4e24a683b97467a77007", size = 16066345, upload-time = "2026-10-15T16:53:57.436Z" }, + { url = "https://files.pythonhosted.org/packages/a6/07/7ca6ff0c53e3a90ecdab05790d33f430fca5c48687613a0b182744166d98/pyrefly-1.4.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:838de4d4a8b9b8ed39bedd0b8b55629c69661d73a6fa3a3a4adff24b9dbeb399", size = 15461223, upload-time = "2026-10-15T16:54:00.364Z" }, + { url = "https://files.pythonhosted.org/packages/61/be/ec7b2f38a898ce5b2219dda730e87d31fc8cfb2108a3d5844f9d590f1281/pyrefly-1.4.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:afb9ce2e0ae3e6106fb45f6d47536ac92b6423e601e08401cdefa850066444f5", size = 16119099, upload-time = "2026-10-15T16:54:02.664Z" }, + { url = "https://files.pythonhosted.org/packages/6e/16/1b9549168b38a575d9c168e17b4b4ee30028d602ec0f7b07c198ef265dbe/pyrefly-1.4.0-py3-none-win32.whl", hash = "sha256:839db536d0ac29f1b23e2d974fb911075c2283e3e623755c45d826aafac175bc", size = 14858880, upload-time = "2026-10-15T16:54:05.114Z" }, + { url = "https://files.pythonhosted.org/packages/03/02/18f075d4de0e255f184f42fc28c1f0ecc9f49dd528678479fc90cf12246c/pyrefly-1.4.0-py3-none-win_amd64.whl", hash = "sha256:7aff37c2b711c8240ed650da0c6148237b327bf7e5dfacee65884f1adea22305", size = 15726220, upload-time = "2026-10-15T16:54:07.452Z" }, + { url = "https://files.pythonhosted.org/packages/92/62/92947076681550904c72a9dd03b24e92694ede9a48548f5e487c849e66ea/pyrefly-1.4.0-py3-none-win_arm64.whl", hash = "sha256:88879f0cb749e5fea81d9ff4c2d5431a70349c458bcdf0b61de72c79214d90a5", size = 14943690, upload-time = "2026-10-15T16:54:09.689Z" }, +] + [[package]] name = "pyserial" version = "3.5" @@ -976,6 +995,11 @@ dependencies = [ { name = "tables" }, ] +[package.dev-dependencies] +dev = [ + { name = "pyrefly" }, +] + [package.metadata] requires-dist = [ { name = "matplotlib", specifier = ">=1.4.1,<=3.6" }, @@ -990,6 +1014,9 @@ requires-dist = [ { name = "tables", specifier = ">=3.1.1" }, ] +[package.metadata.requires-dev] +dev = [{ name = "pyrefly" }] + [[package]] name = "pyxdg" version = "0.28"