feat: add multiple, remote recipients support

This commit is contained in:
cătălin 2023-05-09 09:11:36 +02:00
commit c6ac0d6043
Signed by: catalin
GPG key ID: 0178DF42F43E5FD2
10 changed files with 220 additions and 90 deletions

View file

@ -22,7 +22,7 @@ repos:
args: [ --fix=lf ] args: [ --fix=lf ]
- repo: https://github.com/charliermarsh/ruff-pre-commit - repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.260 rev: v0.0.265
hooks: hooks:
- id: ruff - id: ruff
args: args:

View file

@ -11,13 +11,21 @@
it encrypts the new contents into an [age](https://github.com/FiloSottile/age) file that it encrypts the new contents into an [age](https://github.com/FiloSottile/age) file that
you can store, _relatively_ safe, anywhere. you can store, _relatively_ safe, anywhere.
## Features
- Simple notebooks management with paths autocompletion
- Passphrase-less, fully-encrypted notes, compatible with existing SSH keys
- No external `age` binary needed
- Almost all `age` advantages, like having multiple keys for encryption and decryption
- Remote (HTTP) public keys import: e.g: github.com/<username>.keys
## Install ## Install
```shell ```shell
pipx install halig # or pip pipx install halig # or pip
``` ```
## Setup TLDR ## Setup TLDR
```shell ```shell
@ -26,9 +34,13 @@ ssh-keygen -t ed25519
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/halig" mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/halig"
cat << EOF > "${XDG_CONFIG_HOME:-$HOME/.config}/halig/halig.yml" cat << EOF > "${XDG_CONFIG_HOME:-$HOME/.config}/halig/halig.yml"
--- ---
notebooks_root_path: /home/$(id -un)/Documents/Notebooks notebooks_root_path: ~/Documents/Notebooks
identity_path: /home/$(id -un)/.ssh/id_ed25519 identity_paths:
recipient_path: /home/$(id -un)/.ssh/id_ed25519.pub - ~/.ssh/id_ed25519
recipient_path:
- ~/.ssh/id_ed25519.pub
- https://github.com/<username>.keys
- https://gitlab.com/<username>.keys
EOF EOF
``` ```
@ -38,4 +50,5 @@ EOF
halig edit some_notebook # edit today's note relative to <notebooks_root_path>/some_notebook halig edit some_notebook # edit today's note relative to <notebooks_root_path>/some_notebook
halig edit some_notebook/foo # edit <notebooks_root_path>/some_notebook/foo.age halig edit some_notebook/foo # edit <notebooks_root_path>/some_notebook/foo.age
halig notebooks # list current notebooks halig notebooks # list current notebooks
``` ```

View file

@ -1 +1 @@
__version__ = "0.2.0" __version__ = "0.2.1"

View file

@ -58,7 +58,7 @@ class EditCommand(BaseCommand):
temp_path = Path(tf.name) temp_path = Path(tf.name)
editor = os.environ.get("EDITOR", "vim") editor = os.environ.get("EDITOR", "vim")
subprocess.call([editor, temp_path]) subprocess.call([editor, temp_path]) # noqa: S603
with temp_path.open("rb") as f: with temp_path.open("rb") as f:
new_contents = f.read() new_contents = f.read()

View file

@ -1,6 +1,6 @@
from pyrage import decrypt as rage_decrypt from pyrage import decrypt as rage_decrypt # pyright: ignore [reportGeneralTypeIssues]
from pyrage import encrypt as rage_encrypt from pyrage import encrypt as rage_encrypt # pyright: ignore [reportGeneralTypeIssues]
from pyrage import ssh, x25519 from pyrage import ssh, x25519 # pyright: ignore [reportGeneralTypeIssues]
from halig.settings import Settings from halig.settings import Settings
@ -14,27 +14,25 @@ class Encryptor:
def __init__(self, settings: Settings): def __init__(self, settings: Settings):
self.settings = settings self.settings = settings
self.identities = []
self.recipients = []
# load identity for key in settings.load_private_keys():
with settings.identity_path.open("r") as f: if key.startswith("-----BEGIN OPENSSH PRIVATE KEY-----"):
identity_contents = f.read() self.identities.append(ssh.Identity.from_buffer(key.encode()))
if identity_contents.startswith("-----BEGIN OPENSSH PRIVATE KEY-----"): else:
self.identity = ssh.Identity.from_buffer(identity_contents.encode()) self.identities.append(x25519.Identity.from_str(key))
else:
self.identity = x25519.Identity.from_str(identity_contents)
# load recipient for key in settings.load_public_keys():
with settings.recipient_path.open("r") as f: if key.startswith("ssh-ed25519"):
recipient_contents = f.read() self.recipients.append(ssh.Recipient.from_str(key))
if recipient_contents.startswith("ssh-ed25519"): else:
self.recipient = ssh.Recipient.from_str(recipient_contents) self.recipients.append(x25519.Recipient.from_str(key))
else:
self.recipient = x25519.Recipient.from_str(recipient_contents)
def encrypt(self, data: str | bytes) -> bytes: def encrypt(self, data: str | bytes) -> bytes:
if isinstance(data, str): if isinstance(data, str):
data = data.encode() data = data.encode()
return rage_encrypt(data, [self.recipient]) # type: ignore[no-any-return] return rage_encrypt(data, self.recipients) # type: ignore[no-any-return]
def decrypt(self, data: bytes) -> bytes: def decrypt(self, data: bytes) -> bytes:
return rage_decrypt(data, [self.identity]) # type: ignore[no-any-return] return rage_decrypt(data, self.identities) # type: ignore[no-any-return]

View file

@ -2,8 +2,9 @@ import os
from functools import lru_cache from functools import lru_cache
from pathlib import Path from pathlib import Path
import httpx
import yaml import yaml
from pydantic import BaseSettings, DirectoryPath, FilePath from pydantic import BaseSettings, DirectoryPath, FilePath, HttpUrl, validator
class Settings(BaseSettings): class Settings(BaseSettings):
@ -14,15 +15,53 @@ class Settings(BaseSettings):
Attributes: Attributes:
notebooks_root_path (DirectoryPath): a *valid* path to a directory that notebooks_root_path (DirectoryPath): a *valid* path to a directory that
may contain notes or other notebooks may contain notes or other notebooks
identity_path (FilePath): a *valid* path to an identity file, which usually identity_paths (list[FilePath]): a list of *valid* paths of private keys.
is understood as a private key. Defaults to `~/.ssh/id_ed25519` Defaults to `[~/.ssh/id_ed25519]`
recipient_path (FilePath): a *valid* path to a recipient file, which usually recipient_paths (list[FilePath|HttpUrl]): a list *valid* paths of public keys,
is understood as a public key. Defaults to `~/.ssh/id_ed25519.pub` which usually is understood as a public key. Defaults to
`[~/.ssh/id_ed25519.pub]`
""" """
notebooks_root_path: DirectoryPath notebooks_root_path: DirectoryPath
identity_path: FilePath = Path("~/.ssh/id_ed25519").expanduser() identity_paths: list[FilePath] = [Path("~/.ssh/id_ed25519").expanduser()]
recipient_path: FilePath = Path("~/.ssh/id_ed25519.pub").expanduser() recipient_paths: list[FilePath | HttpUrl] = [
Path("~/.ssh/id_ed25519.pub").expanduser(),
]
@validator("identity_paths", "recipient_paths", pre=True)
def validate_paths(cls, v: list[str]): # noqa: N805
new_v = []
for path in v:
if isinstance(path, str):
if not path.startswith("http"):
new_v.append(Path(path).expanduser())
elif isinstance(path, Path):
new_v.append(path.expanduser())
else:
new_v.append(path)
return v
def load_private_keys(self) -> set[str]:
keys = set()
for path in self.identity_paths:
with path.open("r") as f:
keys.add(f.read())
return keys
def load_public_keys(self) -> set[str]:
keys = set()
for path in self.recipient_paths:
if isinstance(path, HttpUrl):
response = httpx.get(str(path))
if response.status_code == httpx.codes.OK:
for line in response.content.decode().split("\n"):
if line:
keys.add(line)
elif isinstance(path, Path):
with path.open("r") as f:
keys.add(f.read())
return keys
class Config: class Config:
env_prefix = "halig_" env_prefix = "halig_"

View file

@ -24,7 +24,7 @@ def capture(fn: Callable):
print(f"[red]{exc}") print(f"[red]{exc}")
sys.exit(1) sys.exit(1)
except Exception as exc: # noqa: BLE001 except Exception as exc: # noqa: BLE001
print(f"[bold red] Unexpected error: {exc}") print(f"[beld red] Unexpected error: {exc}")
sys.exit(2) sys.exit(2)
return wrapper return wrapper

147
pdm.lock generated
View file

@ -1,6 +1,16 @@
# This file is @generated by PDM. # This file is @generated by PDM.
# It is not intended for manual editing. # It is not intended for manual editing.
[[package]]
name = "anyio"
version = "3.6.2"
requires_python = ">=3.6.2"
summary = "High level compatibility layer for multiple asynchronous event loop implementations"
dependencies = [
"idna>=2.8",
"sniffio>=1.1",
]
[[package]] [[package]]
name = "black" name = "black"
version = "23.3.0" version = "23.3.0"
@ -87,6 +97,36 @@ dependencies = [
"colorama>=0.4", "colorama>=0.4",
] ]
[[package]]
name = "h11"
version = "0.14.0"
requires_python = ">=3.7"
summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
[[package]]
name = "httpcore"
version = "0.17.0"
requires_python = ">=3.7"
summary = "A minimal low-level HTTP client."
dependencies = [
"anyio<5.0,>=3.0",
"certifi",
"h11<0.15,>=0.13",
"sniffio==1.*",
]
[[package]]
name = "httpx"
version = "0.24.0"
requires_python = ">=3.7"
summary = "The next generation HTTP client."
dependencies = [
"certifi",
"httpcore<0.18.0,>=0.15.0",
"idna",
"sniffio",
]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.4" version = "3.4"
@ -171,7 +211,7 @@ dependencies = [
[[package]] [[package]]
name = "mkdocs-material" name = "mkdocs-material"
version = "9.1.7" version = "9.1.11"
requires_python = ">=3.7" requires_python = ">=3.7"
summary = "Documentation that simply works" summary = "Documentation that simply works"
dependencies = [ dependencies = [
@ -342,7 +382,7 @@ summary = "Python bindings for rage (age in Rust)"
[[package]] [[package]]
name = "pyright" name = "pyright"
version = "1.1.304" version = "1.1.306"
requires_python = ">=3.7" requires_python = ">=3.7"
summary = "Command line wrapper for pyright" summary = "Command line wrapper for pyright"
dependencies = [ dependencies = [
@ -416,7 +456,7 @@ dependencies = [
[[package]] [[package]]
name = "pytest-reportlog" name = "pytest-reportlog"
version = "0.2.1" version = "0.3.0"
requires_python = ">=3.7" requires_python = ">=3.7"
summary = "Replacement for the --resultlog option, focused in simplicity and extensibility" summary = "Replacement for the --resultlog option, focused in simplicity and extensibility"
dependencies = [ dependencies = [
@ -473,7 +513,7 @@ dependencies = [
[[package]] [[package]]
name = "rich" name = "rich"
version = "13.3.4" version = "13.3.5"
requires_python = ">=3.7.0" requires_python = ">=3.7.0"
summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
dependencies = [ dependencies = [
@ -483,7 +523,7 @@ dependencies = [
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.0.262" version = "0.0.265"
requires_python = ">=3.7" requires_python = ">=3.7"
summary = "An extremely fast Python linter, written in Rust." summary = "An extremely fast Python linter, written in Rust."
@ -499,6 +539,12 @@ version = "1.16.0"
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
summary = "Python 2 and 3 compatibility utilities" summary = "Python 2 and 3 compatibility utilities"
[[package]]
name = "sniffio"
version = "1.3.0"
requires_python = ">=3.7"
summary = "Sniff out which async library your code is running under"
[[package]] [[package]]
name = "tomli" name = "tomli"
version = "2.0.1" version = "2.0.1"
@ -507,11 +553,12 @@ summary = "A lil' TOML parser"
[[package]] [[package]]
name = "typer" name = "typer"
version = "0.7.0" version = "0.9.0"
requires_python = ">=3.6" requires_python = ">=3.6"
summary = "Typer, build great CLIs. Easy to code. Based on Python type hints." summary = "Typer, build great CLIs. Easy to code. Based on Python type hints."
dependencies = [ dependencies = [
"click<9.0.0,>=7.1.1", "click<9.0.0,>=7.1.1",
"typing-extensions>=3.7.4.3",
] ]
[[package]] [[package]]
@ -540,9 +587,13 @@ summary = "Filesystem events monitoring"
[metadata] [metadata]
lock_version = "4.2" lock_version = "4.2"
groups = ["default", "docs", "linting", "testing"] groups = ["default", "docs", "linting", "testing"]
content_hash = "sha256:65bedbc6ab345a1fc132bbdf4698aafb129f6cf522a4c219eec17d25092287d1" content_hash = "sha256:07ceae75e97c0c1a4e995e1e9c638fb6c30697ec83f437e2e14df6a7ad4441c2"
[metadata.files] [metadata.files]
"anyio 3.6.2" = [
{url = "https://files.pythonhosted.org/packages/77/2b/b4c0b7a3f3d61adb1a1e0b78f90a94e2b6162a043880704b7437ef297cad/anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"},
{url = "https://files.pythonhosted.org/packages/8b/94/6928d4345f2bc1beecbff03325cad43d320717f51ab74ab5a571324f4f5a/anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"},
]
"black 23.3.0" = [ "black 23.3.0" = [
{url = "https://files.pythonhosted.org/packages/06/1e/273d610249f0335afb1ddb03664a03223f4826e3d1a95170a0142cb19fb4/black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, {url = "https://files.pythonhosted.org/packages/06/1e/273d610249f0335afb1ddb03664a03223f4826e3d1a95170a0142cb19fb4/black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"},
{url = "https://files.pythonhosted.org/packages/12/4b/99c71d1cf1353edd5aff2700b8960f92e9b805c9dab72639b67dbb449d3a/black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, {url = "https://files.pythonhosted.org/packages/12/4b/99c71d1cf1353edd5aff2700b8960f92e9b805c9dab72639b67dbb449d3a/black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"},
@ -728,6 +779,18 @@ content_hash = "sha256:65bedbc6ab345a1fc132bbdf4698aafb129f6cf522a4c219eec17d250
{url = "https://files.pythonhosted.org/packages/07/38/d1c37c3021c2f81c4734d96c41c9d263ed92071559c4f417e96647ad2da9/griffe-0.26.0.tar.gz", hash = "sha256:08675ffe8c17139e7769e950dd21f8e98a2e76205cbbd2911d5dec26d2cbf1be"}, {url = "https://files.pythonhosted.org/packages/07/38/d1c37c3021c2f81c4734d96c41c9d263ed92071559c4f417e96647ad2da9/griffe-0.26.0.tar.gz", hash = "sha256:08675ffe8c17139e7769e950dd21f8e98a2e76205cbbd2911d5dec26d2cbf1be"},
{url = "https://files.pythonhosted.org/packages/57/bf/a2e8f1e3b3139c84d2d488beacca56a6b6c2f70e7aefaadda053fe1e87de/griffe-0.26.0-py3-none-any.whl", hash = "sha256:38f3f6bbe834501cc199a07b7b7e0e2550aaf19a9d1ee27acf879027e47c9b9e"}, {url = "https://files.pythonhosted.org/packages/57/bf/a2e8f1e3b3139c84d2d488beacca56a6b6c2f70e7aefaadda053fe1e87de/griffe-0.26.0-py3-none-any.whl", hash = "sha256:38f3f6bbe834501cc199a07b7b7e0e2550aaf19a9d1ee27acf879027e47c9b9e"},
] ]
"h11 0.14.0" = [
{url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
"httpcore 0.17.0" = [
{url = "https://files.pythonhosted.org/packages/41/16/c809655d32fd93e688b9e2a1aaba1008118369d1eda00818f6f65eb509f8/httpcore-0.17.0.tar.gz", hash = "sha256:cc045a3241afbf60ce056202301b4d8b6af08845e3294055eb26b09913ef903c"},
{url = "https://files.pythonhosted.org/packages/6c/39/05ebe30333ec66bba849d3c25c85d759b94c43bb03b2222de051c50d4390/httpcore-0.17.0-py3-none-any.whl", hash = "sha256:0fdfea45e94f0c9fd96eab9286077f9ff788dd186635ae61b312693e4d943599"},
]
"httpx 0.24.0" = [
{url = "https://files.pythonhosted.org/packages/4e/c1/692013f1e6115a061a14f6c7d05947515a1eb7b85ef6e9bf0ffbf0e92738/httpx-0.24.0-py3-none-any.whl", hash = "sha256:447556b50c1921c351ea54b4fe79d91b724ed2b027462ab9a329465d147d5a4e"},
{url = "https://files.pythonhosted.org/packages/ae/23/f7beaf11a8b95fc173b8979c4bfd23ea7711c5ebd458d657d24a59df7e9f/httpx-0.24.0.tar.gz", hash = "sha256:507d676fc3e26110d41df7d35ebd8b3b8585052450f4097401c9be59d928c63e"},
]
"idna 3.4" = [ "idna 3.4" = [
{url = "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, {url = "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
{url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
@ -816,9 +879,9 @@ content_hash = "sha256:65bedbc6ab345a1fc132bbdf4698aafb129f6cf522a4c219eec17d250
{url = "https://files.pythonhosted.org/packages/3b/3f/9531888bc92bafb1bffddca5d9240a7bae9a479d465528883b61808ef9d6/mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, {url = "https://files.pythonhosted.org/packages/3b/3f/9531888bc92bafb1bffddca5d9240a7bae9a479d465528883b61808ef9d6/mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"},
{url = "https://files.pythonhosted.org/packages/fb/5c/6594400290df38f99bf8d9ef249387b56f4ad962667836266f6fe7da8597/mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, {url = "https://files.pythonhosted.org/packages/fb/5c/6594400290df38f99bf8d9ef249387b56f4ad962667836266f6fe7da8597/mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"},
] ]
"mkdocs-material 9.1.7" = [ "mkdocs-material 9.1.11" = [
{url = "https://files.pythonhosted.org/packages/1a/a7/096d75a6751184c068ea3eb4ecff9786b06f2bf01446dd71f7e65551fac8/mkdocs_material-9.1.7-py3-none-any.whl", hash = "sha256:4ab0e2f378bb811ca44a001a63d687403a2725f0485c41d6a2a7aed3aa34cd31"}, {url = "https://files.pythonhosted.org/packages/36/2d/e42f392cc7dd582da579da75180b0ad8f7d27155a5803ba1f79861b97c5d/mkdocs_material-9.1.11.tar.gz", hash = "sha256:f5d473eb79d6640a5e668d4b2ab5b9de5e76ae0a0e2d864112df0cfe9016dc1d"},
{url = "https://files.pythonhosted.org/packages/fe/58/befd75349c7f417a7cc17af1a93ea1ecbad22d166612e8ab816f0c744d96/mkdocs_material-9.1.7.tar.gz", hash = "sha256:244f85d13e3aa773aaa66c74b95bb4b00d89a3a917f6f9d33704a5655bf0c423"}, {url = "https://files.pythonhosted.org/packages/cf/73/18d407acaaa03bdc46027b6696bbdeef42858b3f45708937ec8e3441e5b8/mkdocs_material-9.1.11-py3-none-any.whl", hash = "sha256:fbc86d50ec2cf34d40d5c4365780f290ceedde23f1a0704323b34e7f16b0c0dd"},
] ]
"mkdocs-material-extensions 1.1.1" = [ "mkdocs-material-extensions 1.1.1" = [
{url = "https://files.pythonhosted.org/packages/cd/3f/e5e3c9bfbb42e4cb661f71bcec787ae6bdf4a161b8c4bb68fd7d991c436c/mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, {url = "https://files.pythonhosted.org/packages/cd/3f/e5e3c9bfbb42e4cb661f71bcec787ae6bdf4a161b8c4bb68fd7d991c436c/mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"},
@ -973,9 +1036,9 @@ content_hash = "sha256:65bedbc6ab345a1fc132bbdf4698aafb129f6cf522a4c219eec17d250
{url = "https://files.pythonhosted.org/packages/d9/a4/785af8296a882132b63de046c54d9a45c8d1d634e22c5d40463accd8eefd/pyrage-1.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c554d7669539c0a7a870464faf55f0121c117339b1b0f57d6ca5eb85079c107"}, {url = "https://files.pythonhosted.org/packages/d9/a4/785af8296a882132b63de046c54d9a45c8d1d634e22c5d40463accd8eefd/pyrage-1.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c554d7669539c0a7a870464faf55f0121c117339b1b0f57d6ca5eb85079c107"},
{url = "https://files.pythonhosted.org/packages/fb/9c/54a5c8aad8442ba26845c325c8b4eaa90cc3b470d0e57465d82ca54ad843/pyrage-1.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:32b63a06f138a1c231285c61f61a1800b7b894851b06cb763b7b35b18ccd2b11"}, {url = "https://files.pythonhosted.org/packages/fb/9c/54a5c8aad8442ba26845c325c8b4eaa90cc3b470d0e57465d82ca54ad843/pyrage-1.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:32b63a06f138a1c231285c61f61a1800b7b894851b06cb763b7b35b18ccd2b11"},
] ]
"pyright 1.1.304" = [ "pyright 1.1.306" = [
{url = "https://files.pythonhosted.org/packages/40/dc/1cb8b90aae25a597fd5c17f29b49e3eadc751b353706234ba026a22793a1/pyright-1.1.304.tar.gz", hash = "sha256:87adec38081904c939e3657ab23d5fc40b7ccc22709be0af1859fc785ae4ea61"}, {url = "https://files.pythonhosted.org/packages/91/0a/8e5bfa6079f8240da3bcb02780583f808e431e3e79627c2748b96919ee11/pyright-1.1.306-py3-none-any.whl", hash = "sha256:008eb2a29584ae274a154d749cf81476a3073fb562a462eac8d43a753378b9db"},
{url = "https://files.pythonhosted.org/packages/8d/68/84f64dff33655a42dec9097a33f45eed8fd389a348c5070f60c30a192891/pyright-1.1.304-py3-none-any.whl", hash = "sha256:70021bbae07fc28ed16e435f5efa65cd71e06a1888d9ca998798c283d4b3d010"}, {url = "https://files.pythonhosted.org/packages/9c/65/e12aee503b6093a22e1e01254a37bf5115bce8f62d67a6bd27fb81196c7f/pyright-1.1.306.tar.gz", hash = "sha256:16d5d198be64de497d5f9002000a271176c381e21b977ca5566cf779b643c9ed"},
] ]
"pytest 7.3.1" = [ "pytest 7.3.1" = [
{url = "https://files.pythonhosted.org/packages/1b/d1/72df649a705af1e3a09ffe14b0c7d3be1fd730da6b98beb4a2ed26b8a023/pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, {url = "https://files.pythonhosted.org/packages/1b/d1/72df649a705af1e3a09ffe14b0c7d3be1fd730da6b98beb4a2ed26b8a023/pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"},
@ -1000,9 +1063,9 @@ content_hash = "sha256:65bedbc6ab345a1fc132bbdf4698aafb129f6cf522a4c219eec17d250
{url = "https://files.pythonhosted.org/packages/a5/18/30ad0408295f3157f7a4913f0eaa51a0a377ebad0ffa51ff239e833c6c72/pytest_pretty-1.2.0.tar.gz", hash = "sha256:105a355f128e392860ad2c478ae173ff96d2f03044692f9818ff3d49205d3a60"}, {url = "https://files.pythonhosted.org/packages/a5/18/30ad0408295f3157f7a4913f0eaa51a0a377ebad0ffa51ff239e833c6c72/pytest_pretty-1.2.0.tar.gz", hash = "sha256:105a355f128e392860ad2c478ae173ff96d2f03044692f9818ff3d49205d3a60"},
{url = "https://files.pythonhosted.org/packages/bf/fe/d44d391312c1b8abee2af58ee70fabb1c00b6577ac4e0bdf25b70c1caffb/pytest_pretty-1.2.0-py3-none-any.whl", hash = "sha256:6f79122bf53864ae2951b6c9e94d7a06a87ef753476acd4588aeac018f062036"}, {url = "https://files.pythonhosted.org/packages/bf/fe/d44d391312c1b8abee2af58ee70fabb1c00b6577ac4e0bdf25b70c1caffb/pytest_pretty-1.2.0-py3-none-any.whl", hash = "sha256:6f79122bf53864ae2951b6c9e94d7a06a87ef753476acd4588aeac018f062036"},
] ]
"pytest-reportlog 0.2.1" = [ "pytest-reportlog 0.3.0" = [
{url = "https://files.pythonhosted.org/packages/41/9a/6898175d1079b888994f25388afe7ffa0704a36d5549dd0e1143b01bfba2/pytest-reportlog-0.2.1.tar.gz", hash = "sha256:df59f7f1fcd9a0388e39b30e5aa264a609e64953e116f3ea6eb3aab22e3658e6"}, {url = "https://files.pythonhosted.org/packages/58/20/8d62051ff42965ad21e641e3f69d6418732ff4e0d62fd1f1cb2a350c7bd9/pytest_reportlog-0.3.0-py3-none-any.whl", hash = "sha256:30561aee147b512fcde13cabbd609d275ff8db43e1a348fda5b43ec280b41dcf"},
{url = "https://files.pythonhosted.org/packages/69/d9/636a707bfb67df041dac191abdb5412db4a25167c057a9d5bdcc60f56669/pytest_reportlog-0.2.1-py3-none-any.whl", hash = "sha256:65ac38cb5af90470df3dde6c03a6dd88090913d16765ee54d135279b5579c113"}, {url = "https://files.pythonhosted.org/packages/79/33/2e1e5c5c64282be6f90843e6609942787d254131cc8f0cd1b7efe1ba6cb7/pytest-reportlog-0.3.0.tar.gz", hash = "sha256:7b1644039babcb9cbbce31f4d9407d7131b10b1fc49e1e7cbfa27c2918b95b1a"},
] ]
"python-dateutil 2.8.2" = [ "python-dateutil 2.8.2" = [
{url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, {url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
@ -1124,28 +1187,28 @@ content_hash = "sha256:65bedbc6ab345a1fc132bbdf4698aafb129f6cf522a4c219eec17d250
{url = "https://files.pythonhosted.org/packages/9d/ee/391076f5937f0a8cdf5e53b701ffc91753e87b07d66bae4a09aa671897bf/requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, {url = "https://files.pythonhosted.org/packages/9d/ee/391076f5937f0a8cdf5e53b701ffc91753e87b07d66bae4a09aa671897bf/requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"},
{url = "https://files.pythonhosted.org/packages/d2/f4/274d1dbe96b41cf4e0efb70cbced278ffd61b5c7bb70338b62af94ccb25b/requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, {url = "https://files.pythonhosted.org/packages/d2/f4/274d1dbe96b41cf4e0efb70cbced278ffd61b5c7bb70338b62af94ccb25b/requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"},
] ]
"rich 13.3.4" = [ "rich 13.3.5" = [
{url = "https://files.pythonhosted.org/packages/31/3b/2360352760b436f822258396e66ffb6d42585518a9cde2f93f142e64c5eb/rich-13.3.4.tar.gz", hash = "sha256:b5d573e13605423ec80bdd0cd5f8541f7844a0e71a13f74cf454ccb2f490708b"}, {url = "https://files.pythonhosted.org/packages/39/03/6de23bdd88f5ee7f8b03f94f6e88108f5d7ffe6d207e95cdb06d9aa4cd57/rich-13.3.5-py3-none-any.whl", hash = "sha256:69cdf53799e63f38b95b9bf9c875f8c90e78dd62b2f00c13a911c7a3b9fa4704"},
{url = "https://files.pythonhosted.org/packages/9d/1a/28117ae737aec7c004ed5067034a8949adab43730420b50312821f466c3f/rich-13.3.4-py3-none-any.whl", hash = "sha256:22b74cae0278fd5086ff44144d3813be1cedc9115bdfabbfefd86400cb88b20a"}, {url = "https://files.pythonhosted.org/packages/3d/0b/8dd34d20929c4b5e474db2e64426175469c2b7fea5ba71c6d4b3397a9729/rich-13.3.5.tar.gz", hash = "sha256:2d11b9b8dd03868f09b4fffadc84a6a8cda574e40dc90821bd845720ebb8e89c"},
] ]
"ruff 0.0.262" = [ "ruff 0.0.265" = [
{url = "https://files.pythonhosted.org/packages/00/0c/9f2741560bdf0f922b9d7714d942d46913433ea0b88f4b5c9944efa68dc1/ruff-0.0.262-py3-none-win_amd64.whl", hash = "sha256:973ac29193f718349cf5746b7d86dfeaf7d40e9651ed97790a9b9327305888b9"}, {url = "https://files.pythonhosted.org/packages/27/ee/674d3816cbffa55e4fad4446e2815c783ca0648180a905418edfd664bc50/ruff-0.0.265-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:30ddfe22de6ce4eb1260408f4480bbbce998f954dbf470228a21a9b2c45955e4"},
{url = "https://files.pythonhosted.org/packages/05/69/e5ba7d18ca8ad24f6a7fd2229f30ff52c7e1528929202194dca4f356aa50/ruff-0.0.262-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ca04348372efc59f6ee808d903d35e0d352cf2c78e487757cd48b65104b83e"}, {url = "https://files.pythonhosted.org/packages/40/8d/fbd44882b6ef61c8be8fa9ce6448bb99ff105816ac22e2f2116473809c6d/ruff-0.0.265-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1d5a8de2fbaf91ea5699451a06f4074e7a312accfa774ad9327cde3e4fda2081"},
{url = "https://files.pythonhosted.org/packages/06/01/8227365f0dfef05a14efea32a49019223ac4f1f29c87ad8743481b78da87/ruff-0.0.262-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cca35e2aeddff72bb4379a1dabc134e0c0d25ebc754a2cb733a1f8d4dbbb5e0"}, {url = "https://files.pythonhosted.org/packages/41/33/f9043ba8a20a667d888adc81768ebdaadaa0d2b760da61912682d62bbff8/ruff-0.0.265-py3-none-win_amd64.whl", hash = "sha256:f54facf286103006171a00ce20388d88ed1d6732db3b49c11feb9bf3d46f90e9"},
{url = "https://files.pythonhosted.org/packages/1d/dd/55dcc53ec4c650e0052916ee7788c9844edf98ed6da67d0807bcdab9aa66/ruff-0.0.262-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15bf5533ce169aebbafa00017987f673e879f60a625d932b464b8cdaf32a4fce"}, {url = "https://files.pythonhosted.org/packages/77/cc/8806e6aaf8dc0cd4f41444f5423c31d691b7acf0386665406823f9528e45/ruff-0.0.265-py3-none-win_arm64.whl", hash = "sha256:c78470656e33d32ddc54e8482b1b0fc6de58f1195586731e5ff1405d74421499"},
{url = "https://files.pythonhosted.org/packages/26/d9/e7b66a2050bfda62f4e731d129a5223ffa4021ba8adf9d841b9dc8eca79e/ruff-0.0.262-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:c26c1abd420d041592d05d63aee8c6a18feb24aed4deb6e91129e9f2c7b4914a"}, {url = "https://files.pythonhosted.org/packages/94/67/f875768b8ecfbc6264a9fc5e10735359f23b2ac98194bf0512555225b987/ruff-0.0.265-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5028950f7af9b119d43d91b215d5044976e43b96a0d1458d193ef0dd3c587bf8"},
{url = "https://files.pythonhosted.org/packages/27/81/a01281da437ab6d59d28f6077de9ca16feb708da3a694ea75e98de85e822/ruff-0.0.262-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0baff3c9a22227358ea109c165efe62dbdd0f2b9fd5256567dda8682b444fe23"}, {url = "https://files.pythonhosted.org/packages/9d/07/7178c1b251c2bfed0676e7f8cd76d6107a5e89b2d1b7b464c084b311dfe6/ruff-0.0.265-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b279fa55ea175ef953208a6d8bfbcdcffac1c39b38cdb8c2bfafe9222add70bb"},
{url = "https://files.pythonhosted.org/packages/2e/70/dca9a145a60def1f7891933b42c547ebf5d591e7a0679162fa013df08379/ruff-0.0.262-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:b379e9765afa679316e52288a942df085e590862f8945088936a7bce3116d8f3"}, {url = "https://files.pythonhosted.org/packages/9e/35/fb24000483a56339ffe3c00c8b0d12c4ca99201f7f2f30f4799202a20a83/ruff-0.0.265-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8b44a245b60512403a6a03a5b5212da274d33862225c5eed3bcf12037eb19bb"},
{url = "https://files.pythonhosted.org/packages/50/94/d550ce79dc02684121fcd4728ce8db89837a370ae980a92245dc1fd23883/ruff-0.0.262-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b7e0ca6821aafbd2b059df3119fcd5881250721ca8e825789fd2c471f7c59be"}, {url = "https://files.pythonhosted.org/packages/a1/00/135639453182b6c64c507f448764d78f8df90bcd19e4b0e16427407709e0/ruff-0.0.265-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9ac13b11d9ad3001de9d637974ec5402a67cefdf9fffc3929ab44c2fcbb850a1"},
{url = "https://files.pythonhosted.org/packages/64/32/425fd707ccb3584199a943885f50d0738083722f2a7c80ce7a529c70b554/ruff-0.0.262-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:083bac6e238d8b7d5ac3618666ea63b7ac661cf94c5da160070a58e190082831"}, {url = "https://files.pythonhosted.org/packages/b2/bc/b14c0de95721ef6bd3a1dcfe7b4ce9f368c72a2d5aed7c98e767771c860c/ruff-0.0.265-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d0f9967f84da42d28e3d9d9354cc1575f96ed69e6e40a7d4b780a7a0418d9409"},
{url = "https://files.pythonhosted.org/packages/68/e3/1eb5c08c68380068d6827ae73cd08f23827adae27e4d34b8c328424a0795/ruff-0.0.262-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3909e249d984c4517194005a1c30eaa0c3a6d906c789d9fc0c9c7e007fb3e759"}, {url = "https://files.pythonhosted.org/packages/b3/1d/0e544e4b73bafbe88f6c39bce5306fdbf5af07e9d004fe023eeedf9ee548/ruff-0.0.265-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d586e69ab5cbf521a1910b733412a5735936f6a610d805b89d35b6647e2a66aa"},
{url = "https://files.pythonhosted.org/packages/6a/83/0e55b57dd9773f39c3bcad5a9ed87e62a248c721bc1a084fcde32d466bad/ruff-0.0.262-py3-none-win32.whl", hash = "sha256:15bbfa2d15c137717627e0d56b0e535ae297b734551e34e03fcc25d7642cf43a"}, {url = "https://files.pythonhosted.org/packages/b4/c4/7b8b05279a11ba30cf99eb628dbfd2fbf7219c192eb78f9585280bc3ae69/ruff-0.0.265-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:62a9578b48cfd292c64ea3d28681dc16b1aa7445b7a7709a2884510fc0822118"},
{url = "https://files.pythonhosted.org/packages/7c/f9/2200fe67292c7925edd2d219172265ba7a4aabca45dca6946c34f4e24325/ruff-0.0.262-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:24f989363e9bb5d0283490298102a5218682e49ebf300e445d69e24bee03ac83"}, {url = "https://files.pythonhosted.org/packages/c7/87/9b9d14b1778edb4ce03e0e80ab9378e12e402dce2b5cd27403b6670b6ed6/ruff-0.0.265-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4057eb539a1d88eb84e9f6a36e0a999e0f261ed850ae5d5817e68968e7b89ed9"},
{url = "https://files.pythonhosted.org/packages/a2/fd/0f5de09c7fd1fd30778270d961596d6ab843b2cacac6c7a04327b8714621/ruff-0.0.262.tar.gz", hash = "sha256:faea54231c265f5349975ba6f3d855b71881a01f391b2000c47740390c6d5f68"}, {url = "https://files.pythonhosted.org/packages/d1/c3/2aeec2adaff3cfa95d713283089568ee905f759e18d034646e3aa85a6282/ruff-0.0.265.tar.gz", hash = "sha256:53c17f0dab19ddc22b254b087d1381b601b155acfa8feed514f0d6a413d0ab3a"},
{url = "https://files.pythonhosted.org/packages/ae/b0/570c2ed3a82a2e5021b6aaa8a4810cac9d8322ae68bc6cf90c17fb339043/ruff-0.0.262-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e2813013a19b3e147e840bdb2e42db5825b53b47364e58e7b467c5fa47ffda2"}, {url = "https://files.pythonhosted.org/packages/e3/d1/433db20e8ef8f67ff7cfa32fdf6b9adb0592a1269fd93e230fd0dfbf64f2/ruff-0.0.265-py3-none-win32.whl", hash = "sha256:9e9db5ccb810742d621f93272e3cc23b5f277d8d00c4a79668835d26ccbe48dd"},
{url = "https://files.pythonhosted.org/packages/b2/b0/77527a1b55f02a36d5feed0b0888acbfe797932faa0831504e652bea63ce/ruff-0.0.262-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3c24e678e43ca4b67e29cc9a7a54eea05f31a5898cbf17bfec47b68f08d32a60"}, {url = "https://files.pythonhosted.org/packages/e7/c3/157c421412809780430110b766f8bdad65b55f0671c00c778d827dbb6f35/ruff-0.0.265-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a9b38bdb40a998cbc677db55b6225a6c4fadcf8819eb30695e1b8470942426b"},
{url = "https://files.pythonhosted.org/packages/b3/7e/f088f37524c0dee67d453d4eeb8d46dc439c70ae966d365b6eedb45b7113/ruff-0.0.262-py3-none-win_arm64.whl", hash = "sha256:f102904ebe395acd2a181d295b98120acd7a63f732b691672977fc688674f4af"}, {url = "https://files.pythonhosted.org/packages/f4/1c/5161404f7231c37e951f7dce3a4f623733a6563c17724189d003602ce55f/ruff-0.0.265-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa17b13cd3f29fc57d06bf34c31f21d043735cc9a681203d634549b0e41047d1"},
{url = "https://files.pythonhosted.org/packages/d0/ff/39d24f60765e294bb5d1d57622f7f39bf66da3ee748e02c1704e89150221/ruff-0.0.262-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d25a94996b2037e566c2a801c8b324c0a826194d5d4d90ad7c1ccb8cf06521fa"}, {url = "https://files.pythonhosted.org/packages/fd/ac/1c40dc44ed92744c3a9ca6f07ed2d83f0431d71a02766a7a478e2112fb59/ruff-0.0.265-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:a11bd0889e88d3342e7bc514554bb4461bf6cc30ec115821c2425cfaac0b1b6a"},
] ]
"setuptools 67.6.1" = [ "setuptools 67.6.1" = [
{url = "https://files.pythonhosted.org/packages/0b/fc/8781442def77b0aa22f63f266d4dadd486ebc0c5371d6290caf4320da4b7/setuptools-67.6.1-py3-none-any.whl", hash = "sha256:e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078"}, {url = "https://files.pythonhosted.org/packages/0b/fc/8781442def77b0aa22f63f266d4dadd486ebc0c5371d6290caf4320da4b7/setuptools-67.6.1-py3-none-any.whl", hash = "sha256:e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078"},
@ -1155,13 +1218,17 @@ content_hash = "sha256:65bedbc6ab345a1fc132bbdf4698aafb129f6cf522a4c219eec17d250
{url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, {url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
{url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
] ]
"sniffio 1.3.0" = [
{url = "https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
{url = "https://files.pythonhosted.org/packages/cd/50/d49c388cae4ec10e8109b1b833fd265511840706808576df3ada99ecb0ac/sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
]
"tomli 2.0.1" = [ "tomli 2.0.1" = [
{url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, {url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
] ]
"typer 0.7.0" = [ "typer 0.9.0" = [
{url = "https://files.pythonhosted.org/packages/0d/44/56c3f48d2bb83d76f5c970aef8e2c3ebd6a832f09e3621c5395371fe6999/typer-0.7.0-py3-none-any.whl", hash = "sha256:b5e704f4e48ec263de1c0b3a2387cd405a13767d2f907f44c1a08cbad96f606d"}, {url = "https://files.pythonhosted.org/packages/5b/49/39f10d0f75886439ab3dac889f14f8ad511982a754e382c9b6ca895b29e9/typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"},
{url = "https://files.pythonhosted.org/packages/e1/45/bcbc581f87c8d8f2a56b513eb994d07ea4546322818d95dc6a3caf2c928b/typer-0.7.0.tar.gz", hash = "sha256:ff797846578a9f2a201b53442aedeb543319466870fbe1c701eab66dd7681165"}, {url = "https://files.pythonhosted.org/packages/bf/0e/c68adf10adda05f28a6ed7b9f4cd7b8e07f641b44af88ba72d9c89e4de7a/typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"},
] ]
"types-pyyaml 6.0.12.9" = [ "types-pyyaml 6.0.12.9" = [
{url = "https://files.pythonhosted.org/packages/05/6f/f19081de5ba81864f89e354560a60637822f7d6d54d9a2a907785e9b5cc4/types-PyYAML-6.0.12.9.tar.gz", hash = "sha256:c51b1bd6d99ddf0aa2884a7a328810ebf70a4262c292195d3f4f9a0005f9eeb6"}, {url = "https://files.pythonhosted.org/packages/05/6f/f19081de5ba81864f89e354560a60637822f7d6d54d9a2a907785e9b5cc4/types-PyYAML-6.0.12.9.tar.gz", hash = "sha256:c51b1bd6d99ddf0aa2884a7a328810ebf70a4262c292195d3f4f9a0005f9eeb6"},

View file

@ -10,6 +10,7 @@ dependencies = [
"pyyaml>=6.0", "pyyaml>=6.0",
"pyrage>=1.0.3", "pyrage>=1.0.3",
"pendulum>=2.1.2", "pendulum>=2.1.2",
"httpx>=0.24.0",
] ]
name = "halig" name = "halig"
dynamic = ["version"] dynamic = ["version"]
@ -93,5 +94,6 @@ module = [
"pyrage", "pyrage",
"pyrage.ssh", "pyrage.ssh",
"pyrage.x25519", "pyrage.x25519",
"attr"
] ]
ignore_missing_imports = true ignore_missing_imports = true

View file

@ -5,29 +5,40 @@ from halig.settings import Settings
def test_instance_encryptor_from_age_keys(halig_path, notebooks_path): def test_instance_encryptor_from_age_keys(halig_path, notebooks_path):
identity = x25519.Identity.generate() identity_paths = []
identity_path = halig_path / "identity.key" recipient_paths = []
identity_path.touch() identities = []
recipient_path = halig_path / "recipient.key" for i in range(5):
recipient_path.touch() identity = x25519.Identity.generate()
with identity_path.open("w") as f: identities.append(identity)
f.write(str(identity)) identity_path = halig_path / f"identity_{i}.key"
identity_path.touch()
with identity_path.open("w") as f:
f.write(str(identity))
identity_paths.append(identity_path)
with recipient_path.open("w") as f: recipient_path = halig_path / f"recipient_{i}.key"
f.write(str(identity.to_public())) recipient_path.touch()
with recipient_path.open("w") as f:
f.write(str(identity.to_public()))
recipient_paths.append(recipient_path)
settings = Settings( settings = Settings(
notebooks_root_path=notebooks_path, notebooks_root_path=notebooks_path,
identity_path=identity_path, identity_paths=identity_paths,
recipient_path=recipient_path, recipient_paths=recipient_paths,
) )
assert Encryptor(settings) encryptor = Encryptor(settings)
for identity in identities:
assert str(identity) in [str(identity) for identity in encryptor.identities]
assert str(identity.to_public()) in [str(recipient) for recipient in encryptor.recipients]
def test_encrypt(encryptor: Encryptor, ssh_identity): def test_encrypt(encryptor: Encryptor, ssh_identity):
unencrypted_data = "foo" unencrypted_data = "foo"
encrypted_data = encryptor.encrypt(unencrypted_data) encrypted_data = encryptor.encrypt(unencrypted_data)
assert isinstance(encrypted_data, bytes) assert isinstance(encrypted_data, bytes)
assert unencrypted_data == decrypt(encrypted_data, [ssh_identity]).decode() assert unencrypted_data == decrypt(encrypted_data, [ssh_identity]).decode()