haplohub-cli 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- haplohub_cli-0.0.1/.env.sample +3 -0
- haplohub_cli-0.0.1/.github/workflows/on_pr.yml +27 -0
- haplohub_cli-0.0.1/.github/workflows/on_push_main.yml +79 -0
- haplohub_cli-0.0.1/.gitignore +18 -0
- haplohub_cli-0.0.1/.pre-commit-config.yaml +19 -0
- haplohub_cli-0.0.1/PKG-INFO +61 -0
- haplohub_cli-0.0.1/README.md +45 -0
- haplohub_cli-0.0.1/haplohub_cli/__init__.py +3 -0
- haplohub_cli-0.0.1/haplohub_cli/_version.py +21 -0
- haplohub_cli-0.0.1/haplohub_cli/auth/__init__.py +0 -0
- haplohub_cli-0.0.1/haplohub_cli/auth/auth.py +42 -0
- haplohub_cli-0.0.1/haplohub_cli/auth/auth0_client.py +104 -0
- haplohub_cli-0.0.1/haplohub_cli/auth/auth_web_server.py +43 -0
- haplohub_cli-0.0.1/haplohub_cli/auth/tests/__init__.py +0 -0
- haplohub_cli-0.0.1/haplohub_cli/auth/tests/test_auth_web_server.py +22 -0
- haplohub_cli-0.0.1/haplohub_cli/auth/tests/test_token_storage.py +43 -0
- haplohub_cli-0.0.1/haplohub_cli/auth/token_storage.py +27 -0
- haplohub_cli-0.0.1/haplohub_cli/cli.py +35 -0
- haplohub_cli-0.0.1/haplohub_cli/commands/__init__.py +0 -0
- haplohub_cli-0.0.1/haplohub_cli/commands/cohort.py +33 -0
- haplohub_cli-0.0.1/haplohub_cli/commands/config.py +40 -0
- haplohub_cli-0.0.1/haplohub_cli/commands/file.py +84 -0
- haplohub_cli-0.0.1/haplohub_cli/commands/login.py +17 -0
- haplohub_cli-0.0.1/haplohub_cli/commands/model/__init__.py +0 -0
- haplohub_cli-0.0.1/haplohub_cli/commands/model/model.py +62 -0
- haplohub_cli-0.0.1/haplohub_cli/commands/model/run.py +63 -0
- haplohub_cli-0.0.1/haplohub_cli/commands/version.py +11 -0
- haplohub_cli-0.0.1/haplohub_cli/config/__init__.py +0 -0
- haplohub_cli-0.0.1/haplohub_cli/config/config.py +13 -0
- haplohub_cli-0.0.1/haplohub_cli/config/config_manager.py +57 -0
- haplohub_cli-0.0.1/haplohub_cli/config/environments.py +20 -0
- haplohub_cli-0.0.1/haplohub_cli/core/__init__.py +7 -0
- haplohub_cli-0.0.1/haplohub_cli/core/api/__init__.py +0 -0
- haplohub_cli-0.0.1/haplohub_cli/core/api/client.py +45 -0
- haplohub_cli-0.0.1/haplohub_cli/core/checksum.py +6 -0
- haplohub_cli-0.0.1/haplohub_cli/core/network.py +6 -0
- haplohub_cli-0.0.1/haplohub_cli/core/slug.py +7 -0
- haplohub_cli-0.0.1/haplohub_cli/core/tests/__init__.py +0 -0
- haplohub_cli-0.0.1/haplohub_cli/core/tests/test_network.py +20 -0
- haplohub_cli-0.0.1/haplohub_cli/core/tests/test_slug.py +20 -0
- haplohub_cli-0.0.1/haplohub_cli/formatters/__init__.py +6 -0
- haplohub_cli-0.0.1/haplohub_cli/formatters/cohort.py +39 -0
- haplohub_cli-0.0.1/haplohub_cli/formatters/config.py +15 -0
- haplohub_cli-0.0.1/haplohub_cli/formatters/decorators.py +9 -0
- haplohub_cli-0.0.1/haplohub_cli/formatters/file.py +46 -0
- haplohub_cli-0.0.1/haplohub_cli/formatters/formatter_registry.py +21 -0
- haplohub_cli-0.0.1/haplohub_cli/formatters/generic.py +11 -0
- haplohub_cli-0.0.1/haplohub_cli/formatters/model.py +31 -0
- haplohub_cli-0.0.1/haplohub_cli/formatters/utils.py +13 -0
- haplohub_cli-0.0.1/haplohub_cli/settings.py +24 -0
- haplohub_cli-0.0.1/haplohub_cli.egg-info/PKG-INFO +61 -0
- haplohub_cli-0.0.1/haplohub_cli.egg-info/SOURCES.txt +57 -0
- haplohub_cli-0.0.1/haplohub_cli.egg-info/dependency_links.txt +1 -0
- haplohub_cli-0.0.1/haplohub_cli.egg-info/entry_points.txt +2 -0
- haplohub_cli-0.0.1/haplohub_cli.egg-info/requires.txt +8 -0
- haplohub_cli-0.0.1/haplohub_cli.egg-info/top_level.txt +1 -0
- haplohub_cli-0.0.1/pyproject.toml +56 -0
- haplohub_cli-0.0.1/setup.cfg +4 -0
- haplohub_cli-0.0.1/uv.lock +1538 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: On Pull Request
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
lint:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: astral-sh/setup-uv@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python-version }}
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: uv sync --dev
|
|
22
|
+
- name: Lint and format with ruff
|
|
23
|
+
run: |
|
|
24
|
+
ruff format --check .
|
|
25
|
+
ruff check .
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: uv run pytest -v
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
name: On Push to Main
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["v[0-9]+.[0-9]+.[0-9]+*"]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
attestations: write
|
|
10
|
+
contents: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
name: Build python package
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
- uses: hynek/build-and-inspect-python-package@v2
|
|
22
|
+
with:
|
|
23
|
+
attest-build-provenance-github: 'true'
|
|
24
|
+
|
|
25
|
+
publish-test-pypi:
|
|
26
|
+
name: Publish to Test PyPI
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
needs: build
|
|
29
|
+
environment: testpypi
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/download-artifact@v4
|
|
32
|
+
with:
|
|
33
|
+
name: Packages
|
|
34
|
+
path: dist
|
|
35
|
+
- name: Publish to Test PyPI
|
|
36
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
37
|
+
with:
|
|
38
|
+
verbose: true
|
|
39
|
+
repository-url: https://test.pypi.org/legacy/
|
|
40
|
+
|
|
41
|
+
publish-pypi:
|
|
42
|
+
name: Publish to PyPI
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
45
|
+
needs: build
|
|
46
|
+
environment: pypi
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/download-artifact@v4
|
|
49
|
+
with:
|
|
50
|
+
name: Packages
|
|
51
|
+
path: dist
|
|
52
|
+
- name: Publish to Test PyPI
|
|
53
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
54
|
+
with:
|
|
55
|
+
verbose: true
|
|
56
|
+
|
|
57
|
+
github-release:
|
|
58
|
+
name: Create GitHub Release
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
needs: publish-pypi
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/download-artifact@v4
|
|
63
|
+
with:
|
|
64
|
+
name: Packages
|
|
65
|
+
path: dist
|
|
66
|
+
- name: Sign the dists with Sigstore
|
|
67
|
+
uses: sigstore/gh-action-sigstore-python@v3.0.0
|
|
68
|
+
with:
|
|
69
|
+
inputs: >-
|
|
70
|
+
./dist/*.tar.gz
|
|
71
|
+
./dist/*.whl
|
|
72
|
+
- name: Create GitHub Release
|
|
73
|
+
uses: actions/create-release@v1
|
|
74
|
+
env:
|
|
75
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
76
|
+
with:
|
|
77
|
+
tag_name: ${{ github.ref }}
|
|
78
|
+
release_name: Release ${{ github.ref }}
|
|
79
|
+
body: Release ${{ github.ref }}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Environment variables
|
|
2
|
+
.env
|
|
3
|
+
|
|
4
|
+
# Virtual environment
|
|
5
|
+
.venv/
|
|
6
|
+
|
|
7
|
+
# Python caches, tools caches
|
|
8
|
+
__pycache__/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
|
|
12
|
+
# Distribution files
|
|
13
|
+
dist/
|
|
14
|
+
*.egg-info/
|
|
15
|
+
haplohub_cli/_version.py
|
|
16
|
+
|
|
17
|
+
# Mac OS
|
|
18
|
+
.DS_Store
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.9.6
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: ["--fix"]
|
|
7
|
+
always_run: true
|
|
8
|
+
- id: ruff-format
|
|
9
|
+
always_run: true
|
|
10
|
+
- repo: local
|
|
11
|
+
hooks:
|
|
12
|
+
- id: tests
|
|
13
|
+
stages: [push]
|
|
14
|
+
name: Run tests
|
|
15
|
+
entry: uv run pytest -v
|
|
16
|
+
language: system
|
|
17
|
+
verbose: true
|
|
18
|
+
pass_filenames: false
|
|
19
|
+
always_run: true
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: haplohub-cli
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: HaploHub Command Line Interface
|
|
5
|
+
Author-email: Mike Polcari <mike@haplotype-labs.com>, Ilya Khrustalev <ilya@haplotype-labs.com>
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: auth0-python>=4.8.0
|
|
9
|
+
Requires-Dist: click>=8.1.8
|
|
10
|
+
Requires-Dist: haplohub>=1.0.4
|
|
11
|
+
Requires-Dist: pendulum>=3.0.0
|
|
12
|
+
Requires-Dist: python-dotenv>=1.0.1
|
|
13
|
+
Requires-Dist: requests>=2.32.0
|
|
14
|
+
Requires-Dist: rich>=13.9.4
|
|
15
|
+
Requires-Dist: haplohub
|
|
16
|
+
|
|
17
|
+
# HaploHub CLI
|
|
18
|
+
|
|
19
|
+
HaploHub is a platform for haplotype data storage and analysis. This CLI provides a way to interact with the HaploHub API.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
To install the CLI, run the following command:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python -m venv .venv
|
|
27
|
+
source .venv/bin/activate
|
|
28
|
+
pip install --upgrade pip
|
|
29
|
+
pip install haplohub-cli
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
You can ensure that the CLI is installed correctly by running the following command:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
haplohub version
|
|
36
|
+
|
|
37
|
+
# HaploHub CLI version 0.1.0
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
haplohub --help
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Login
|
|
47
|
+
|
|
48
|
+
The first time you run the CLI, you will be prompted to login.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
haplohub login
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This will open a browser window to the HaploHub login page. Once you login, you will be redirected to the CLI.
|
|
55
|
+
```bash
|
|
56
|
+
Your browser has been opened to authenticate with HaploHub.
|
|
57
|
+
|
|
58
|
+
https://xxx.us.auth0.com/authorize...
|
|
59
|
+
|
|
60
|
+
Successfully authenticated with HaploHub.
|
|
61
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# HaploHub CLI
|
|
2
|
+
|
|
3
|
+
HaploHub is a platform for haplotype data storage and analysis. This CLI provides a way to interact with the HaploHub API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
To install the CLI, run the following command:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
python -m venv .venv
|
|
11
|
+
source .venv/bin/activate
|
|
12
|
+
pip install --upgrade pip
|
|
13
|
+
pip install haplohub-cli
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
You can ensure that the CLI is installed correctly by running the following command:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
haplohub version
|
|
20
|
+
|
|
21
|
+
# HaploHub CLI version 0.1.0
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
haplohub --help
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Login
|
|
31
|
+
|
|
32
|
+
The first time you run the CLI, you will be prompted to login.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
haplohub login
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This will open a browser window to the HaploHub login page. Once you login, you will be redirected to the CLI.
|
|
39
|
+
```bash
|
|
40
|
+
Your browser has been opened to authenticate with HaploHub.
|
|
41
|
+
|
|
42
|
+
https://xxx.us.auth0.com/authorize...
|
|
43
|
+
|
|
44
|
+
Successfully authenticated with HaploHub.
|
|
45
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '0.0.1'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 0, 1)
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import webbrowser
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from haplohub_cli.auth.auth0_client import auth0_client
|
|
6
|
+
from haplohub_cli.auth.auth_web_server import AuthWebServer
|
|
7
|
+
from haplohub_cli.config.config_manager import config_manager
|
|
8
|
+
from haplohub_cli.core import ensure_config_dir
|
|
9
|
+
from haplohub_cli.core.network import check_port_available
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def token_login(refresh_token: str):
|
|
13
|
+
credentials = auth0_client.exchange_refresh_token(refresh_token)
|
|
14
|
+
|
|
15
|
+
click.echo("Successfully authenticated with HaploHub.")
|
|
16
|
+
auth0_client.token_storage.store_credentials(credentials)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def interactive_login():
|
|
20
|
+
ensure_config_dir()
|
|
21
|
+
|
|
22
|
+
if not check_port_available(config_manager.config.redirect_port):
|
|
23
|
+
click.echo(
|
|
24
|
+
f"Port {config_manager.config.redirect_port} is already in use. Please ensure it is free and try again.\n"
|
|
25
|
+
f"Use `lsof -i4TCP:{config_manager.config.redirect_port} -sTCP:LISTEN -P -n` to find the process using the port."
|
|
26
|
+
)
|
|
27
|
+
exit(1)
|
|
28
|
+
|
|
29
|
+
auth_request = auth0_client.init_auth_request()
|
|
30
|
+
auth_url = auth_request.auth_url
|
|
31
|
+
|
|
32
|
+
click.echo(
|
|
33
|
+
f"Your browser has been opened to authenticate with HaploHub.\n\n {auth_url[0 : auth_url.find('?')] + '...'}\n"
|
|
34
|
+
)
|
|
35
|
+
webbrowser.open(auth_url)
|
|
36
|
+
|
|
37
|
+
auth_web_server = AuthWebServer(config_manager.config.redirect_port)
|
|
38
|
+
auth_code = auth_web_server.handle_request()
|
|
39
|
+
credentials = auth_request.exchange_code(auth_code)
|
|
40
|
+
|
|
41
|
+
click.echo("Successfully authenticated with HaploHub.")
|
|
42
|
+
auth0_client.token_storage.store_credentials(credentials)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from requests import Session
|
|
6
|
+
|
|
7
|
+
from haplohub_cli.auth.token_storage import TokenStorage, token_storage
|
|
8
|
+
from haplohub_cli.config.config_manager import config_manager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AuthRequest:
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
client: "Auth0Client",
|
|
15
|
+
scopes: list[str],
|
|
16
|
+
):
|
|
17
|
+
self.client = client
|
|
18
|
+
self.scopes = scopes
|
|
19
|
+
self.code_verifier, self.code_challenge = self.generate_code_verifier()
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def auth_url(self):
|
|
23
|
+
return (
|
|
24
|
+
f"https://{self.client.domain}/authorize"
|
|
25
|
+
f"?client_id={self.client.client_id}"
|
|
26
|
+
f"&response_type=code"
|
|
27
|
+
f"&redirect_uri={self.client.redirect_uri}"
|
|
28
|
+
f"&scope={' '.join(self.scopes)}"
|
|
29
|
+
f"&code_challenge={self.code_challenge}"
|
|
30
|
+
f"&code_challenge_method=S256"
|
|
31
|
+
f"&audience={self.client.audience}"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def generate_code_verifier(self):
|
|
35
|
+
verifier = base64.urlsafe_b64encode(os.urandom(32)).decode("utf-8").rstrip("=")
|
|
36
|
+
challenge = hashlib.sha256(verifier.encode("utf-8")).digest()
|
|
37
|
+
challenge = base64.urlsafe_b64encode(challenge).decode("utf-8").rstrip("=")
|
|
38
|
+
return verifier, challenge
|
|
39
|
+
|
|
40
|
+
def exchange_code(self, code: str):
|
|
41
|
+
return self.client.exchange_code(code, self.code_verifier)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Auth0Client:
|
|
45
|
+
http = Session()
|
|
46
|
+
|
|
47
|
+
def __init__(self, token_storage: TokenStorage, domain: str, client_id: str, audience: str, redirect_uri: str):
|
|
48
|
+
self.token_storage = token_storage
|
|
49
|
+
self.domain = domain
|
|
50
|
+
self.client_id = client_id
|
|
51
|
+
self.audience = audience
|
|
52
|
+
self.redirect_uri = redirect_uri
|
|
53
|
+
|
|
54
|
+
def init_auth_request(self, scopes: list[str] = ("openid", "profile", "email")):
|
|
55
|
+
return AuthRequest(client=self, scopes=scopes)
|
|
56
|
+
|
|
57
|
+
def exchange_refresh_token(self, refresh_token: str):
|
|
58
|
+
return self._make_request(
|
|
59
|
+
"oauth/token",
|
|
60
|
+
"POST",
|
|
61
|
+
json={
|
|
62
|
+
"grant_type": "refresh_token",
|
|
63
|
+
"client_id": self.client_id,
|
|
64
|
+
"refresh_token": refresh_token,
|
|
65
|
+
"audience": self.audience,
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def exchange_code(self, code: str, code_verifier: str):
|
|
70
|
+
return self._make_request(
|
|
71
|
+
"oauth/token",
|
|
72
|
+
"POST",
|
|
73
|
+
json={
|
|
74
|
+
"grant_type": "authorization_code",
|
|
75
|
+
"client_id": self.client_id,
|
|
76
|
+
"code": code,
|
|
77
|
+
"redirect_uri": self.redirect_uri,
|
|
78
|
+
"code_verifier": code_verifier,
|
|
79
|
+
"audience": self.audience,
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def get_user_info(self):
|
|
84
|
+
return self._make_request("userinfo", "GET")
|
|
85
|
+
|
|
86
|
+
def _make_request(self, path: str, method: str, data: dict = None, json: dict = None):
|
|
87
|
+
url = f"https://{self.domain}/{path}"
|
|
88
|
+
|
|
89
|
+
headers = None
|
|
90
|
+
if self.token_storage.credentials_exist:
|
|
91
|
+
headers = {"Authorization": f"Bearer {self.token_storage.get_access_token()}"}
|
|
92
|
+
|
|
93
|
+
response = self.http.request(method, url, headers=headers, data=data, json=json)
|
|
94
|
+
response.raise_for_status()
|
|
95
|
+
return response.json()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
auth0_client = Auth0Client(
|
|
99
|
+
token_storage=token_storage,
|
|
100
|
+
domain=config_manager.config.auth0_domain,
|
|
101
|
+
client_id=config_manager.config.auth0_client_id,
|
|
102
|
+
audience=config_manager.config.auth0_audience,
|
|
103
|
+
redirect_uri=config_manager.config.auth0_redirect_uri,
|
|
104
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
2
|
+
from typing import Any
|
|
3
|
+
from urllib.parse import parse_qs, urlparse
|
|
4
|
+
|
|
5
|
+
HTML_SNIPPET = """
|
|
6
|
+
<html>
|
|
7
|
+
<body>
|
|
8
|
+
<h1>Authentication successful!</h1>
|
|
9
|
+
<p>
|
|
10
|
+
You can now close this window.
|
|
11
|
+
</p>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AuthHandler(BaseHTTPRequestHandler):
|
|
18
|
+
def log_message(self, format: str, *args: Any) -> None:
|
|
19
|
+
# Suppress logging
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def do_GET(self):
|
|
23
|
+
query = urlparse(self.path).query
|
|
24
|
+
params = parse_qs(query)
|
|
25
|
+
if "code" in params:
|
|
26
|
+
self.server.last_auth_code = params["code"][0]
|
|
27
|
+
self.send_response(200)
|
|
28
|
+
self.send_header("Content-Type", "text/html")
|
|
29
|
+
self.end_headers()
|
|
30
|
+
self.wfile.write(HTML_SNIPPET.encode("utf-8"))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AuthWebServer:
|
|
34
|
+
def __init__(self, port: int):
|
|
35
|
+
self.port = port
|
|
36
|
+
self.server = HTTPServer(
|
|
37
|
+
("localhost", self.port),
|
|
38
|
+
AuthHandler,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def handle_request(self):
|
|
42
|
+
self.server.handle_request()
|
|
43
|
+
return self.server.last_auth_code
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from threading import Timer
|
|
2
|
+
from unittest import TestCase
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from haplohub_cli.auth.auth_web_server import AuthWebServer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AuthWebServerTestCase(TestCase):
|
|
10
|
+
@classmethod
|
|
11
|
+
def setUpClass(cls):
|
|
12
|
+
cls.port = 60000
|
|
13
|
+
cls.auth_code = "qwerty123456"
|
|
14
|
+
cls.instance = AuthWebServer(cls.port)
|
|
15
|
+
|
|
16
|
+
def test_auth_web_server_should_return_auth_code_from_redirect_uri(self):
|
|
17
|
+
Timer(0.01, self._send_request).start()
|
|
18
|
+
auth_code = self.instance.handle_request()
|
|
19
|
+
self.assertEqual(auth_code, self.auth_code)
|
|
20
|
+
|
|
21
|
+
def _send_request(self):
|
|
22
|
+
requests.get(f"http://localhost:{self.port}/?code={self.auth_code}")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from pyfakefs.fake_filesystem_unittest import TestCase
|
|
4
|
+
|
|
5
|
+
from haplohub_cli.auth.token_storage import TokenStorage
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TokenStorageTestCase(TestCase):
|
|
9
|
+
@classmethod
|
|
10
|
+
def setUpClass(cls):
|
|
11
|
+
cls.setUpClassPyfakefs()
|
|
12
|
+
cls.instance = TokenStorage("/tmp/test_token_storage.json")
|
|
13
|
+
|
|
14
|
+
def test_credentials_exist_should_return_false_when_file_does_not_exist(self):
|
|
15
|
+
self.assertFalse(self.instance.credentials_exist)
|
|
16
|
+
|
|
17
|
+
def test_credentials_exist_should_return_true_when_file_exists(self):
|
|
18
|
+
with open(self.instance.token_file, "w"):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
self.assertTrue(self.instance.credentials_exist)
|
|
22
|
+
|
|
23
|
+
def test_store_credentials_should_save_credentials_to_file(self):
|
|
24
|
+
creds = {"test": True}
|
|
25
|
+
|
|
26
|
+
self.instance.store_credentials(creds)
|
|
27
|
+
|
|
28
|
+
with open(self.instance.token_file, "r") as f:
|
|
29
|
+
self.assertEqual(json.load(f), creds)
|
|
30
|
+
|
|
31
|
+
def test_get_credentials_should_return_credentials_from_file(self):
|
|
32
|
+
creds = {"test": True}
|
|
33
|
+
|
|
34
|
+
self.instance.store_credentials(creds)
|
|
35
|
+
|
|
36
|
+
self.assertEqual(self.instance.get_credentials(), creds)
|
|
37
|
+
|
|
38
|
+
def test_get_access_token_should_return_access_token_from_credentials(self):
|
|
39
|
+
creds = {"access_token": "123456"}
|
|
40
|
+
|
|
41
|
+
self.instance.store_credentials(creds)
|
|
42
|
+
|
|
43
|
+
self.assertEqual(self.instance.get_access_token(), "123456")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from genericpath import exists
|
|
3
|
+
|
|
4
|
+
from haplohub_cli import settings
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TokenStorage:
|
|
8
|
+
def __init__(self, token_file: str):
|
|
9
|
+
self.token_file = token_file
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def credentials_exist(self):
|
|
13
|
+
return exists(self.token_file)
|
|
14
|
+
|
|
15
|
+
def store_credentials(self, credentials: dict):
|
|
16
|
+
with open(self.token_file, "w") as f:
|
|
17
|
+
json.dump(credentials, f)
|
|
18
|
+
|
|
19
|
+
def get_credentials(self):
|
|
20
|
+
with open(self.token_file, "r") as f:
|
|
21
|
+
return json.load(f)
|
|
22
|
+
|
|
23
|
+
def get_access_token(self):
|
|
24
|
+
return self.get_credentials()["access_token"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
token_storage = TokenStorage(settings.CREDENTIALS_FILE)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from haplohub_cli.commands import cohort, config, file, login, version
|
|
4
|
+
from haplohub_cli.commands.model import model
|
|
5
|
+
from haplohub_cli.formatters.formatter_registry import formatter_registry
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.group()
|
|
9
|
+
def cli():
|
|
10
|
+
"""
|
|
11
|
+
HaploHub CLI
|
|
12
|
+
|
|
13
|
+
To get started, take a look at the documentation:
|
|
14
|
+
https://github.com/haplotypelabs/haplohub-cli
|
|
15
|
+
"""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@cli.result_callback()
|
|
20
|
+
def formatter_callback(result):
|
|
21
|
+
if result is None or not formatter_registry.has_formatter(type(result)):
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
formatter_registry.format(result)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
cli.add_command(config.config)
|
|
28
|
+
cli.add_command(login.cmd)
|
|
29
|
+
cli.add_command(version.cmd)
|
|
30
|
+
cli.add_command(cohort.cohort)
|
|
31
|
+
cli.add_command(file.file)
|
|
32
|
+
cli.add_command(model.model)
|
|
33
|
+
|
|
34
|
+
if __name__ == "__main__":
|
|
35
|
+
cli()
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from haplohub import CreateCohortRequest
|
|
3
|
+
|
|
4
|
+
from haplohub_cli.core.api.client import client
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.group()
|
|
8
|
+
def cohort():
|
|
9
|
+
"""
|
|
10
|
+
Manage cohorts
|
|
11
|
+
"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@cohort.command()
|
|
16
|
+
def list():
|
|
17
|
+
return client.cohort.list_cohorts()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@cohort.command()
|
|
21
|
+
@click.argument("name")
|
|
22
|
+
@click.option("--description", type=str, required=False)
|
|
23
|
+
def create(name, description=None):
|
|
24
|
+
description = description or ""
|
|
25
|
+
|
|
26
|
+
request = CreateCohortRequest(name=name, description=description)
|
|
27
|
+
return client.cohort.create_cohort(request)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@cohort.command()
|
|
31
|
+
@click.argument("id")
|
|
32
|
+
def delete(id):
|
|
33
|
+
return client.cohort.delete_cohort(id)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from haplohub_cli.config.config_manager import config_manager
|
|
4
|
+
from haplohub_cli.config.environments import ENVIRONMENTS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.group()
|
|
8
|
+
def config():
|
|
9
|
+
"""
|
|
10
|
+
Manage configuration
|
|
11
|
+
"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@config.command()
|
|
16
|
+
def show():
|
|
17
|
+
return config_manager.config
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@config.command()
|
|
21
|
+
@click.argument("key")
|
|
22
|
+
@click.argument("value")
|
|
23
|
+
def set(key, value):
|
|
24
|
+
setattr(config_manager.config, key, value)
|
|
25
|
+
config_manager.save()
|
|
26
|
+
return config_manager.config
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@config.command()
|
|
30
|
+
@click.argument("environment", type=click.Choice(ENVIRONMENTS.keys()))
|
|
31
|
+
def switch(environment):
|
|
32
|
+
config_manager.switch_environment(environment)
|
|
33
|
+
return config_manager.config
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@config.command()
|
|
37
|
+
def reset():
|
|
38
|
+
config_manager.reset()
|
|
39
|
+
config_manager.save()
|
|
40
|
+
return config_manager.config
|