Compare commits
26 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 90f9873cad | |||
| e598e8f1ed | |||
| 6d371c13d7 | |||
| 654a123458 | |||
| 12b94065d6 | |||
| 4dad8eb1e7 | |||
| 7fb62ce201 | |||
| 34b552176a | |||
| 84558826fb | |||
| 97cf1a8f5c | |||
| 54ab31b853 | |||
| 27312ecfaf | |||
| c81715ad1f | |||
| 595f93cb61 | |||
| 3e84b1dfed | |||
| 76f80c5032 | |||
| 9a50b78a16 | |||
| df869b0828 | |||
| 03666c3334 | |||
| a86ddd262a | |||
| c4fb34629a | |||
| 6e66da4045 | |||
| ee31311db7 | |||
| 780dd5a624 | |||
| 400792922b | |||
| 816bc54213 |
29 changed files with 1250 additions and 963 deletions
|
|
@ -1,9 +0,0 @@
|
|||
[run]
|
||||
source = ./transip_client/
|
||||
omit =
|
||||
**/tests/**
|
||||
**/tests.py
|
||||
**/__init__.py
|
||||
|
||||
[html]
|
||||
directory = coverage
|
||||
1
.dockerignore
Normal file
1
.dockerignore
Normal file
|
|
@ -0,0 +1 @@
|
|||
tests
|
||||
25
.editorconfig
Normal file
25
.editorconfig
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# https://editorconfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{yaml,yml,toml,md}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[Dockerfile*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.json]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
stages:
|
||||
- build
|
||||
- test
|
||||
- release
|
||||
|
||||
variables:
|
||||
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
|
||||
|
||||
cache:
|
||||
key: "$CI_COMMIT_REF_SLUG"
|
||||
paths:
|
||||
- .venv/
|
||||
- .cache/pip
|
||||
- .cache/poetry
|
||||
|
||||
development-build:
|
||||
stage: build
|
||||
image: python:3.11
|
||||
script:
|
||||
- make install-development
|
||||
- transip-listen --help
|
||||
only:
|
||||
- development
|
||||
|
||||
production-build:
|
||||
stage: build
|
||||
image: python:3.11
|
||||
script:
|
||||
- make install-production
|
||||
- transip-listen --help
|
||||
only:
|
||||
- main
|
||||
- tags
|
||||
|
||||
tests:
|
||||
stage: test
|
||||
coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/'
|
||||
image: python:3.11
|
||||
before_script:
|
||||
- pip install -r requirements/ci.txt
|
||||
script:
|
||||
- coverage run --module unittest
|
||||
- coverage report
|
||||
|
||||
release:
|
||||
stage: release
|
||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
script:
|
||||
- echo 'running release job'
|
||||
release:
|
||||
name: 'Release $CI_COMMIT_TAG'
|
||||
description: './CHANGELOG.md'
|
||||
tag_name: '$CI_COMMIT_TAG'
|
||||
ref: '$CI_COMMIT_TAG'
|
||||
10
.isort.cfg
10
.isort.cfg
|
|
@ -1,10 +0,0 @@
|
|||
[settings]
|
||||
include_trailing_comma = true
|
||||
line_length = 88
|
||||
multi_line_output = 3
|
||||
skip = env/, venv/
|
||||
default_section = THIRDPARTY
|
||||
known_first_party = transip_client
|
||||
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
|
||||
lines_between_types=1
|
||||
lines_after_imports=2
|
||||
12
.woodpecker/publish.yaml
Normal file
12
.woodpecker/publish.yaml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
when:
|
||||
- event: tag
|
||||
|
||||
steps:
|
||||
- name: publish package
|
||||
image: ghcr.io/astral-sh/uv:python3.11-alpine
|
||||
commands:
|
||||
- uv build
|
||||
- uv publish --index forgejo
|
||||
environment:
|
||||
UV_PUBLISH_TOKEN:
|
||||
from_secret: publish_token
|
||||
18
.woodpecker/tests.yaml
Normal file
18
.woodpecker/tests.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
when:
|
||||
- event: push
|
||||
- event: manual
|
||||
|
||||
steps:
|
||||
- name: python tests
|
||||
image: ghcr.io/astral-sh/uv:python3.11-alpine
|
||||
commands:
|
||||
- uv sync --group ci
|
||||
- uv run -- coverage run --module unittest
|
||||
- uv run -- coverage report
|
||||
|
||||
- name: code formatting & linting
|
||||
image: ghcr.io/astral-sh/uv:python3.11-alpine
|
||||
commands:
|
||||
- uv sync
|
||||
- uv run -- ruff format --diff
|
||||
- uv run -- ruff check --diff
|
||||
19
CHANGELOG.md
19
CHANGELOG.md
|
|
@ -1,15 +1,30 @@
|
|||
# Changelog
|
||||
|
||||
# 0.7.0
|
||||
|
||||
- Added different classes responsible for determining host IP
|
||||
- Changed cli usage from `transip-client` to `transip-update`
|
||||
- Added Dockerfile
|
||||
- Added editorconfig configuration file
|
||||
- Added README
|
||||
|
||||
# 0.6.0
|
||||
|
||||
- Replaced dns query usage with calling an external API
|
||||
- Replaced gitlab CI with Woodpecker configuration
|
||||
- Refactored optional dependencies & implemented dependency groups
|
||||
- Moved to uv.lock file for pinning dependencies
|
||||
|
||||
# 0.5.1
|
||||
|
||||
- Update gitlab CI configuration
|
||||
|
||||
# 0.5.0
|
||||
|
||||
- Remove poetry
|
||||
- Removed poetry
|
||||
- Use `setuptools` for packaging
|
||||
- Added a `Makefile`
|
||||
- Use `pip-compile` for dependecy management
|
||||
- Use `pip-compile` for dependency management
|
||||
|
||||
# 0.4.0
|
||||
|
||||
|
|
|
|||
43
Dockerfile
Normal file
43
Dockerfile
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
FROM python:3.11-alpine
|
||||
|
||||
ARG LOGGING_CONFIG_SRC="./transip_client/logging/default.yml"
|
||||
ARG LOGGING_CONFIG_DST="./transip_client/logging/config.yml"
|
||||
ARG UV_ARGS=""
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:python3.11-alpine \
|
||||
/usr/local/bin/uv \
|
||||
/usr/local/bin/uvx \
|
||||
/bin/
|
||||
|
||||
# provides the dig package for the DNSAdapter
|
||||
RUN apk add --no-cache bind-tools
|
||||
|
||||
# copy from the cache instead of linking since mounted volumes are used
|
||||
ENV UV_LINK_MODE=copy
|
||||
ENV UV_CACHE_DIR=/app/.cache/uv
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
ENV LOGGING_CONFIG=$LOGGING_CONFIG_DST
|
||||
|
||||
RUN adduser -DHu 1000 transip_client
|
||||
|
||||
USER transip_client
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN mkdir --parents /app/.cache/uv
|
||||
|
||||
# only install dependencies
|
||||
RUN --mount=type=cache,uid=1000,target=/app/.cache/uv \
|
||||
--mount=type=bind,source=uv.lock,target=/app/uv.lock \
|
||||
--mount=type=bind,source=pyproject.toml,target=/app/pyproject.toml \
|
||||
uv sync --frozen --no-dev --no-install-project
|
||||
|
||||
COPY pyproject.toml uv.lock /app
|
||||
COPY transip_client /app/transip_client
|
||||
COPY $LOGGING_CONFIG_SRC $LOGGING_CONFIG_DST
|
||||
|
||||
# install dependencies + project
|
||||
RUN --mount=type=cache,uid=1000,target=/app/.cache/uv \
|
||||
uv sync --frozen --no-dev $UV_ARGS
|
||||
|
||||
ENTRYPOINT ["/bin/uv", "run", "--no-sync", "--", "transip-update"]
|
||||
36
Makefile
36
Makefile
|
|
@ -1,36 +0,0 @@
|
|||
# Note: run this file from within your virtualenv!
|
||||
#
|
||||
#
|
||||
# Build dependencies
|
||||
build:
|
||||
pip-compile --output-file=requirements/base.txt pyproject.toml
|
||||
|
||||
# development
|
||||
pip-compile \
|
||||
--extra=development \
|
||||
--output-file=requirements/development.txt \
|
||||
requirements/base.txt \
|
||||
pyproject.toml
|
||||
|
||||
# ci
|
||||
pip-compile \
|
||||
--extra=development \
|
||||
--extra=ci \
|
||||
--output-file=requirements/ci.txt \
|
||||
requirements/development.txt \
|
||||
pyproject.toml
|
||||
|
||||
# production
|
||||
pip-compile \
|
||||
--extra=sentry-enabled \
|
||||
--output-file=requirements/production.txt \
|
||||
requirements/base.txt \
|
||||
pyproject.toml
|
||||
|
||||
install-development:
|
||||
python -m pip install -r requirements/development.txt
|
||||
python -m pip install --no-dependencies -e .
|
||||
|
||||
install-production:
|
||||
python -m pip install -r requirements/production.txt
|
||||
python -m pip install --no-dependencies .
|
||||
53
README.md
Normal file
53
README.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# Transip client
|
||||
|
||||
A simple command line client for updating DNS records with the Transip API. It does
|
||||
so by determining the current hosts (external) IP address and accordingly updates
|
||||
the records DNS for the given domain names.
|
||||
|
||||
## Installation
|
||||
|
||||
Installation can be done through using uv:
|
||||
|
||||
```
|
||||
$ uv sync --frozen --no-dev
|
||||
```
|
||||
|
||||
Or through the provided Dockerfile:
|
||||
|
||||
```
|
||||
$ docker image build --tag transip-client:0.7.0 .
|
||||
```
|
||||
|
||||
Optional dependencies can be installed with:
|
||||
|
||||
```
|
||||
$ uv sync --frozen --no-dev --extra sentry-enabled
|
||||
```
|
||||
|
||||
For docker installations optional dependencies can be installed with:
|
||||
|
||||
```
|
||||
$ docker image build \
|
||||
--build-arg UV_ARGS="--extra sentry-enabled" \
|
||||
--tag transip-client:0.7.0 .
|
||||
```
|
||||
|
||||
## Usage
|
||||
Use the help option to show all available options:
|
||||
|
||||
```
|
||||
transip-update --help
|
||||
```
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
The client can be configured with the following environment variables:
|
||||
|
||||
`LOGGING_CONFIG`: Specifies the path for the [logging configuration](https://docs.python.org/3.11/library/logging.html) to be used. Note that both `LOGGING_CONFIG_SRC` and `LOGGING_CONFIG_DST` can be used when building the docker image to achieve similar results.
|
||||
|
||||
`VERSION`: Application version. The client will try to retrieve this through git if it is not set.
|
||||
|
||||
`SENTRY_DSN`: Optionally used for specifying Sentry's DSN
|
||||
|
||||
`ENVIRONMENT`: Optionally used for specifying the environment in Sentry
|
||||
|
|
@ -1,34 +1,65 @@
|
|||
[project]
|
||||
name = 'transip_client'
|
||||
version = '0.5.0'
|
||||
description = 'Listens for changes about the current public IP and acts upon it.'
|
||||
authors = [{name = 'Sonny', email= 'sonnyba871@gmail.com'}]
|
||||
license = {text = 'GPL-3.0'}
|
||||
requires-python = '>=3.11'
|
||||
name = "transip_client"
|
||||
version = "0.7.0"
|
||||
description = "Listens for changes about the current public IP and acts upon it."
|
||||
authors = [{name = "Sonny"}]
|
||||
license = {text = "GPL-3.0"}
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
'click>=8.0.1',
|
||||
'python-dotenv>=0.15.0',
|
||||
'requests>=2.25.1',
|
||||
'cryptography>=3.4.7'
|
||||
"click>=8.0.1",
|
||||
"python-dotenv>=0.15.0",
|
||||
"requests>=2.25.1",
|
||||
"cryptography>=3.4.7",
|
||||
"ruamel-yaml>=0.18.10",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ruff>=0.8.6"]
|
||||
ci = ["coverage>=5.3.1"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
development = [
|
||||
'black>=20.8b1',
|
||||
'isort>=5.6.4',
|
||||
'autoflake>=1.4',
|
||||
'pip-tools>=6.13.0',
|
||||
]
|
||||
|
||||
ci = ['coverage>=5.3.1']
|
||||
sentry-enabled = ['sentry_sdk>=0.19.5']
|
||||
sentry-enabled = ["sentry_sdk>=0.19.5"]
|
||||
|
||||
[tool.setuptools.packages]
|
||||
find = {include = ['transip_client']}
|
||||
find = {include = ["transip_client"]}
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "forgejo"
|
||||
url = "https://forgejo.fudiggity.nl/sonny/transip-client/packages"
|
||||
publish-url = "https://forgejo.fudiggity.nl/api/packages/sonny/pypi"
|
||||
explicit = true
|
||||
|
||||
[project.scripts]
|
||||
transip-listen = 'transip_client.cli:run'
|
||||
transip-update = "transip_client.cli:update"
|
||||
|
||||
[build-system]
|
||||
requires = ['setuptools']
|
||||
build-backend = 'setuptools.build_meta'
|
||||
requires = ["setuptools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.uv]
|
||||
environments = ["sys_platform == 'linux'"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E4", "E7", "E9", "F", "I"]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
lines-between-types=1
|
||||
lines-after-imports=2
|
||||
|
||||
default-section = "third-party"
|
||||
known-first-party = ["transip_client"]
|
||||
section-order = [
|
||||
"future",
|
||||
"standard-library",
|
||||
"third-party",
|
||||
"first-party",
|
||||
"local-folder",
|
||||
]
|
||||
|
||||
[tool.coverage.run]
|
||||
source = ["./transip_client/"]
|
||||
omit = [
|
||||
"**/tests/**",
|
||||
"**/tests.py",
|
||||
"**/__init__.py"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.11
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --output-file=requirements/base.txt pyproject.toml
|
||||
#
|
||||
certifi==2023.5.7
|
||||
# via requests
|
||||
cffi==1.15.1
|
||||
# via cryptography
|
||||
charset-normalizer==3.1.0
|
||||
# via requests
|
||||
click==8.1.3
|
||||
# via transip-client (pyproject.toml)
|
||||
cryptography==41.0.1
|
||||
# via transip-client (pyproject.toml)
|
||||
idna==3.4
|
||||
# via requests
|
||||
pycparser==2.21
|
||||
# via cffi
|
||||
python-dotenv==1.0.0
|
||||
# via transip-client (pyproject.toml)
|
||||
requests==2.31.0
|
||||
# via transip-client (pyproject.toml)
|
||||
urllib3==2.0.3
|
||||
# via requests
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.11
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --extra=ci --extra=development --output-file=requirements/ci.txt pyproject.toml requirements/development.txt
|
||||
#
|
||||
autoflake==2.2.0
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# transip-client (pyproject.toml)
|
||||
black==23.3.0
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# transip-client (pyproject.toml)
|
||||
build==0.10.0
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# pip-tools
|
||||
certifi==2023.5.7
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# requests
|
||||
cffi==1.15.1
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# cryptography
|
||||
charset-normalizer==3.1.0
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# requests
|
||||
click==8.1.3
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# black
|
||||
# pip-tools
|
||||
# transip-client (pyproject.toml)
|
||||
coverage==7.2.7
|
||||
# via transip-client (pyproject.toml)
|
||||
cryptography==41.0.1
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# transip-client (pyproject.toml)
|
||||
idna==3.4
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# requests
|
||||
isort==5.12.0
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# transip-client (pyproject.toml)
|
||||
mypy-extensions==1.0.0
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# black
|
||||
packaging==23.1
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# black
|
||||
# build
|
||||
pathspec==0.11.1
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# black
|
||||
pip-tools==6.13.0
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# transip-client (pyproject.toml)
|
||||
platformdirs==3.8.0
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# black
|
||||
pycparser==2.21
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# cffi
|
||||
pyflakes==3.0.1
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# autoflake
|
||||
pyproject-hooks==1.0.0
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# build
|
||||
python-dotenv==1.0.0
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# transip-client (pyproject.toml)
|
||||
requests==2.31.0
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# transip-client (pyproject.toml)
|
||||
urllib3==2.0.3
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# requests
|
||||
wheel==0.40.0
|
||||
# via
|
||||
# -r requirements/development.txt
|
||||
# pip-tools
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
# pip
|
||||
# setuptools
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.11
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --extra=development --output-file=requirements/development.txt pyproject.toml requirements/base.txt
|
||||
#
|
||||
autoflake==2.2.0
|
||||
# via transip-client (pyproject.toml)
|
||||
black==23.3.0
|
||||
# via transip-client (pyproject.toml)
|
||||
build==0.10.0
|
||||
# via pip-tools
|
||||
certifi==2023.5.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# requests
|
||||
cffi==1.15.1
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# cryptography
|
||||
charset-normalizer==3.1.0
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# requests
|
||||
click==8.1.3
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# black
|
||||
# pip-tools
|
||||
# transip-client (pyproject.toml)
|
||||
cryptography==41.0.1
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# transip-client (pyproject.toml)
|
||||
idna==3.4
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# requests
|
||||
isort==5.12.0
|
||||
# via transip-client (pyproject.toml)
|
||||
mypy-extensions==1.0.0
|
||||
# via black
|
||||
packaging==23.1
|
||||
# via
|
||||
# black
|
||||
# build
|
||||
pathspec==0.11.1
|
||||
# via black
|
||||
pip-tools==6.13.0
|
||||
# via transip-client (pyproject.toml)
|
||||
platformdirs==3.8.0
|
||||
# via black
|
||||
pycparser==2.21
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# cffi
|
||||
pyflakes==3.0.1
|
||||
# via autoflake
|
||||
pyproject-hooks==1.0.0
|
||||
# via build
|
||||
python-dotenv==1.0.0
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# transip-client (pyproject.toml)
|
||||
requests==2.31.0
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# transip-client (pyproject.toml)
|
||||
urllib3==2.0.3
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# requests
|
||||
wheel==0.40.0
|
||||
# via pip-tools
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
# pip
|
||||
# setuptools
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.11
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --extra=sentry-enabled --output-file=requirements/production.txt pyproject.toml requirements/base.txt
|
||||
#
|
||||
certifi==2023.5.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# requests
|
||||
# sentry-sdk
|
||||
cffi==1.15.1
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# cryptography
|
||||
charset-normalizer==3.1.0
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# requests
|
||||
click==8.1.3
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# transip-client (pyproject.toml)
|
||||
cryptography==41.0.1
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# transip-client (pyproject.toml)
|
||||
idna==3.4
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# requests
|
||||
pycparser==2.21
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# cffi
|
||||
python-dotenv==1.0.0
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# transip-client (pyproject.toml)
|
||||
requests==2.31.0
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# transip-client (pyproject.toml)
|
||||
sentry-sdk==1.26.0
|
||||
# via transip-client (pyproject.toml)
|
||||
urllib3==2.0.3
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# requests
|
||||
# sentry-sdk
|
||||
|
|
@ -1,24 +1,32 @@
|
|||
import os
|
||||
import subprocess
|
||||
|
||||
from logging.config import dictConfig
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
|
||||
yml = YAML(typ="safe", pure=True)
|
||||
env_path = Path("..") / ".env"
|
||||
|
||||
load_dotenv(dotenv_path=env_path)
|
||||
|
||||
default_config_path = Path(__file__).resolve().parent / "logging" / "default.yml"
|
||||
logging_config_path = os.environ.get("LOGGING_CONFIG", default_config_path)
|
||||
|
||||
with open(logging_config_path, "r") as f:
|
||||
logging_config = yml.load(f.read())
|
||||
dictConfig(logging_config)
|
||||
|
||||
|
||||
def get_current_version():
|
||||
if "VERSION" in os.environ:
|
||||
return os.environ["VERSION"]
|
||||
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
["git", "describe", "--tags"], universal_newlines=True
|
||||
)
|
||||
output = subprocess.check_output(["git", "describe", "--tags"], text=True)
|
||||
return output.strip()
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
return ""
|
||||
|
|
@ -32,6 +40,7 @@ try:
|
|||
|
||||
sentry_init(
|
||||
dsn=os.environ.get("SENTRY_DSN"),
|
||||
environment=os.environ.get("ENVIRONMENT", "production"),
|
||||
send_default_pii=False,
|
||||
release=VERSION,
|
||||
)
|
||||
|
|
|
|||
86
transip_client/adapters.py
Normal file
86
transip_client/adapters.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import logging
|
||||
import subprocess
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Adapter:
|
||||
def get_ip(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class HTTPAdapter(Adapter):
|
||||
service: str
|
||||
|
||||
def handle_response(self, response: requests.Response) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_params(self) -> dict:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_ip(self) -> str:
|
||||
try:
|
||||
response = requests.get(self.service, params=self.get_params(), timeout=5)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
raise OSError(f"Unable to retrieve current IP from {self.service}") from e
|
||||
|
||||
ip = self.handle_response(response)
|
||||
|
||||
if not ip:
|
||||
raise OSError(f"Unable to determine IP from response from {self.service}")
|
||||
|
||||
return ip
|
||||
|
||||
|
||||
class IpifyAdapter(HTTPAdapter):
|
||||
service: str = "https://api.ipify.org"
|
||||
|
||||
def get_params(self) -> dict:
|
||||
return {"format": "text"}
|
||||
|
||||
def handle_response(self, response: requests.Response) -> str:
|
||||
return response.text
|
||||
|
||||
|
||||
class DNSAdapter(Adapter):
|
||||
resolvers: list[str]
|
||||
dns: str
|
||||
|
||||
def try_resolver(self, resolver: str) -> str:
|
||||
output = subprocess.check_output(
|
||||
["dig", "+short", self.dns, resolver], stderr=subprocess.STDOUT, text=True
|
||||
)
|
||||
|
||||
return output.strip()
|
||||
|
||||
def get_ip(self) -> str:
|
||||
for resolver in self.resolvers:
|
||||
try:
|
||||
ip = self.try_resolver(resolver)
|
||||
except subprocess.CalledProcessError as e:
|
||||
if e.returncode == 9:
|
||||
continue
|
||||
|
||||
raise OSError("Unable to retrieve current IP") from e
|
||||
|
||||
if not ip:
|
||||
logger.warning(f"No IP returned from {resolver}")
|
||||
continue
|
||||
|
||||
return ip
|
||||
|
||||
raise OSError("Exhausted all known IP resolvers, unable to retrieve IP")
|
||||
|
||||
|
||||
class OpenDNSAdapter(DNSAdapter):
|
||||
dns = "myip.opendns.com"
|
||||
resolvers = [
|
||||
"@resolver1.opendns.com",
|
||||
"@resolver2.opendns.com",
|
||||
"@resolver3.opendns.com",
|
||||
"@resolver4.opendns.com",
|
||||
]
|
||||
|
|
@ -1,48 +1,40 @@
|
|||
from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
from transip_client.main import detect
|
||||
|
||||
|
||||
DEFAULT_DNS = "myip.opendns.com"
|
||||
DEFAULT_DNS_NAME = "@resolver1.opendns.com"
|
||||
DEFAULT_API_URL = "https://api.transip.nl/v6"
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("domains", envvar="DOMAINS", nargs=-1)
|
||||
@click.option("--token", envvar="TOKEN")
|
||||
@click.option("--login", envvar="LOGIN")
|
||||
@click.option("--private-key-path", envvar="PRIVATE_KEY_PATH")
|
||||
@click.option("--dns", envvar="DNS", default=DEFAULT_DNS)
|
||||
@click.option("--dns-name", envvar="DNS_NAME", default=DEFAULT_DNS_NAME)
|
||||
@click.option("--api-url", envvar="API_URL", default=DEFAULT_API_URL)
|
||||
@click.option("--read-only/--write", envvar="READ_ONLY", default=False)
|
||||
def run(domains, token, login, private_key_path, dns, dns_name, api_url, read_only):
|
||||
@click.argument("login")
|
||||
@click.argument("private-key-path")
|
||||
@click.argument("domains", nargs=-1)
|
||||
@click.option(
|
||||
"--adapter-class",
|
||||
default="transip_client.adapters.OpenDNSAdapter",
|
||||
)
|
||||
@click.option("--read-only/--write", default=False)
|
||||
def update(
|
||||
domains: list[str],
|
||||
login: str,
|
||||
private_key_path: str,
|
||||
adapter_class: str,
|
||||
read_only: bool,
|
||||
) -> None:
|
||||
if not domains:
|
||||
raise ValueError("No domain(s) specified")
|
||||
|
||||
token_retrieval = any(
|
||||
(
|
||||
login,
|
||||
private_key_path,
|
||||
)
|
||||
)
|
||||
if not Path(private_key_path).exists():
|
||||
raise ValueError(f"Unknown private key path: {private_key_path}")
|
||||
|
||||
if token_retrieval and not all((login, private_key_path)):
|
||||
if not all((login, private_key_path)):
|
||||
raise ValueError(
|
||||
"Both a login name and the path to a private key need to be specified"
|
||||
)
|
||||
elif not token_retrieval and not token:
|
||||
raise ValueError(
|
||||
"Either a token or a login name with a path to a private key need"
|
||||
" to be specified"
|
||||
)
|
||||
|
||||
detect(
|
||||
domains,
|
||||
(dns, dns_name),
|
||||
(private_key_path, login),
|
||||
token,
|
||||
api_url,
|
||||
adapter_class,
|
||||
read_only,
|
||||
)
|
||||
|
|
|
|||
14
transip_client/logging/default.yml
Normal file
14
transip_client/logging/default.yml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
version: 1
|
||||
formatters:
|
||||
simple:
|
||||
format: '%(asctime)s %(levelname)s %(name)s %(message)s'
|
||||
datefmt: '%Y-%m-%d %H:%M:%S'
|
||||
handlers:
|
||||
console:
|
||||
level: DEBUG
|
||||
formatter: simple
|
||||
class: logging.StreamHandler
|
||||
loggers:
|
||||
transip_client:
|
||||
handlers: [console]
|
||||
level: INFO
|
||||
14
transip_client/logging/development.yml
Normal file
14
transip_client/logging/development.yml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
version: 1
|
||||
formatters:
|
||||
simple:
|
||||
format: '%(asctime)s %(levelname)s %(name)s %(message)s'
|
||||
datefmt: '%Y-%m-%d %H:%M:%S'
|
||||
handlers:
|
||||
console:
|
||||
level: DEBUG
|
||||
formatter: simple
|
||||
class: logging.StreamHandler
|
||||
loggers:
|
||||
transip_client:
|
||||
handlers: [console]
|
||||
level: DEBUG
|
||||
21
transip_client/logging/file.yml
Normal file
21
transip_client/logging/file.yml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
version: 1
|
||||
formatters:
|
||||
simple:
|
||||
format: '%(asctime)s %(levelname)s %(name)s %(message)s'
|
||||
datefmt: '%Y-%m-%d %H:%M:%S'
|
||||
handlers:
|
||||
console:
|
||||
level: DEBUG
|
||||
formatter: simple
|
||||
class: logging.StreamHandler
|
||||
file:
|
||||
level: INFO
|
||||
formatter: simple
|
||||
class: logging.handlers.RotatingFileHandler
|
||||
filename: /app/transip_client/logging/logs/app.log
|
||||
backupCount: 5
|
||||
maxBytes: 50000000 # 50 mB
|
||||
loggers:
|
||||
transip_client:
|
||||
handlers: [console, file]
|
||||
level: INFO
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import base64
|
||||
import json
|
||||
import logging
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from secrets import token_urlsafe
|
||||
from typing import Generator
|
||||
|
||||
import requests
|
||||
|
||||
|
|
@ -13,32 +13,25 @@ from cryptography.hazmat.primitives import serialization
|
|||
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
||||
from cryptography.hazmat.primitives.hashes import SHA512
|
||||
|
||||
from transip_client.utils import import_string
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_ip(resolvers):
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
["dig", "+short", *resolvers],
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise OSError("Unable to retrieve current IP") from e
|
||||
|
||||
return output.decode("utf-8").strip()
|
||||
API_URL = "https://api.transip.nl/v6"
|
||||
|
||||
|
||||
def _get_token(private_key_path, login, api_url):
|
||||
# Note that tokens cannot be recreated when an existing token is present
|
||||
def _get_token(private_key_path: str, login: str, api_url: str) -> str:
|
||||
request = requests.Request(
|
||||
"POST",
|
||||
f"{api_url}/auth",
|
||||
json={
|
||||
"login": login,
|
||||
"nonce": str(int(time.time() * 1000)),
|
||||
"nonce": token_urlsafe(),
|
||||
"read_only": False,
|
||||
"expiration_time": "30 minutes",
|
||||
"label": "Custom token",
|
||||
"label": "Trans IP client",
|
||||
"global_key": True,
|
||||
},
|
||||
)
|
||||
|
|
@ -55,6 +48,8 @@ def _get_token(private_key_path, login, api_url):
|
|||
|
||||
prepped_request.headers["Signature"] = signature.decode("ascii")
|
||||
|
||||
logger.info(f"Retrieving token from {api_url} for {login}")
|
||||
|
||||
with requests.Session() as session:
|
||||
response = session.send(prepped_request)
|
||||
|
||||
|
|
@ -66,13 +61,16 @@ def _get_token(private_key_path, login, api_url):
|
|||
return response_data["token"]
|
||||
|
||||
|
||||
def _get_domain(domain, token, api_url):
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
def _get_domain(domain: str, token: str, api_url: str) -> requests.Response:
|
||||
logger.info(f"Retrieving domain information for {domain} from {api_url}")
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
return requests.get(f"{api_url}/domains/{domain}/dns", headers=headers)
|
||||
|
||||
|
||||
def _get_domain_data(domains, token, api_url):
|
||||
def _get_domain_data(
|
||||
domains: list[str], token: str, api_url: str
|
||||
) -> Generator[dict, None, None]:
|
||||
with ThreadPoolExecutor(max_workers=10) as executor:
|
||||
futures = {
|
||||
executor.submit(_get_domain, domain, token, api_url): domain
|
||||
|
|
@ -80,27 +78,32 @@ def _get_domain_data(domains, token, api_url):
|
|||
}
|
||||
|
||||
for future in as_completed(futures):
|
||||
response = future.result()
|
||||
domain = futures[future]
|
||||
|
||||
try:
|
||||
response = future.result()
|
||||
response.raise_for_status()
|
||||
except requests.HTTPError as e:
|
||||
except requests.HTTPError:
|
||||
logger.exception(f"Failed retrieving information for {domain}")
|
||||
continue
|
||||
|
||||
yield {"domain": domain, **response.json()}
|
||||
|
||||
|
||||
def _update_domain(domain, payload, api_url, token):
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
def _update_domain(
|
||||
domain: str, payload: dict, api_url: str, token: str
|
||||
) -> requests.Response:
|
||||
logger.info(f"Updating domain {domain} at {api_url}")
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
return requests.put(
|
||||
f"{api_url}/domains/{domain}/dns", data=json.dumps(payload), headers=headers
|
||||
)
|
||||
|
||||
|
||||
def _update_domains(updated_domains, api_url, token, read_only):
|
||||
def _update_domains(
|
||||
updated_domains: dict, api_url: str, token: str, read_only: bool
|
||||
) -> None:
|
||||
if read_only:
|
||||
return
|
||||
|
||||
|
|
@ -120,18 +123,24 @@ def _update_domains(updated_domains, api_url, token, read_only):
|
|||
logger.exception(f"Unable to update domain {domain}")
|
||||
continue
|
||||
|
||||
logger.info(f"Updated domain {domain}")
|
||||
logger.info(f"Updated domain {domain} at {api_url}")
|
||||
|
||||
|
||||
def detect(domains, resolvers, credentials, token, api_url, read_only):
|
||||
ip = _get_ip(resolvers)
|
||||
def detect(
|
||||
domains: list[str],
|
||||
credentials: tuple[str, str],
|
||||
adapter_class: str,
|
||||
read_only: bool,
|
||||
) -> None:
|
||||
_adapter_class = import_string(adapter_class)
|
||||
adapter = _adapter_class()
|
||||
ip = adapter.get_ip()
|
||||
|
||||
token = _get_token(*credentials, API_URL)
|
||||
|
||||
domain_data = _get_domain_data(domains, token, API_URL)
|
||||
updated_domains = {}
|
||||
|
||||
if all(credentials):
|
||||
token = _get_token(*credentials, api_url)
|
||||
|
||||
domain_data = _get_domain_data(domains, token, api_url)
|
||||
|
||||
for data in domain_data:
|
||||
dns_entries = data["dnsEntries"]
|
||||
domain = data["domain"]
|
||||
|
|
@ -146,8 +155,9 @@ def detect(domains, resolvers, credentials, token, api_url, read_only):
|
|||
)
|
||||
|
||||
if dns_entries == updated_entries:
|
||||
logger.info(f"No changes detected for {domain}, skipping...")
|
||||
continue
|
||||
|
||||
updated_domains[domain] = {"dnsEntries": updated_entries}
|
||||
|
||||
_update_domains(updated_domains, api_url, token, read_only)
|
||||
_update_domains(updated_domains, API_URL, token, read_only)
|
||||
|
|
|
|||
129
transip_client/tests/test_adapters.py
Normal file
129
transip_client/tests/test_adapters.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
from subprocess import CalledProcessError
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from requests.exceptions import Timeout
|
||||
from requests.models import HTTPError
|
||||
|
||||
from transip_client.adapters import IpifyAdapter, OpenDNSAdapter
|
||||
|
||||
|
||||
class IpifyAdapterTestCase(TestCase):
|
||||
adapter_class = IpifyAdapter
|
||||
|
||||
def setUp(self) -> None:
|
||||
patcher = patch("transip_client.adapters.requests.get")
|
||||
|
||||
self.mocked_requests_get = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_simple(self):
|
||||
self.mocked_requests_get.return_value = MagicMock(text="8.8.8.8")
|
||||
|
||||
adapter = self.adapter_class()
|
||||
|
||||
self.assertEqual(adapter.get_ip(), "8.8.8.8")
|
||||
|
||||
def test_service_unavailable(self):
|
||||
self.mocked_requests_get.side_effect = Timeout
|
||||
|
||||
adapter = self.adapter_class()
|
||||
|
||||
with self.assertRaises(OSError) as exception_manager:
|
||||
adapter.get_ip()
|
||||
|
||||
exception = exception_manager.exception
|
||||
|
||||
self.assertEqual(
|
||||
str(exception), f"Unable to retrieve current IP from {adapter.service}"
|
||||
)
|
||||
|
||||
def test_error_response(self):
|
||||
request_mock = MagicMock()
|
||||
request_mock.raise_for_status.side_effect = HTTPError
|
||||
self.mocked_requests_get.return_value = request_mock
|
||||
|
||||
adapter = self.adapter_class()
|
||||
|
||||
with self.assertRaises(OSError) as exception_manager:
|
||||
adapter.get_ip()
|
||||
|
||||
exception = exception_manager.exception
|
||||
|
||||
self.assertEqual(
|
||||
str(exception), f"Unable to retrieve current IP from {adapter.service}"
|
||||
)
|
||||
|
||||
def test_no_text_content(self):
|
||||
self.mocked_requests_get.return_value = MagicMock(text="")
|
||||
|
||||
adapter = self.adapter_class()
|
||||
|
||||
with self.assertRaises(OSError) as exception_manager:
|
||||
adapter.get_ip()
|
||||
|
||||
exception = exception_manager.exception
|
||||
|
||||
self.assertEqual(
|
||||
str(exception),
|
||||
f"Unable to determine IP from response from {adapter.service}",
|
||||
)
|
||||
|
||||
|
||||
class OpenDNSAdapterTestCase(TestCase):
|
||||
def setUp(self) -> None:
|
||||
patcher = patch("transip_client.adapters.subprocess.check_output")
|
||||
|
||||
self.mocked_check_output = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_simple(self):
|
||||
self.mocked_check_output.return_value = "8.8.8.8\n"
|
||||
|
||||
adapter = OpenDNSAdapter()
|
||||
|
||||
self.assertEqual(adapter.get_ip(), "8.8.8.8")
|
||||
|
||||
def test_retry_next_resolvers(self):
|
||||
self.mocked_check_output.side_effect = (
|
||||
CalledProcessError(9, "dig +short myip.opendns.com @resolver1.opendns.com"),
|
||||
CalledProcessError(9, "dig +short myip.opendns.com @resolver2.opendns.com"),
|
||||
CalledProcessError(9, "dig +short myip.opendns.com @resolver3.opendns.com"),
|
||||
"9.9.9.9\n",
|
||||
)
|
||||
|
||||
adapter = OpenDNSAdapter()
|
||||
|
||||
self.assertEqual(adapter.get_ip(), "9.9.9.9")
|
||||
|
||||
def test_resolvers_exhausted(self):
|
||||
self.mocked_check_output.side_effect = (
|
||||
CalledProcessError(9, "dig +short myip.opendns.com @resolver1.opendns.com"),
|
||||
CalledProcessError(9, "dig +short myip.opendns.com @resolver2.opendns.com"),
|
||||
CalledProcessError(9, "dig +short myip.opendns.com @resolver3.opendns.com"),
|
||||
CalledProcessError(9, "dig +short myip.opendns.com @resolver4.opendns.com"),
|
||||
)
|
||||
|
||||
adapter = OpenDNSAdapter()
|
||||
|
||||
with self.assertRaises(OSError) as exception_manager:
|
||||
adapter.get_ip()
|
||||
|
||||
self.assertEqual(
|
||||
str(exception_manager.exception),
|
||||
"Exhausted all known IP resolvers, unable to retrieve IP",
|
||||
)
|
||||
|
||||
def test_command_error(self):
|
||||
self.mocked_check_output.side_effect = (
|
||||
CalledProcessError(1, "dig +short myip.opendns.com @resolver1.opendns.com"),
|
||||
)
|
||||
|
||||
adapter = OpenDNSAdapter()
|
||||
|
||||
with self.assertRaises(OSError) as exception_manager:
|
||||
adapter.get_ip()
|
||||
|
||||
self.assertEqual(
|
||||
str(exception_manager.exception), "Unable to retrieve current IP"
|
||||
)
|
||||
359
transip_client/tests/test_cli.py
Normal file
359
transip_client/tests/test_cli.py
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
import json
|
||||
|
||||
from pathlib import Path
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, call, patch
|
||||
|
||||
from click.testing import CliRunner
|
||||
from requests import HTTPError
|
||||
|
||||
from transip_client.cli import update
|
||||
from transip_client.main import API_URL
|
||||
|
||||
|
||||
class UpdateTestCase(TestCase):
|
||||
def setUp(self):
|
||||
patcher = patch("transip_client.main.requests.get")
|
||||
self.mocked_get = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
patcher = patch("transip_client.main.requests.put")
|
||||
self.mocked_put = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
patcher = patch("transip_client.main.requests.Session.send")
|
||||
self.mocked_session = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
patcher = patch("transip_client.main.import_string")
|
||||
self.mocked_import_string = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
self.runner = CliRunner()
|
||||
|
||||
self.private_key_path = (
|
||||
Path(__file__).resolve().parent / "files" / "test-private-key.pem"
|
||||
)
|
||||
|
||||
def test_simple(self):
|
||||
mocked_adapter = MagicMock(get_ip=lambda: "111.420")
|
||||
self.mocked_import_string.return_value = MagicMock(return_value=mocked_adapter)
|
||||
|
||||
self.mocked_get.side_effect = [
|
||||
MagicMock(
|
||||
json=lambda: {
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.421",
|
||||
}
|
||||
],
|
||||
"_links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com/dns",
|
||||
},
|
||||
{
|
||||
"rel": "domain",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com",
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
]
|
||||
|
||||
self.mocked_session.return_value.json.return_value = {"token": "token"}
|
||||
|
||||
result = self.runner.invoke(
|
||||
update,
|
||||
["my-user", str(self.private_key_path), "foobar.com"],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
|
||||
self.mocked_get.assert_called_with(
|
||||
f"{API_URL}/domains/foobar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
)
|
||||
|
||||
expected_json = json.dumps(
|
||||
{
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.420",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
self.mocked_put.assert_called_with(
|
||||
f"{API_URL}/domains/foobar.com/dns",
|
||||
data=expected_json,
|
||||
headers={"Authorization": "Bearer token"},
|
||||
)
|
||||
|
||||
def test_error_response(self):
|
||||
mocked_adapter = MagicMock(get_ip=lambda: "111.420")
|
||||
self.mocked_import_string.return_value = MagicMock(return_value=mocked_adapter)
|
||||
|
||||
self.mocked_session.return_value.json.return_value = {"token": "token"}
|
||||
self.mocked_get.side_effect = [HTTPError]
|
||||
|
||||
result = self.runner.invoke(
|
||||
update,
|
||||
["my-user", str(self.private_key_path), "foobar.com"],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
|
||||
self.mocked_get.assert_called_with(
|
||||
f"{API_URL}/domains/foobar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
)
|
||||
|
||||
self.mocked_put.assert_not_called()
|
||||
|
||||
def test_update_error_response(self):
|
||||
mocked_adapter = MagicMock(get_ip=lambda: "111.420")
|
||||
self.mocked_import_string.return_value = MagicMock(return_value=mocked_adapter)
|
||||
|
||||
self.mocked_session.return_value.json.return_value = {"token": "token"}
|
||||
|
||||
self.mocked_get.side_effect = [
|
||||
MagicMock(
|
||||
json=lambda: {
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.421",
|
||||
}
|
||||
],
|
||||
"_links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com/dns",
|
||||
},
|
||||
{
|
||||
"rel": "domain",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com",
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
MagicMock(
|
||||
json=lambda: {
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.421",
|
||||
}
|
||||
],
|
||||
"_links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"link": "https://api.transip.nl/v6/domains/boofar.com/dns",
|
||||
},
|
||||
{
|
||||
"rel": "domain",
|
||||
"link": "https://api.transip.nl/v6/domains/boofar.com",
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
]
|
||||
|
||||
def raise_exception() -> None:
|
||||
raise HTTPError
|
||||
|
||||
self.mocked_put.side_effect = (
|
||||
MagicMock(raise_for_status=raise_exception),
|
||||
MagicMock(status=200),
|
||||
)
|
||||
|
||||
result = self.runner.invoke(
|
||||
update,
|
||||
["my-user", str(self.private_key_path), "foobar.com", "boofar.com"],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
|
||||
self.mocked_get.assert_has_calls(
|
||||
(
|
||||
call(
|
||||
f"{API_URL}/domains/foobar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
),
|
||||
call(
|
||||
f"{API_URL}/domains/boofar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
self.assertCountEqual(
|
||||
self.mocked_put.mock_calls,
|
||||
(
|
||||
call(
|
||||
f"{API_URL}/domains/foobar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
data=json.dumps(
|
||||
{
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.420",
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
),
|
||||
call(
|
||||
f"{API_URL}/domains/boofar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
data=json.dumps(
|
||||
{
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.420",
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
def test_matching_ip(self):
|
||||
mocked_adapter = MagicMock(get_ip=lambda: "111.420")
|
||||
self.mocked_import_string.return_value = MagicMock(return_value=mocked_adapter)
|
||||
|
||||
self.mocked_get.side_effect = [
|
||||
MagicMock(
|
||||
json=lambda: {
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.420",
|
||||
}
|
||||
],
|
||||
"_links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com/dns",
|
||||
},
|
||||
{
|
||||
"rel": "domain",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com",
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
]
|
||||
|
||||
self.mocked_session.return_value.json.return_value = {"token": "token"}
|
||||
|
||||
result = self.runner.invoke(
|
||||
update,
|
||||
["my-user", str(self.private_key_path), "foobar.com"],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
|
||||
self.mocked_get.assert_called_with(
|
||||
f"{API_URL}/domains/foobar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
)
|
||||
|
||||
self.mocked_put.assert_not_called()
|
||||
|
||||
def test_readonly(self):
|
||||
mocked_adapter = MagicMock(get_ip=lambda: "111.420")
|
||||
self.mocked_import_string.return_value = MagicMock(return_value=mocked_adapter)
|
||||
|
||||
self.mocked_get.side_effect = [
|
||||
MagicMock(
|
||||
json=lambda: {
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.421",
|
||||
}
|
||||
],
|
||||
"_links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com/dns",
|
||||
},
|
||||
{
|
||||
"rel": "domain",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com",
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
]
|
||||
|
||||
self.mocked_session.return_value.json.return_value = {"token": "token"}
|
||||
|
||||
result = self.runner.invoke(
|
||||
update,
|
||||
["my-user", str(self.private_key_path), "foobar.com", "--read-only"],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
|
||||
self.mocked_get.assert_called_with(
|
||||
f"{API_URL}/domains/foobar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
)
|
||||
|
||||
self.mocked_put.assert_not_called()
|
||||
|
||||
def test_no_domains(self):
|
||||
result = self.runner.invoke(
|
||||
update,
|
||||
["my-user", str(self.private_key_path)],
|
||||
catch_exceptions=True,
|
||||
)
|
||||
|
||||
self.assertEqual(result.exit_code, 1)
|
||||
self.assertEqual(str(result.exception), "No domain(s) specified")
|
||||
|
||||
self.mocked_get.assert_not_called()
|
||||
self.mocked_put.assert_not_called()
|
||||
|
||||
def test_unknown_private_key_path(self):
|
||||
result = self.runner.invoke(
|
||||
update,
|
||||
["my-user", str("/tmp/foo"), "foobar.com"],
|
||||
catch_exceptions=True,
|
||||
)
|
||||
|
||||
self.assertEqual(result.exit_code, 1)
|
||||
self.assertEqual(str(result.exception), "Unknown private key path: /tmp/foo")
|
||||
|
||||
self.mocked_get.assert_not_called()
|
||||
self.mocked_put.assert_not_called()
|
||||
|
|
@ -1,505 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from unittest import TestCase
|
||||
from unittest.mock import call, patch
|
||||
from pathlib import Path
|
||||
|
||||
from click.testing import CliRunner
|
||||
from requests import HTTPError
|
||||
|
||||
from transip_client.cli import DEFAULT_API_URL, run
|
||||
|
||||
|
||||
class RunTestCase(TestCase):
|
||||
def setUp(self):
|
||||
patcher = patch("transip_client.main.subprocess.check_output")
|
||||
self.mocked_dns = patcher.start()
|
||||
|
||||
patcher = patch("transip_client.main.requests.get")
|
||||
self.mocked_get = patcher.start()
|
||||
|
||||
patcher = patch("transip_client.main.requests.put")
|
||||
self.mocked_put = patcher.start()
|
||||
|
||||
patcher = patch("transip_client.main.requests.Session.send")
|
||||
self.mocked_session = patcher.start()
|
||||
|
||||
self.runner = CliRunner()
|
||||
|
||||
def test_simple(self):
|
||||
self.mocked_dns.return_value = b"111.420\n"
|
||||
self.mocked_get.return_value.json.return_value = {
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.421",
|
||||
}
|
||||
],
|
||||
"_links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com/dns",
|
||||
},
|
||||
{
|
||||
"rel": "domain",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
with self.assertLogs("transip_client.main", level="INFO") as logger:
|
||||
result = self.runner.invoke(run, ["foobar.com"], env={"TOKEN": "token"})
|
||||
|
||||
self.assertEqual(
|
||||
logger.output, ["INFO:transip_client.main:Updated domain foobar.com"]
|
||||
)
|
||||
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
|
||||
self.mocked_get.assert_called_with(
|
||||
f"{DEFAULT_API_URL}/domains/foobar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
)
|
||||
|
||||
expected_json = json.dumps(
|
||||
{
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.420",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
self.mocked_put.assert_called_with(
|
||||
f"{DEFAULT_API_URL}/domains/foobar.com/dns",
|
||||
data=expected_json,
|
||||
headers={"Authorization": "Bearer token"},
|
||||
)
|
||||
|
||||
def test_error_response(self):
|
||||
self.mocked_dns.return_value = b"111.420\n"
|
||||
self.mocked_get.return_value.raise_for_status.side_effect = HTTPError
|
||||
|
||||
with self.assertLogs("transip_client.main", level="INFO") as logger:
|
||||
result = self.runner.invoke(run, ["foobar.com"], env={"TOKEN": "token"})
|
||||
|
||||
error_log = logger.output[0]
|
||||
|
||||
self.assertIn("Failed retrieving information for foobar.com", error_log)
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
|
||||
self.mocked_get.assert_called_with(
|
||||
f"{DEFAULT_API_URL}/domains/foobar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
)
|
||||
|
||||
self.mocked_put.assert_not_called()
|
||||
|
||||
def test_matching_ip(self):
|
||||
self.mocked_dns.return_value = b"111.420\n"
|
||||
self.mocked_get.return_value.json.return_value = {
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.420",
|
||||
}
|
||||
],
|
||||
"_links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com/dns",
|
||||
},
|
||||
{
|
||||
"rel": "domain",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
result = self.runner.invoke(run, ["foobar.com"], env={"TOKEN": "token"})
|
||||
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
|
||||
self.mocked_get.assert_called_with(
|
||||
f"{DEFAULT_API_URL}/domains/foobar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
)
|
||||
|
||||
self.mocked_put.assert_not_called()
|
||||
|
||||
def test_readonly(self):
|
||||
self.mocked_dns.return_value = b"111.420\n"
|
||||
self.mocked_get.return_value.json.return_value = {
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.421",
|
||||
}
|
||||
],
|
||||
"_links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com/dns",
|
||||
},
|
||||
{
|
||||
"rel": "domain",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
result = self.runner.invoke(
|
||||
run, ["foobar.com", "--read-only"], env={"TOKEN": "token"}
|
||||
)
|
||||
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
|
||||
self.mocked_get.assert_called_with(
|
||||
f"{DEFAULT_API_URL}/domains/foobar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
)
|
||||
|
||||
self.mocked_put.assert_not_called()
|
||||
|
||||
def test_different_api_url(self):
|
||||
self.mocked_dns.return_value = b"111.420\n"
|
||||
self.mocked_get.return_value.json.return_value = {
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.421",
|
||||
}
|
||||
],
|
||||
"_links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com/dns",
|
||||
},
|
||||
{
|
||||
"rel": "domain",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
with self.assertLogs("transip_client.main", level="INFO") as logger:
|
||||
result = self.runner.invoke(
|
||||
run,
|
||||
["foobar.com", "--api-url", "https://other-provider.com"],
|
||||
env={"TOKEN": "token"},
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
logger.output, ["INFO:transip_client.main:Updated domain foobar.com"]
|
||||
)
|
||||
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
|
||||
self.mocked_get.assert_called_with(
|
||||
f"https://other-provider.com/domains/foobar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
)
|
||||
|
||||
expected_json = json.dumps(
|
||||
{
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.420",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
self.mocked_put.assert_called_with(
|
||||
f"https://other-provider.com/domains/foobar.com/dns",
|
||||
data=expected_json,
|
||||
headers={"Authorization": "Bearer token"},
|
||||
)
|
||||
|
||||
def test_env_var(self):
|
||||
self.mocked_dns.return_value = b"111.420\n"
|
||||
self.mocked_get.return_value.json.return_value = {
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.421",
|
||||
}
|
||||
],
|
||||
"_links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com/dns",
|
||||
},
|
||||
{
|
||||
"rel": "domain",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
with self.assertLogs("transip_client.main", level="INFO") as logger:
|
||||
result = self.runner.invoke(
|
||||
run,
|
||||
["foobar.com"],
|
||||
env={
|
||||
"TOKEN": "token",
|
||||
"API_URL": "https://new-api.com",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
logger.output, ["INFO:transip_client.main:Updated domain foobar.com"]
|
||||
)
|
||||
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
|
||||
self.mocked_get.assert_called_with(
|
||||
"https://new-api.com/domains/foobar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
)
|
||||
|
||||
expected_json = json.dumps(
|
||||
{
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.420",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
self.mocked_put.assert_called_with(
|
||||
"https://new-api.com/domains/foobar.com/dns",
|
||||
data=expected_json,
|
||||
headers={"Authorization": "Bearer token"},
|
||||
)
|
||||
|
||||
def test_multi_arg_env_var(self):
|
||||
self.mocked_dns.return_value = b"111.420\n"
|
||||
self.mocked_get.return_value.json.side_effect = [
|
||||
{
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.421",
|
||||
}
|
||||
],
|
||||
"_links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com/dns",
|
||||
},
|
||||
{
|
||||
"rel": "domain",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.421",
|
||||
}
|
||||
],
|
||||
"_links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"link": "https://api.transip.nl/v6/domains/foofoo.com/dns",
|
||||
},
|
||||
{
|
||||
"rel": "domain",
|
||||
"link": "https://api.transip.nl/v6/domains/foofoo.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
with self.assertLogs("transip_client.main", level="INFO") as logger:
|
||||
result = self.runner.invoke(
|
||||
run, [], env={"TOKEN": "token", "DOMAINS": "foobar.com foofoo.com"}
|
||||
)
|
||||
|
||||
self.assertIsNone(result.exception)
|
||||
self.assertEqual(
|
||||
logger.output,
|
||||
[
|
||||
"INFO:transip_client.main:Updated domain foobar.com",
|
||||
"INFO:transip_client.main:Updated domain foofoo.com",
|
||||
],
|
||||
)
|
||||
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
|
||||
expected_calls = [
|
||||
call(
|
||||
f"{DEFAULT_API_URL}/domains/foobar.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
),
|
||||
call().raise_for_status(),
|
||||
call().json(),
|
||||
call(
|
||||
f"{DEFAULT_API_URL}/domains/foofoo.com/dns",
|
||||
headers={"Authorization": "Bearer token"},
|
||||
),
|
||||
call().raise_for_status(),
|
||||
call().json(),
|
||||
]
|
||||
|
||||
# use any_order because of the asynchronous requests
|
||||
self.mocked_get.assert_has_calls(expected_calls, any_order=True)
|
||||
|
||||
expected_json = json.dumps(
|
||||
{
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.420",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
expected_calls = [
|
||||
call(
|
||||
f"{DEFAULT_API_URL}/domains/foobar.com/dns",
|
||||
data=expected_json,
|
||||
headers={"Authorization": "Bearer token"},
|
||||
),
|
||||
call().raise_for_status(),
|
||||
call(
|
||||
f"{DEFAULT_API_URL}/domains/foofoo.com/dns",
|
||||
data=expected_json,
|
||||
headers={"Authorization": "Bearer token"},
|
||||
),
|
||||
call().raise_for_status(),
|
||||
]
|
||||
|
||||
self.mocked_put.assert_has_calls(expected_calls, any_order=True)
|
||||
|
||||
def test_no_domains(self):
|
||||
result = self.runner.invoke(run, [], env={"TOKEN": "token"})
|
||||
|
||||
self.assertEqual(result.exit_code, 1)
|
||||
self.assertEqual(str(result.exception), "No domain(s) specified")
|
||||
|
||||
self.mocked_get.assert_not_called()
|
||||
self.mocked_put.assert_not_called()
|
||||
|
||||
def test_no_token_or_login_with_private_key_path(self):
|
||||
result = self.runner.invoke(run, ["foobar.com"])
|
||||
|
||||
self.assertEqual(result.exit_code, 1)
|
||||
self.assertEqual(
|
||||
str(result.exception),
|
||||
"Either a token or a login name with a path to a private key need"
|
||||
" to be specified"
|
||||
)
|
||||
|
||||
self.mocked_get.assert_not_called()
|
||||
self.mocked_put.assert_not_called()
|
||||
|
||||
def test_login_without_private_key_path(self):
|
||||
result = self.runner.invoke(run, ["foobar.com"], env={"LOGIN": "foo"})
|
||||
|
||||
self.assertEqual(result.exit_code, 1)
|
||||
self.assertEqual(
|
||||
str(result.exception),
|
||||
"Both a login name and the path to a private key need to be specified"
|
||||
)
|
||||
|
||||
self.mocked_get.assert_not_called()
|
||||
self.mocked_put.assert_not_called()
|
||||
|
||||
def test_login_with_private_key_path(self):
|
||||
self.mocked_dns.return_value = b"111.420\n"
|
||||
self.mocked_get.return_value.json.return_value = {
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.421",
|
||||
}
|
||||
],
|
||||
"_links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com/dns",
|
||||
},
|
||||
{
|
||||
"rel": "domain",
|
||||
"link": "https://api.transip.nl/v6/domains/foobar.com",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
self.mocked_session.return_value.json.return_value = {"token": "FOOBAR"}
|
||||
|
||||
private_key_path = (
|
||||
Path(os.path.dirname(__file__)) / "files" / "test-private-key.pem"
|
||||
)
|
||||
|
||||
with self.assertLogs("transip_client.main", level="INFO") as logger:
|
||||
result = self.runner.invoke(
|
||||
run,
|
||||
["foobar.com"],
|
||||
env={"LOGIN": "foo", "PRIVATE_KEY_PATH": str(private_key_path)}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
logger.output, ["INFO:transip_client.main:Updated domain foobar.com"]
|
||||
)
|
||||
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
|
||||
self.mocked_get.assert_called_with(
|
||||
f"{DEFAULT_API_URL}/domains/foobar.com/dns",
|
||||
headers={"Authorization": "Bearer FOOBAR"},
|
||||
)
|
||||
|
||||
expected_json = json.dumps(
|
||||
{
|
||||
"dnsEntries": [
|
||||
{
|
||||
"name": "@",
|
||||
"expire": 60,
|
||||
"type": "A",
|
||||
"content": "111.420",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
self.mocked_put.assert_called_with(
|
||||
f"{DEFAULT_API_URL}/domains/foobar.com/dns",
|
||||
data=expected_json,
|
||||
headers={"Authorization": "Bearer FOOBAR"},
|
||||
)
|
||||
10
transip_client/utils.py
Normal file
10
transip_client/utils.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from importlib import import_module
|
||||
from typing import Type
|
||||
|
||||
from transip_client.adapters import Adapter
|
||||
|
||||
|
||||
def import_string(path: str) -> Type[Adapter]:
|
||||
module_path, class_name = path.rsplit(".", 1)
|
||||
module = import_module(module_path)
|
||||
return getattr(module, class_name)
|
||||
318
uv.lock
generated
Normal file
318
uv.lock
generated
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.11"
|
||||
resolution-markers = [
|
||||
"sys_platform == 'linux'",
|
||||
]
|
||||
supported-markers = [
|
||||
"sys_platform == 'linux'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.12.14"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010, upload-time = "2024-12-14T13:52:38.02Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927, upload-time = "2024-12-14T13:52:36.114Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.17.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pycparser", marker = "sys_platform == 'linux'" },
|
||||
]
|
||||
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/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload-time = "2024-12-24T18:12:35.43Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471, upload-time = "2024-12-24T18:10:14.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831, upload-time = "2024-12-24T18:10:15.512Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335, upload-time = "2024-12-24T18:10:18.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862, upload-time = "2024-12-24T18:10:19.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673, upload-time = "2024-12-24T18:10:21.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211, upload-time = "2024-12-24T18:10:22.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039, upload-time = "2024-12-24T18:10:24.802Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939, upload-time = "2024-12-24T18:10:26.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075, upload-time = "2024-12-24T18:10:30.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340, upload-time = "2024-12-24T18:10:32.679Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404, upload-time = "2024-12-24T18:10:44.272Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423, upload-time = "2024-12-24T18:10:45.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184, upload-time = "2024-12-24T18:10:47.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268, upload-time = "2024-12-24T18:10:50.589Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601, upload-time = "2024-12-24T18:10:52.541Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098, upload-time = "2024-12-24T18:10:53.789Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520, upload-time = "2024-12-24T18:10:55.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852, upload-time = "2024-12-24T18:10:57.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488, upload-time = "2024-12-24T18:10:59.43Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192, upload-time = "2024-12-24T18:11:00.676Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162, upload-time = "2024-12-24T18:11:07.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263, upload-time = "2024-12-24T18:11:08.374Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966, upload-time = "2024-12-24T18:11:09.831Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992, upload-time = "2024-12-24T18:11:12.03Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162, upload-time = "2024-12-24T18:11:13.372Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972, upload-time = "2024-12-24T18:11:14.628Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095, upload-time = "2024-12-24T18:11:17.672Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668, upload-time = "2024-12-24T18:11:18.989Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073, upload-time = "2024-12-24T18:11:21.507Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload-time = "2024-12-24T18:11:22.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.6.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/84/ba/ac14d281f80aab516275012e8875991bb06203957aa1e19950139238d658/coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23", size = 803868, upload-time = "2024-12-26T16:59:18.734Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/62/c6a0cf80318c1c1af376d52df444da3608eafc913b82c84a4600d8349472/coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132", size = 240474, upload-time = "2024-12-26T16:57:28.659Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/59/750adafc2e57786d2e8739a46b680d4fb0fbc2d57fbcb161290a9f1ecf23/coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f", size = 237880, upload-time = "2024-12-26T16:57:30.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/f8/ef009b3b98e9f7033c19deb40d629354aab1d8b2d7f9cfec284dbedf5096/coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994", size = 239750, upload-time = "2024-12-26T16:57:31.48Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/e2/6622f3b70f5f5b59f705e680dae6db64421af05a5d1e389afd24dae62e5b/coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99", size = 238642, upload-time = "2024-12-26T16:57:34.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/10/57ac3f191a3c95c67844099514ff44e6e19b2915cd1c22269fb27f9b17b6/coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd", size = 237266, upload-time = "2024-12-26T16:57:35.48Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/2d/7016f4ad9d553cabcb7333ed78ff9d27248ec4eba8dd21fa488254dff894/coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377", size = 238045, upload-time = "2024-12-26T16:57:36.952Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/d1/febf59030ce1c83b7331c3546d7317e5120c5966471727aa7ac157729c4b/coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0", size = 241537, upload-time = "2024-12-26T16:57:48.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/7e/5ac4c90192130e7cf8b63153fe620c8bfd9068f89a6d9b5f26f1550f7a26/coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50", size = 238572, upload-time = "2024-12-26T16:57:51.668Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/03/0334a79b26ecf59958f2fe9dd1f5ab3e2f88db876f5071933de39af09647/coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022", size = 240639, upload-time = "2024-12-26T16:57:53.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/45/8a707f23c202208d7b286d78ad6233f50dcf929319b664b6cc18a03c1aae/coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b", size = 240072, upload-time = "2024-12-26T16:57:56.087Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/02/603ce0ac2d02bc7b393279ef618940b4a0535b0868ee791140bda9ecfa40/coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0", size = 238386, upload-time = "2024-12-26T16:57:57.572Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/62/4e6887e9be060f5d18f1dd58c2838b2d9646faf353232dec4e2d4b1c8644/coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852", size = 240054, upload-time = "2024-12-26T16:57:58.967Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/0a/d89bc2d1cc61d3a8dfe9e9d75217b2be85f6c73ebf1b9e3c2f4e797f4531/coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690", size = 241083, upload-time = "2024-12-26T16:58:10.27Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/81/6d64b88a00c7a7aaed3a657b8eaa0931f37a6395fcef61e53ff742b49c97/coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18", size = 238235, upload-time = "2024-12-26T16:58:12.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/0b/7797d4193f5adb4b837207ed87fecf5fc38f7cc612b369a8e8e12d9fa114/coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c", size = 240220, upload-time = "2024-12-26T16:58:15.619Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/4d/6f83ca1bddcf8e51bf8ff71572f39a1c73c34cf50e752a952c34f24d0a60/coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd", size = 239847, upload-time = "2024-12-26T16:58:17.126Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/9d/2470df6aa146aff4c65fee0f87f58d2164a67533c771c9cc12ffcdb865d5/coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e", size = 237922, upload-time = "2024-12-26T16:58:20.198Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/dd/723fef5d901e6a89f2507094db66c091449c8ba03272861eaefa773ad95c/coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694", size = 239783, upload-time = "2024-12-26T16:58:23.614Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/7d/125a5362180fcc1c03d91850fc020f3831d5cda09319522bcfa6b2b70be7/coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8", size = 252039, upload-time = "2024-12-26T16:58:36.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/9c/4358bf3c74baf1f9bddd2baf3756b54c07f2cfd2535f0a47f1e7757e54b3/coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098", size = 247758, upload-time = "2024-12-26T16:58:39.458Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/c7/de3eb6fc5263b26fab5cda3de7a0f80e317597a4bad4781859f72885f300/coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb", size = 250119, upload-time = "2024-12-26T16:58:41.018Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/e6/43de91f8ba2ec9140c6a4af1102141712949903dc732cf739167cfa7a3bc/coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0", size = 249597, upload-time = "2024-12-26T16:58:42.827Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/40/61158b5499aa2adf9e37bc6d0117e8f6788625b283d51e7e0c53cf340530/coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf", size = 247473, upload-time = "2024-12-26T16:58:44.486Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/69/b3f2416725621e9f112e74e8470793d5b5995f146f596f133678a633b77e/coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2", size = 248737, upload-time = "2024-12-26T16:58:45.919Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "44.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'linux'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/91/4c/45dfa6829acffa344e3967d6006ee4ae8be57af746ae2eba1c431949b32c/cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02", size = 710657, upload-time = "2024-11-27T18:07:10.168Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/5b/3759e30a103144e29632e7cb72aec28cedc79e514b2ea8896bb17163c19b/cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092", size = 3922710, upload-time = "2024-11-27T18:05:58.621Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/58/3b14bf39f1a0cfd679e753e8647ada56cddbf5acebffe7db90e184c76168/cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f", size = 4137546, upload-time = "2024-11-27T18:06:01.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/65/13d9e76ca19b0ba5603d71ac8424b5694415b348e719db277b5edc985ff5/cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb", size = 3915420, upload-time = "2024-11-27T18:06:03.487Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/07/40fe09ce96b91fc9276a9ad272832ead0fddedcba87f1190372af8e3039c/cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b", size = 4154498, upload-time = "2024-11-27T18:06:05.763Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/ea/af65619c800ec0a7e4034207aec543acdf248d9bffba0533342d1bd435e1/cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543", size = 3932569, upload-time = "2024-11-27T18:06:07.489Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/af/d1deb0c04d59612e3d5e54203159e284d3e7a6921e565bb0eeb6269bdd8a/cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e", size = 4016721, upload-time = "2024-11-27T18:06:11.57Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/69/7ca326c55698d0688db867795134bdfac87136b80ef373aaa42b225d6dd5/cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e", size = 4240915, upload-time = "2024-11-27T18:06:13.515Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/07/5f165b6c65696ef75601b781a280fc3b33f1e0cd6aa5a92d9fb96c410e97/cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7", size = 3922613, upload-time = "2024-11-27T18:06:24.314Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/34/6b3ac1d80fc174812486561cf25194338151780f27e438526f9c64e16869/cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc", size = 4137925, upload-time = "2024-11-27T18:06:27.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/c7/c656eb08fd22255d21bc3129625ed9cd5ee305f33752ef2278711b3fa98b/cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289", size = 3915417, upload-time = "2024-11-27T18:06:28.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/82/72403624f197af0db6bac4e58153bc9ac0e6020e57234115db9596eee85d/cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7", size = 4155160, upload-time = "2024-11-27T18:06:30.866Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/cd/2f3c440913d4329ade49b146d74f2e9766422e1732613f57097fea61f344/cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c", size = 3932331, upload-time = "2024-11-27T18:06:33.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/df/8be88797f0a1cca6e255189a57bb49237402b1880d6e8721690c5603ac23/cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64", size = 4017372, upload-time = "2024-11-27T18:06:38.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/36/5ccc376f025a834e72b8e52e18746b927f34e4520487098e283a719c205e/cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285", size = 4239657, upload-time = "2024-11-27T18:06:41.045Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||
]
|
||||
|
||||
[[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 = "python-dotenv"
|
||||
version = "1.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115, upload-time = "2024-01-23T06:33:00.505Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863, upload-time = "2024-01-23T06:32:58.246Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi", marker = "sys_platform == 'linux'" },
|
||||
{ name = "charset-normalizer", marker = "sys_platform == 'linux'" },
|
||||
{ name = "idna", marker = "sys_platform == 'linux'" },
|
||||
{ name = "urllib3", marker = "sys_platform == 'linux'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruamel-yaml"
|
||||
version = "0.18.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "ruamel-yaml-clib", marker = "python_full_version < '3.13' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ea/46/f44d8be06b85bc7c4d8c95d658be2b68f27711f279bf9dd0612a5e4794f5/ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58", size = 143447, upload-time = "2025-01-06T14:08:51.334Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/36/dfc1ebc0081e6d39924a2cc53654497f967a084a436bb64402dfce4254d9/ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1", size = 117729, upload-time = "2025-01-06T14:08:47.471Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruamel-yaml-clib"
|
||||
version = "0.2.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315, upload-time = "2024-10-20T10:10:56.22Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480, upload-time = "2024-10-20T10:12:46.758Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068, upload-time = "2024-10-20T10:12:48.605Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012, upload-time = "2024-10-20T10:12:51.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352, upload-time = "2024-10-21T11:26:41.438Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344, upload-time = "2024-10-21T11:26:43.62Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498, upload-time = "2024-12-11T19:58:15.592Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362, upload-time = "2024-10-20T10:12:57.155Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118, upload-time = "2024-10-20T10:12:58.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497, upload-time = "2024-10-20T10:13:00.211Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042, upload-time = "2024-10-21T11:26:46.038Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831, upload-time = "2024-10-21T11:26:47.487Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/5d/36619b61ffa2429eeaefaab4f3374666adf36ad8ac6330d855848d7d36fd/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", size = 715692, upload-time = "2024-12-11T19:58:17.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/5e/212f473a93ae78c669ffa0cb051e3fee1139cb2d385d2ae1653d64281507/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475", size = 642488, upload-time = "2024-10-20T10:13:05.906Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/8f/ecfbe2123ade605c49ef769788f79c38ddb1c8fa81e01f4dbf5cf1a44b16/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef", size = 745066, upload-time = "2024-10-20T10:13:07.26Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/a9/28f60726d29dfc01b8decdb385de4ced2ced9faeb37a847bd5cf26836815/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6", size = 701785, upload-time = "2024-10-20T10:13:08.504Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/7e/8e7ec45920daa7f76046578e4f677a3215fe8f18ee30a9cb7627a19d9b4c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf", size = 693017, upload-time = "2024-10-21T11:26:48.866Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/b3/d650eaade4ca225f02a648321e1ab835b9d361c60d51150bac49063b83fa/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1", size = 741270, upload-time = "2024-10-21T11:26:50.213Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/b8/01c29b924dcbbed75cc45b30c30d565d763b9c4d540545a0eeecffb8f09c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01", size = 709059, upload-time = "2024-12-11T19:58:18.846Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.8.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/da/00/089db7890ea3be5709e3ece6e46408d6f1e876026ec3fd081ee585fef209/ruff-0.8.6.tar.gz", hash = "sha256:dcad24b81b62650b0eb8814f576fc65cfee8674772a6e24c9b747911801eeaa5", size = 3473116, upload-time = "2025-01-04T12:23:00.794Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/28/aa07903694637c2fa394a9f4fe93cf861ad8b09f1282fa650ef07ff9fe97/ruff-0.8.6-py3-none-linux_armv6l.whl", hash = "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3", size = 10628735, upload-time = "2025-01-04T12:21:53.632Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/e9/e0ed4af1794335fb280c4fac180f2bf40f6a3b859cae93a5a3ada27325ae/ruff-0.8.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0509e8da430228236a18a677fcdb0c1f102dd26d5520f71f79b094963322ed25", size = 10861031, upload-time = "2025-01-04T12:22:09.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/68/da0db02f5ecb2ce912c2bef2aa9fcb8915c31e9bc363969cfaaddbc4c1c2/ruff-0.8.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a7ddb221779871cf226100e677b5ea38c2d54e9e2c8ed847450ebbdf99b32d", size = 10388246, upload-time = "2025-01-04T12:22:12.63Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/1d/b85383db181639019b50eb277c2ee48f9f5168f4f7c287376f2b6e2a6dc2/ruff-0.8.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:248b1fb3f739d01d528cc50b35ee9c4812aa58cc5935998e776bf8ed5b251e75", size = 11424693, upload-time = "2025-01-04T12:22:17.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/b7/30bc78a37648d31bfc7ba7105b108cb9091cd925f249aa533038ebc5a96f/ruff-0.8.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bc3c083c50390cf69e7e1b5a5a7303898966be973664ec0c4a4acea82c1d4315", size = 12141921, upload-time = "2025-01-04T12:22:20.456Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/b3/ee0a14cf6a1fbd6965b601c88d5625d250b97caf0534181e151504498f86/ruff-0.8.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52d587092ab8df308635762386f45f4638badb0866355b2b86760f6d3c076188", size = 11692419, upload-time = "2025-01-04T12:22:23.62Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/d6/c597062b2931ba3e3861e80bd2b147ca12b3370afc3889af46f29209037f/ruff-0.8.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61323159cf21bc3897674e5adb27cd9e7700bab6b84de40d7be28c3d46dc67cf", size = 12981648, upload-time = "2025-01-04T12:22:26.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/84/21f578c2a4144917985f1f4011171aeff94ab18dfa5303ac632da2f9af36/ruff-0.8.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae4478b1471fc0c44ed52a6fb787e641a2ac58b1c1f91763bafbc2faddc5117", size = 11251801, upload-time = "2025-01-04T12:22:29.59Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/aa/1ac02537c8edeb13e0955b5db86b5c050a1dcba54f6d49ab567decaa59c1/ruff-0.8.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0c000a471d519b3e6cfc9c6680025d923b4ca140ce3e4612d1a2ef58e11f11fe", size = 10849857, upload-time = "2025-01-04T12:22:33.536Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/00/020cb222252d833956cb3b07e0e40c9d4b984fbb2dc3923075c8f944497d/ruff-0.8.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9257aa841e9e8d9b727423086f0fa9a86b6b420fbf4bf9e1465d1250ce8e4d8d", size = 10470852, upload-time = "2025-01-04T12:22:36.374Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/56/e6d6578202a0141cd52299fe5acb38b2d873565f4670c7a5373b637cf58d/ruff-0.8.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45a56f61b24682f6f6709636949ae8cc82ae229d8d773b4c76c09ec83964a95a", size = 10972997, upload-time = "2025-01-04T12:22:41.424Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/31/dd0db1f4796bda30dea7592f106f3a67a8f00bcd3a50df889fbac58e2786/ruff-0.8.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:496dd38a53aa173481a7d8866bcd6451bd934d06976a2505028a50583e001b76", size = 11317760, upload-time = "2025-01-04T12:22:44.541Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.19.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi", marker = "sys_platform == 'linux'" },
|
||||
{ name = "urllib3", marker = "sys_platform == 'linux'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/36/4a/eccdcb8c2649d53440ae1902447b86e2e2ad1bc84207c80af9696fa07614/sentry_sdk-2.19.2.tar.gz", hash = "sha256:467df6e126ba242d39952375dd816fbee0f217d119bf454a8ce74cf1e7909e8d", size = 299047, upload-time = "2024-12-06T08:23:30.762Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/31/4d/74597bb6bcc23abc774b8901277652c61331a9d4d0a8d1bdb20679b9bbcb/sentry_sdk-2.19.2-py2.py3-none-any.whl", hash = "sha256:ebdc08228b4d131128e568d696c210d846e5b9d70aa0327dec6b1272d9d40b84", size = 322942, upload-time = "2024-12-06T08:23:28.842Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "transip-client"
|
||||
version = "0.7.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "click", marker = "sys_platform == 'linux'" },
|
||||
{ name = "cryptography", marker = "sys_platform == 'linux'" },
|
||||
{ name = "python-dotenv", marker = "sys_platform == 'linux'" },
|
||||
{ name = "requests", marker = "sys_platform == 'linux'" },
|
||||
{ name = "ruamel-yaml", marker = "sys_platform == 'linux'" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
sentry-enabled = [
|
||||
{ name = "sentry-sdk", marker = "sys_platform == 'linux'" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
ci = [
|
||||
{ name = "coverage", marker = "sys_platform == 'linux'" },
|
||||
]
|
||||
dev = [
|
||||
{ name = "ruff", marker = "sys_platform == 'linux'" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "click", specifier = ">=8.0.1" },
|
||||
{ name = "cryptography", specifier = ">=3.4.7" },
|
||||
{ name = "python-dotenv", specifier = ">=0.15.0" },
|
||||
{ name = "requests", specifier = ">=2.25.1" },
|
||||
{ name = "ruamel-yaml", specifier = ">=0.18.10" },
|
||||
{ name = "sentry-sdk", marker = "extra == 'sentry-enabled'", specifier = ">=0.19.5" },
|
||||
]
|
||||
provides-extras = ["sentry-enabled"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
ci = [{ name = "coverage", specifier = ">=5.3.1" }]
|
||||
dev = [{ name = "ruff", specifier = ">=0.8.6" }]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268, upload-time = "2024-12-22T07:47:30.032Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369, upload-time = "2024-12-22T07:47:28.074Z" },
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue