From a91326e9eeb05c9e5a12a64071403347b51c825b Mon Sep 17 00:00:00 2001 From: Alex Myers Date: Fri, 11 Jul 2025 17:15:03 -0500 Subject: [PATCH 1/7] reckless: fix installer search Some installer procedures have more options for valid entypoint names than others. We iterate through each of their first choices, then their second choices, etc.. --- tools/reckless | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/reckless b/tools/reckless index 891bedb05b95..97fa1b2c5f33 100755 --- a/tools/reckless +++ b/tools/reckless @@ -821,6 +821,9 @@ class InferInstall(): for tier in range(0, 10): # Look for each installers preferred entrypoint format first for inst in INSTALLERS: + # All of this installer's entrypoint options exhausted. + if tier >= len(inst.entries): + continue fmt = inst.entries[tier] if '{name}' in fmt: pre = fmt.split('{name}')[0] From bf811ddf96ef996abadfc93cf31b2241570dbe68 Mon Sep 17 00:00:00 2001 From: Alex Myers Date: Thu, 17 Jul 2025 16:23:24 -0500 Subject: [PATCH 2/7] reckless: correct direct install from local repo subdirectory --- tools/reckless | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/reckless b/tools/reckless index 97fa1b2c5f33..880ea8d3424d 100755 --- a/tools/reckless +++ b/tools/reckless @@ -1398,13 +1398,13 @@ def install(plugin_name: str) -> Union[str, None]: src = None if direct_location: logging.debug(f"install of {name} requested from {direct_location}") - src = InstInfo(name, direct_location, None) - if not src.get_inst_details(): - src = None + src = InstInfo(name, direct_location, name) # Treating a local git repo as a directory allows testing # uncommitted changes. if src and src.srctype == Source.LOCAL_REPO: src.srctype = Source.DIRECTORY + if not src.get_inst_details(): + src = None if not direct_location or not src: log.debug(f"Searching for {name}") if search(name): From 9d73ec2e2c3cc85d8bbc4b6a79844afc43fb54e9 Mon Sep 17 00:00:00 2001 From: Alex Myers Date: Fri, 11 Jul 2025 17:20:57 -0500 Subject: [PATCH 3/7] reckless: add uv python env installation method uv is a python installation and package manager written in rust. We can use it to quickly install python package dependencies and configure our plugin's python virtual environment. To maintain consistency with our other reckless python installations, the venv is still activated in a wrapper which then imports the original python source. Changelog-added: reckless can now install python plugins using the uv package manager. --- tools/reckless | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/tools/reckless b/tools/reckless index 880ea8d3424d..c33b4ce7947a 100755 --- a/tools/reckless +++ b/tools/reckless @@ -986,6 +986,37 @@ def cargo_installation(cloned_plugin: InstInfo): return cloned_plugin +def install_python_uv(cloned_plugin: InstInfo): + """This uses the rust-based python plugin manager uv to manage the python + installation and create a virtual environment.""" + + source = Path(cloned_plugin.source_loc) / 'source' / cloned_plugin.name + # This virtual env path matches the other python installations and allows + # creating the wrapper in the same manner. Otherwise uv would build it in + # the source/{name} subdirectory. + cloned_plugin.venv = Path('.venv') + + # We want the virtual env at the head of our directory structure and uv + # will need a pyproject.toml there in order to get started. + (Path(cloned_plugin.source_loc) / 'pyproject.toml').\ + symlink_to(source / 'pyproject.toml') + + call = ['uv', '-v', 'sync'] + uv = run(call, cwd=str(cloned_plugin.source_loc), stdout=PIPE, stderr=PIPE, + text=True, check=False) + if uv.returncode != 0: + for line in uv.stderr.splitlines(): + log.debug(line) + log.error('Failed to install virtual environment') + raise InstallationFailure('Failed to create virtual environment!') + + # Delete entrypoint symlink so that a venv wrapper can take it's place + (Path(cloned_plugin.source_loc) / cloned_plugin.entry).unlink() + + create_wrapper(cloned_plugin) + return cloned_plugin + + python3venv = Installer('python3venv', exe='python3', manager='pip', entry='{name}.py') python3venv.add_entrypoint('{name}') @@ -997,6 +1028,7 @@ poetryvenv = Installer('poetryvenv', exe='python3', manager='poetry', entry='{name}.py') poetryvenv.add_entrypoint('{name}') poetryvenv.add_entrypoint('__init__.py') +poetryvenv.add_dependency_file('poetry.lock') poetryvenv.add_dependency_file('pyproject.toml') poetryvenv.dependency_call = install_to_python_virtual_environment @@ -1007,6 +1039,9 @@ pyprojectViaPip.add_entrypoint('__init__.py') pyprojectViaPip.add_dependency_file('pyproject.toml') pyprojectViaPip.dependency_call = install_to_python_virtual_environment +pythonuv = Installer('pythonuv', exe='python3', manager='uv', entry="{name}.py") +pythonuv.add_dependency_file('uv.lock') +pythonuv.dependency_call = install_python_uv # Nodejs plugin installer nodejs = Installer('nodejs', exe='node', @@ -1020,7 +1055,8 @@ rust_cargo = Installer('rust', manager='cargo', entry='Cargo.toml') rust_cargo.add_dependency_file('Cargo.toml') rust_cargo.dependency_call = cargo_installation -INSTALLERS = [python3venv, poetryvenv, pyprojectViaPip, nodejs, rust_cargo] +INSTALLERS = [pythonuv, python3venv, poetryvenv, pyprojectViaPip, nodejs, + rust_cargo] def help_alias(targets: list): From 61fa5d96c278c707e7c2fd6054835775f1d1f859 Mon Sep 17 00:00:00 2001 From: Alex Myers Date: Mon, 11 Aug 2025 13:44:07 -0500 Subject: [PATCH 4/7] pytest: add reckless regex search --- tests/test_reckless.py | 86 ++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/tests/test_reckless.py b/tests/test_reckless.py index e0244fa10659..1a223f401c69 100644 --- a/tests/test_reckless.py +++ b/tests/test_reckless.py @@ -5,6 +5,7 @@ from pyln.testing.utils import VALGRIND import pytest import os +import re import shutil import time import unittest @@ -91,25 +92,51 @@ def canned_github_server(directory): server.terminate() +class RecklessResult: + def __init__(self, process, returncode, stdout, stderr): + self.process = process + self.returncode = returncode + self.stdout = stdout + self.stderr = stderr + + def __repr__(self): + return f'self.returncode, self.stdout, self.stderr' + + def search_stdout(self, regex): + """return the matching regex line from reckless output.""" + ex = re.compile(regex) + matching = [] + for line in self.stdout: + if ex.search(line): + matching.append(line) + return matching + + def reckless(cmds: list, dir: PosixPath = None, - autoconfirm=True, timeout: int = 15): + autoconfirm=True, timeout: int = 60): '''Call the reckless executable, optionally with a directory.''' if dir is not None: cmds.insert(0, "-l") cmds.insert(1, str(dir)) cmds.insert(0, "tools/reckless") + if autoconfirm: + process_input = 'Y\n' + else: + process_input = None r = subprocess.run(cmds, capture_output=True, encoding='utf-8', env=my_env, - input='Y\n') + input=process_input, timeout=timeout) + stdout = r.stdout.splitlines() + stderr = r.stderr.splitlines() print(" ".join(r.args), "\n") print("***RECKLESS STDOUT***") - for l in r.stdout.splitlines(): + for l in stdout: print(l) print('\n') print("***RECKLESS STDERR***") - for l in r.stderr.splitlines(): + for l in stderr: print(l) print('\n') - return r + return RecklessResult(r, r.returncode, stdout, stderr) def get_reckless_node(node_factory): @@ -119,28 +146,13 @@ def get_reckless_node(node_factory): return node -def check_stderr(stderr): - def output_okay(out): - for warning in ['[notice]', 'WARNING:', 'npm WARN', - 'npm notice', 'DEPRECATION:', 'Creating virtualenv', - 'config file not found:', 'press [Y]']: - if out.startswith(warning): - return True - return False - for e in stderr.splitlines(): - if len(e) < 1: - continue - # Don't err on verbosity from pip, npm - assert output_okay(e) - - def test_basic_help(): '''Validate that argparse provides basic help info. This requires no config options passed to reckless.''' r = reckless(["-h"]) assert r.returncode == 0 - assert "positional arguments:" in r.stdout.splitlines() - assert "options:" in r.stdout.splitlines() or "optional arguments:" in r.stdout.splitlines() + assert r.search_stdout("positional arguments:") + assert r.search_stdout("options:") or r.search_stdout("optional arguments:") def test_contextual_help(node_factory): @@ -149,7 +161,7 @@ def test_contextual_help(node_factory): 'enable', 'disable', 'source']: r = reckless([subcmd, "-h"], dir=n.lightning_dir) assert r.returncode == 0 - assert "positional arguments:" in r.stdout.splitlines() + assert r.search_stdout("positional arguments:") def test_sources(node_factory): @@ -194,7 +206,7 @@ def test_search(node_factory): n = get_reckless_node(node_factory) r = reckless([f"--network={NETWORK}", "search", "testplugpass"], dir=n.lightning_dir) assert r.returncode == 0 - assert 'found testplugpass in source: https://github.com/lightningd/plugins' in r.stdout + assert r.search_stdout('found testplugpass in source: https://github.com/lightningd/plugins') def test_install(node_factory): @@ -202,9 +214,9 @@ def test_install(node_factory): n = get_reckless_node(node_factory) r = reckless([f"--network={NETWORK}", "-v", "install", "testplugpass"], dir=n.lightning_dir) assert r.returncode == 0 - assert 'dependencies installed successfully' in r.stdout - assert 'plugin installed:' in r.stdout - assert 'testplugpass enabled' in r.stdout + assert r.search_stdout('dependencies installed successfully') + assert r.search_stdout('plugin installed:') + assert r.search_stdout('testplugpass enabled') check_stderr(r.stderr) plugin_path = Path(n.lightning_dir) / 'reckless/testplugpass' print(plugin_path) @@ -217,9 +229,9 @@ def test_poetry_install(node_factory): n = get_reckless_node(node_factory) r = reckless([f"--network={NETWORK}", "-v", "install", "testplugpyproj"], dir=n.lightning_dir) assert r.returncode == 0 - assert 'dependencies installed successfully' in r.stdout - assert 'plugin installed:' in r.stdout - assert 'testplugpyproj enabled' in r.stdout + assert r.search_stdout('dependencies installed successfully') + assert r.search_stdout('plugin installed:') + assert r.search_stdout('testplugpyproj enabled') check_stderr(r.stderr) plugin_path = Path(n.lightning_dir) / 'reckless/testplugpyproj' print(plugin_path) @@ -240,7 +252,7 @@ def test_local_dir_install(node_factory): assert r.returncode == 0 r = reckless([f"--network={NETWORK}", "-v", "install", "testplugpass"], dir=n.lightning_dir) assert r.returncode == 0 - assert 'testplugpass enabled' in r.stdout + assert r.search_stdout('testplugpass enabled') plugin_path = Path(n.lightning_dir) / 'reckless/testplugpass' print(plugin_path) assert os.path.exists(plugin_path) @@ -249,9 +261,9 @@ def test_local_dir_install(node_factory): r = reckless(['uninstall', 'testplugpass', '-v'], dir=n.lightning_dir) assert not os.path.exists(plugin_path) r = reckless(['source', 'remove', source_dir], dir=n.lightning_dir) - assert 'plugin source removed' in r.stdout + assert r.search_stdout('plugin source removed') r = reckless(['install', '-v', source_dir], dir=n.lightning_dir) - assert 'testplugpass enabled' in r.stdout + assert r.search_stdout('testplugpass enabled') assert os.path.exists(plugin_path) @@ -263,9 +275,9 @@ def test_disable_enable(node_factory): r = reckless([f"--network={NETWORK}", "-v", "install", "testPlugPass"], dir=n.lightning_dir) assert r.returncode == 0 - assert 'dependencies installed successfully' in r.stdout - assert 'plugin installed:' in r.stdout - assert 'testplugpass enabled' in r.stdout + assert r.search_stdout('dependencies installed successfully') + assert r.search_stdout('plugin installed:') + assert r.search_stdout('testplugpass enabled') check_stderr(r.stderr) plugin_path = Path(n.lightning_dir) / 'reckless/testplugpass' print(plugin_path) @@ -278,7 +290,7 @@ def test_disable_enable(node_factory): r = reckless([f"--network={NETWORK}", "-v", "enable", "testplugpass.py"], dir=n.lightning_dir) assert r.returncode == 0 - assert 'testplugpass enabled' in r.stdout + assert r.search_stdout('testplugpass enabled') test_plugin = {'name': str(plugin_path / 'testplugpass.py'), 'active': True, 'dynamic': True} time.sleep(1) From 8eee614a3556246d61dbbaef8d3036a00d0189fc Mon Sep 17 00:00:00 2001 From: Alex Myers Date: Mon, 11 Aug 2025 14:08:49 -0500 Subject: [PATCH 5/7] pytest: refactor reckless check_stderr --- tests/test_reckless.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/test_reckless.py b/tests/test_reckless.py index 1a223f401c69..229c8d56242e 100644 --- a/tests/test_reckless.py +++ b/tests/test_reckless.py @@ -111,6 +111,21 @@ def search_stdout(self, regex): matching.append(line) return matching + def check_stderr(self): + def output_okay(out): + for warning in ['[notice]', 'WARNING:', 'npm WARN', + 'npm notice', 'DEPRECATION:', 'Creating virtualenv', + 'config file not found:', 'press [Y]']: + if out.startswith(warning): + return True + return False + for e in self.stderr: + if len(e) < 1: + continue + # Don't err on verbosity from pip, npm + if not output_okay(e): + raise Exception(f'reckless stderr contains `{e}`') + def reckless(cmds: list, dir: PosixPath = None, autoconfirm=True, timeout: int = 60): @@ -217,7 +232,7 @@ def test_install(node_factory): assert r.search_stdout('dependencies installed successfully') assert r.search_stdout('plugin installed:') assert r.search_stdout('testplugpass enabled') - check_stderr(r.stderr) + r.check_stderr() plugin_path = Path(n.lightning_dir) / 'reckless/testplugpass' print(plugin_path) assert os.path.exists(plugin_path) @@ -232,7 +247,7 @@ def test_poetry_install(node_factory): assert r.search_stdout('dependencies installed successfully') assert r.search_stdout('plugin installed:') assert r.search_stdout('testplugpyproj enabled') - check_stderr(r.stderr) + r.check_stderr() plugin_path = Path(n.lightning_dir) / 'reckless/testplugpyproj' print(plugin_path) assert os.path.exists(plugin_path) @@ -278,7 +293,7 @@ def test_disable_enable(node_factory): assert r.search_stdout('dependencies installed successfully') assert r.search_stdout('plugin installed:') assert r.search_stdout('testplugpass enabled') - check_stderr(r.stderr) + r.check_stderr() plugin_path = Path(n.lightning_dir) / 'reckless/testplugpass' print(plugin_path) assert os.path.exists(plugin_path) From 358aad8ac7dff93b8373046d2ad512b310ce984e Mon Sep 17 00:00:00 2001 From: Alex Myers Date: Mon, 11 Aug 2025 14:14:25 -0500 Subject: [PATCH 6/7] pytest: test python plugin installation via uv --- .../lightningd/testpluguv/README.md | 0 .../lightningd/testpluguv/pyproject.toml | 9 + .../lightningd/testpluguv/testpluguv.py | 25 +++ .../lightningd/testpluguv/uv.lock | 188 ++++++++++++++++++ .../rkls_api_lightningd_plugins.json | 9 + tests/test_reckless.py | 16 ++ 6 files changed, 247 insertions(+) create mode 100644 tests/data/recklessrepo/lightningd/testpluguv/README.md create mode 100644 tests/data/recklessrepo/lightningd/testpluguv/pyproject.toml create mode 100755 tests/data/recklessrepo/lightningd/testpluguv/testpluguv.py create mode 100644 tests/data/recklessrepo/lightningd/testpluguv/uv.lock diff --git a/tests/data/recklessrepo/lightningd/testpluguv/README.md b/tests/data/recklessrepo/lightningd/testpluguv/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/data/recklessrepo/lightningd/testpluguv/pyproject.toml b/tests/data/recklessrepo/lightningd/testpluguv/pyproject.toml new file mode 100644 index 000000000000..430f86b8df87 --- /dev/null +++ b/tests/data/recklessrepo/lightningd/testpluguv/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "testpluguv" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "pyln-client>=24.4", +] diff --git a/tests/data/recklessrepo/lightningd/testpluguv/testpluguv.py b/tests/data/recklessrepo/lightningd/testpluguv/testpluguv.py new file mode 100755 index 000000000000..50fc52a85753 --- /dev/null +++ b/tests/data/recklessrepo/lightningd/testpluguv/testpluguv.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +from pyln.client import Plugin + +plugin = Plugin() + +__version__ = 'v1' + + +@plugin.init() +def init(options, configuration, plugin, **kwargs): + plugin.log("testpluguv initialized") + + +@plugin.method("uvplugintest") +def uvplugintest(plugin): + return "I live." + + +@plugin.method("getuvpluginversion") +def getuvpluginversion(plugin): + "to test commit/tag checkout" + return __version__ + + +plugin.run() diff --git a/tests/data/recklessrepo/lightningd/testpluguv/uv.lock b/tests/data/recklessrepo/lightningd/testpluguv/uv.lock new file mode 100644 index 000000000000..5775be9323b7 --- /dev/null +++ b/tests/data/recklessrepo/lightningd/testpluguv/uv.lock @@ -0,0 +1,188 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "asn1crypto" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/cf/d547feed25b5244fcb9392e288ff9fdc3280b10260362fc45d37a798a6ee/asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c", size = 121080, upload-time = "2022-03-15T14:46:52.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67", size = 105045, upload-time = "2022-03-15T14:46:51.055Z" }, +] + +[[package]] +name = "base58" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/45/8ae61209bb9015f516102fa559a2914178da1d5868428bd86a1b4421141d/base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c", size = 6528, upload-time = "2021-10-30T22:12:17.858Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/45/ec96b29162a402fc4c1c5512d114d7b3787b9d1c2ec241d9568b4816ee23/base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2", size = 5621, upload-time = "2021-10-30T22:12:16.658Z" }, +] + +[[package]] +name = "bitarray" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/ee/3b2fcbac3a4192e5d079aaa1850dff2f9ac625861c4c644819c2b34292ec/bitarray-3.6.0.tar.gz", hash = "sha256:20febc849a1f858e6a57a7d47b323fe9e727c579ddd526d317ad8831748a66a8", size = 147946, upload-time = "2025-07-29T18:03:56.681Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/2c/21066c7a97b2c88037b0fc04480fa13b0031c30c6f70452dc9c84fb2b087/bitarray-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3f96f57cea35ba19fd23a20b38fa0dfa3d87d582507129b8c8e314aa298f59b", size = 144156, upload-time = "2025-07-29T18:01:35.58Z" }, + { url = "https://files.pythonhosted.org/packages/34/a5/9cc42ea0c440ac1c2a65375688ac5891da12b3820f4a32440791d25ed668/bitarray-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:81e84054b22babcd6c5cc1eac0de2bfc1054ecdf742720cbfb36efbe89ec6c30", size = 140916, upload-time = "2025-07-29T18:01:36.67Z" }, + { url = "https://files.pythonhosted.org/packages/d7/66/709d259d855528213b1099facddb08d6108cb0074cf88dc357cdd07bacff/bitarray-3.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca643295bf5441dd38dadf7571ca4b63961820eedbffbe46ceba0893bf226203", size = 324713, upload-time = "2025-07-29T18:01:37.925Z" }, + { url = "https://files.pythonhosted.org/packages/6c/67/831e366ea4f0d52d622482b8475f87040cbc210d8f5f383935a4cc6363fe/bitarray-3.6.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139963494fc3dd5caee5e38c0a03783ef50be118565e94b1dbb0210770f0b32d", size = 341300, upload-time = "2025-07-29T18:01:39.56Z" }, + { url = "https://files.pythonhosted.org/packages/66/c9/197375b63ca768ac8b1e624f27dc0eccdd451f94c6b9bf8950500d8da134/bitarray-3.6.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:243825f56b58bef28bfc602992a8c6d09bbc625628c195498d6020120d632a09", size = 333724, upload-time = "2025-07-29T18:01:40.861Z" }, + { url = "https://files.pythonhosted.org/packages/e1/23/96c882d798b8bc9d5354ad1fba18ad3ad4f3c0a661a296c8e51ca2941e0f/bitarray-3.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:583b46b3ba44121de5e87e95ae379932dc5fd2e37ebdf2c11a6d7975891425c1", size = 327276, upload-time = "2025-07-29T18:01:42.039Z" }, + { url = "https://files.pythonhosted.org/packages/20/8e/51751fe0e6f9fe7980b0467b471ba9ab8d1713a2a6576980d18143511656/bitarray-3.6.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f0be27d06732e2833b672a8fcc32fa195bdb22161eb88f8890de15e30264a01", size = 314903, upload-time = "2025-07-29T18:01:43.302Z" }, + { url = "https://files.pythonhosted.org/packages/49/7a/e4db9876e6e8bb261e64a384d3adb4372f13099b356e559cec85d022b897/bitarray-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:507e567aee4806576e20752f22533e8b7ec61e7e75062a7ce9222a0675aa0da6", size = 322551, upload-time = "2025-07-29T18:01:44.548Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5a/9460070e6cb671067cc2e115a82da6fc9ef0958542b98b07a5ed4a05a97b/bitarray-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:22188943a29072b684cd7c99e0b2cfc0af317cea3366c583d820507e6d1f2ed4", size = 316128, upload-time = "2025-07-29T18:01:45.789Z" }, + { url = "https://files.pythonhosted.org/packages/34/6f/f5d78c8e908750b9c3d5839eca2af5f6e99d6c7fe8a0498ef79a1af90bd8/bitarray-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f92462ea3888c99439f58f7561ecd5dd4cf8b8b1b259ccf5376667b8c46ee747", size = 339337, upload-time = "2025-07-29T18:01:47.684Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d3/f740b601eae4e28e22d8560877fe9881f1b7a96fcb23b186e8580d328929/bitarray-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3800f3c8c9780f281cf590543fd4b3278fea6988202273a260ecc58136895efb", size = 338607, upload-time = "2025-07-29T18:01:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/4e/81/b9451089eea0ef66996852d2694b0f5afc0a76b1bc45c9a4f8204ae8674d/bitarray-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a50a66fa34dd7f9dcdbc7602a1b7bf6f9ab030b4f43e892324193423d9ede180", size = 324788, upload-time = "2025-07-29T18:01:51.454Z" }, + { url = "https://files.pythonhosted.org/packages/82/e8/80620fc60ad34bff647881a4f25c15b992c524e0f7af9c7c6c573b03556e/bitarray-3.6.0-cp313-cp313-win32.whl", hash = "sha256:afa24e5750c9b89ad5a7efef037efe49f4e339f20a94bf678c422c0c71e1207a", size = 137841, upload-time = "2025-07-29T18:01:52.95Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ee/303be88b847da29a067babc690e231d7838520dc1af57d14dad5a7ca095c/bitarray-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4c5e7edf1e7bcbde3b52058f171a411e2a24a081b3e951d685dfea4c3c383d5", size = 144820, upload-time = "2025-07-29T18:01:54.137Z" }, +] + +[[package]] +name = "bitstring" +version = "4.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bitarray" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/a8/a80c890db75d5bdd5314b5de02c4144c7de94fd0cefcae51acaeb14c6a3f/bitstring-4.3.1.tar.gz", hash = "sha256:a08bc09d3857216d4c0f412a1611056f1cc2b64fd254fb1e8a0afba7cfa1a95a", size = 251426, upload-time = "2025-03-22T09:39:06.978Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/2d/174566b533755ddf8efb32a5503af61c756a983de379f8ad3aed6a982d38/bitstring-4.3.1-py3-none-any.whl", hash = "sha256:69d1587f0ac18dc7d93fc7e80d5f447161a33e57027e726dc18a0a8bacf1711a", size = 71930, upload-time = "2025-03-22T09:39:05.163Z" }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, +] + +[[package]] +name = "coincurve" +version = "20.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asn1crypto" }, + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/4c/9e5e51e6c12cec6444c86697992f9c6ccffa19f84d042ff939c8b89206ff/coincurve-20.0.0.tar.gz", hash = "sha256:872419e404300302e938849b6b92a196fabdad651060b559dc310e52f8392829", size = 122865, upload-time = "2024-06-02T18:15:50.787Z" } + +[[package]] +name = "cryptography" +version = "42.0.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/a7/1498799a2ea06148463a9a2c10ab2f6a921a74fb19e231b27dc412a748e2/cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2", size = 671250, upload-time = "2024-06-04T19:55:08.609Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/8b/1b929ba8139430e09e140e6939c2b29c18df1f2fc2149e41bdbdcdaf5d1f/cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e", size = 5899961, upload-time = "2024-06-04T19:53:57.933Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5d/31d833daa800e4fab33209843095df7adb4a78ea536929145534cbc15026/cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d", size = 3114353, upload-time = "2024-06-04T19:54:12.171Z" }, + { url = "https://files.pythonhosted.org/packages/5d/32/f6326c70a9f0f258a201d3b2632bca586ea24d214cec3cf36e374040e273/cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902", size = 3647773, upload-time = "2024-06-04T19:54:07.051Z" }, + { url = "https://files.pythonhosted.org/packages/35/66/2d87e9ca95c82c7ee5f2c09716fc4c4242c1ae6647b9bd27e55e920e9f10/cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801", size = 3839763, upload-time = "2024-06-04T19:54:30.383Z" }, + { url = "https://files.pythonhosted.org/packages/c2/de/8083fa2e68d403553a01a9323f4f8b9d7ffed09928ba25635c29fb28c1e7/cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949", size = 3632661, upload-time = "2024-06-04T19:54:32.955Z" }, + { url = "https://files.pythonhosted.org/packages/07/40/d6f6819c62e808ea74639c3c640f7edd636b86cce62cb14943996a15df92/cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9", size = 3851536, upload-time = "2024-06-04T19:53:53.131Z" }, + { url = "https://files.pythonhosted.org/packages/5c/46/de71d48abf2b6d3c808f4fbb0f4dc44a4e72786be23df0541aa2a3f6fd7e/cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583", size = 3754209, upload-time = "2024-06-04T19:54:55.259Z" }, + { url = "https://files.pythonhosted.org/packages/25/c9/86f04e150c5d5d5e4a731a2c1e0e43da84d901f388e3fea3d5de98d689a7/cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7", size = 3923551, upload-time = "2024-06-04T19:54:16.46Z" }, + { url = "https://files.pythonhosted.org/packages/53/c2/903014dafb7271fb148887d4355b2e90319cad6e810663be622b0c933fc9/cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b", size = 3739265, upload-time = "2024-06-04T19:54:23.194Z" }, + { url = "https://files.pythonhosted.org/packages/95/26/82d704d988a193cbdc69ac3b41c687c36eaed1642cce52530ad810c35645/cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7", size = 3937371, upload-time = "2024-06-04T19:55:04.303Z" }, + { url = "https://files.pythonhosted.org/packages/cf/71/4e0d05c9acd638a225f57fb6162aa3d03613c11b76893c23ea4675bb28c5/cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2", size = 2438849, upload-time = "2024-06-04T19:54:27.39Z" }, + { url = "https://files.pythonhosted.org/packages/06/0f/78da3cad74f2ba6c45321dc90394d70420ea846730dc042ef527f5a224b5/cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba", size = 2889090, upload-time = "2024-06-04T19:54:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/60/12/f064af29190cdb1d38fe07f3db6126091639e1dece7ec77c4ff037d49193/cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28", size = 5901232, upload-time = "2024-06-04T19:54:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/43/c2/4a3eef67e009a522711ebd8ac89424c3a7fe591ece7035d964419ad52a1d/cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e", size = 3648711, upload-time = "2024-06-04T19:54:44.323Z" }, + { url = "https://files.pythonhosted.org/packages/49/1c/9f6d13cc8041c05eebff1154e4e71bedd1db8e174fff999054435994187a/cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70", size = 3841968, upload-time = "2024-06-04T19:54:57.911Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f9/c3d4f19b82bdb25a3d857fe96e7e571c981810e47e3f299cc13ac429066a/cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c", size = 3633032, upload-time = "2024-06-04T19:54:48.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e2/b7e6e8c261536c489d9cf908769880d94bd5d9a187e166b0dc838d2e6a56/cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7", size = 3852478, upload-time = "2024-06-04T19:54:50.599Z" }, + { url = "https://files.pythonhosted.org/packages/a2/68/e16751f6b859bc120f53fddbf3ebada5c34f0e9689d8af32884d8b2e4b4c/cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e", size = 3754102, upload-time = "2024-06-04T19:54:46.231Z" }, + { url = "https://files.pythonhosted.org/packages/0f/38/85c74d0ac4c540780e072b1e6f148ecb718418c1062edcb20d22f3ec5bbb/cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961", size = 3925042, upload-time = "2024-06-04T19:54:34.767Z" }, + { url = "https://files.pythonhosted.org/packages/89/f4/a8b982e88eb5350407ebdbf4717b55043271d878705329e107f4783555f2/cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1", size = 3738833, upload-time = "2024-06-04T19:54:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2b/be327b580645927bb1a1f32d5a175b897a9b956bc085b095e15c40bac9ed/cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14", size = 3938751, upload-time = "2024-06-04T19:54:37.837Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d5/c6a78ffccdbe4516711ebaa9ed2c7eb6ac5dfa3dc920f2c7e920af2418b0/cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c", size = 2439281, upload-time = "2024-06-04T19:53:55.903Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7b/b0d330852dd5953daee6b15f742f15d9f18e9c0154eb4cfcc8718f0436da/cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a", size = 2886038, upload-time = "2024-06-04T19:54:18.707Z" }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, +] + +[[package]] +name = "pyln-bolt7" +version = "1.0.246" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/3c/6d1d6643c6a501e998c17c25d40c3b68ab75be1d16d9055e6a9ffba30fe4/pyln-bolt7-1.0.246.tar.gz", hash = "sha256:2b53744fa21c1b12d2c9c9df153651b122e38fa65d4a5c3f2957317ee148e089", size = 17905, upload-time = "2022-08-31T17:28:20.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/f1/30b626e7cec279a2f84084898594c8e84537d6d9af9afbe9858f9a6d8e13/pyln_bolt7-1.0.246-py3-none-any.whl", hash = "sha256:54d48ec27fdc8751762cb068b0a9f2757a58fb57933c6d8f8255d02c27eb63c5", size = 18811, upload-time = "2022-08-31T17:28:22.137Z" }, +] + +[[package]] +name = "pyln-client" +version = "25.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyln-bolt7" }, + { name = "pyln-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/f5/70dae7e6985ba44f53e1c634e204ecc4e9ccde8de6cd90e009672203023f/pyln_client-25.5.tar.gz", hash = "sha256:18656e667c7218f8b40c70f893b936aef4518e87e3ce99081bd52f89e6e5af85", size = 36015, upload-time = "2025-06-16T18:13:48.973Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/45/c504ed8ea59577d8093d377e2f657482f08e87b93aaa039e64421297ff55/pyln_client-25.5-py3-none-any.whl", hash = "sha256:269f9ab346ff679b532af2d24c2a524201884efc5b4781dd40685c8ccaec9507", size = 37362, upload-time = "2025-06-16T18:13:47.394Z" }, +] + +[[package]] +name = "pyln-proto" +version = "25.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "base58" }, + { name = "bitstring" }, + { name = "coincurve" }, + { name = "cryptography" }, + { name = "pysocks" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/6f/48b7879c11b698b0004e1b9cf820ce26454fbfa893181a009df7d7184189/pyln_proto-25.5.tar.gz", hash = "sha256:c5e38b726123af723f8c6a4f38ab310cbec46579f52c8e6f666e6beef320b96c", size = 27314, upload-time = "2025-06-16T18:13:46.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/31/4f5ef88ec5abe6a2e2b84f1bb5e1d0818d07f8afd372570916493849bf6c/pyln_proto-25.5-py3-none-any.whl", hash = "sha256:31abcf193744d6253b7f06b7712983c6a4d4c28815d46e1f3803eac17bfe1cf9", size = 31902, upload-time = "2025-06-16T18:13:45.07Z" }, +] + +[[package]] +name = "pysocks" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429, upload-time = "2019-09-20T02:07:35.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" }, +] + +[[package]] +name = "testpluguv" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pyln-client" }, +] + +[package.metadata] +requires-dist = [{ name = "pyln-client", specifier = ">=24.4" }] diff --git a/tests/data/recklessrepo/rkls_api_lightningd_plugins.json b/tests/data/recklessrepo/rkls_api_lightningd_plugins.json index 3a0484f4f1ee..a91c4844d898 100644 --- a/tests/data/recklessrepo/rkls_api_lightningd_plugins.json +++ b/tests/data/recklessrepo/rkls_api_lightningd_plugins.json @@ -8,6 +8,15 @@ "download_url": null, "type": "dir" }, + { + "name": "testpluguv", + "path": "testpluguv", + "url": "https://api.github.com/repos/lightningd/plugins/contents/webhook?ref=master", + "html_url": "https://github.com/lightningd/plugins/tree/master/testpluguv", + "git_url": "https://api.github.com/repos/lightningd/plugins/git/trees/testpluguv", + "download_url": null, + "type": "dir" + }, { "name": "testplugfail", "path": "testplugfail", diff --git a/tests/test_reckless.py b/tests/test_reckless.py index 229c8d56242e..64efa6ab6418 100644 --- a/tests/test_reckless.py +++ b/tests/test_reckless.py @@ -349,3 +349,19 @@ def test_tag_install(node_factory): if header == 'requested commit': assert line == 'v1' header = line + + +def test_reckless_uv_install(node_factory): + node = get_reckless_node(node_factory) + node.start() + r = reckless([f"--network={NETWORK}", "-v", "install", "testpluguv"], + dir=node.lightning_dir) + assert r.returncode == 0 + installed_path = Path(node.lightning_dir) / 'reckless/testpluguv' + assert installed_path.is_dir() + assert node.rpc.uvplugintest() == 'I live.' + version = node.rpc.getuvpluginversion() + assert version == 'v1' + + assert r.search_stdout('using installer pythonuv') + r.check_stderr() From 705c5e42d5d1c7e69e672cbeace9c45b02e259ff Mon Sep 17 00:00:00 2001 From: Alex Myers Date: Mon, 11 Aug 2025 16:08:05 -0500 Subject: [PATCH 7/7] reckless: add uv installer support for legacy projects Those that only have a requirements.txt can be installed with uv even if it's not managing the project requirements. --- tools/reckless | 53 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/tools/reckless b/tools/reckless index c33b4ce7947a..5dba0daaa6a7 100755 --- a/tools/reckless +++ b/tools/reckless @@ -1017,6 +1017,51 @@ def install_python_uv(cloned_plugin: InstInfo): return cloned_plugin +def install_python_uv_legacy(cloned_plugin: InstInfo): + """Install a python plugin with uv that was created with a requirements.txt. + This requires creating a bare virtual environment with uv first.""" + source = Path(cloned_plugin.source_loc) / 'source' / cloned_plugin.name + cloned_plugin.venv = Path('.venv') + (Path(cloned_plugin.source_loc) / 'pyproject.toml').\ + symlink_to(source / 'pyproject.toml') + (Path(cloned_plugin.source_loc) / 'requirements.txt').\ + symlink_to(source / 'requirements.txt') + + venv = run(['uv', '-v', 'venv'], cwd=str(cloned_plugin.source_loc), + stdout=PIPE, stderr=PIPE, text=True, check=False) + if venv.returncode != 0: + for line in venv.stderr.splitlines(): + log.debug(line) + log.error('Failed to create virtual environment') + raise InstallationFailure('Failed to create virtual environment!') + for line in venv.stdout.splitlines(): + log.debug(line) + for line in venv.stderr.splitlines(): + log.debug(line) + # Running this as a shell allows overriding any active virtual environment + # which would make uv skip installing packages already present in the + # current env. + call = ['. .venv/bin/activate; uv -v pip install -r requirements.txt'] + uv = run(call, shell=True, cwd=str(cloned_plugin.source_loc), + stdout=PIPE, stderr=PIPE, text=True, check=False) + if uv.returncode != 0: + for line in uv.stderr.splitlines(): + log.debug(line) + log.error('Failed to install virtual environment') + raise InstallationFailure('Failed to create virtual environment!') + for line in uv.stdout.splitlines(): + log.debug(line) + for line in uv.stderr.splitlines(): + log.debug(line) + + # Delete entrypoint symlink so that a venv wrapper can take it's place + (Path(cloned_plugin.source_loc) / cloned_plugin.entry).unlink() + + create_wrapper(cloned_plugin) + log.info('dependencies installed successfully') + return cloned_plugin + + python3venv = Installer('python3venv', exe='python3', manager='pip', entry='{name}.py') python3venv.add_entrypoint('{name}') @@ -1043,6 +1088,10 @@ pythonuv = Installer('pythonuv', exe='python3', manager='uv', entry="{name}.py") pythonuv.add_dependency_file('uv.lock') pythonuv.dependency_call = install_python_uv +pythonuvlegacy = Installer('pythonuvlegacy', exe='python3', manager='uv', entry='{name}.py') +pythonuvlegacy.add_dependency_file('requirements.txt') +pythonuvlegacy.dependency_call = install_python_uv_legacy + # Nodejs plugin installer nodejs = Installer('nodejs', exe='node', manager='npm', entry='{name}.js') @@ -1055,8 +1104,8 @@ rust_cargo = Installer('rust', manager='cargo', entry='Cargo.toml') rust_cargo.add_dependency_file('Cargo.toml') rust_cargo.dependency_call = cargo_installation -INSTALLERS = [pythonuv, python3venv, poetryvenv, pyprojectViaPip, nodejs, - rust_cargo] +INSTALLERS = [pythonuv, pythonuvlegacy, python3venv, poetryvenv, + pyprojectViaPip, nodejs, rust_cargo] def help_alias(targets: list):