mirror of
https://github.com/bellingcat/octosuite.git
synced 2026-06-07 19:08:36 +03:00
4.0/beta
This commit is contained in:
33
Makefile
Normal file
33
Makefile
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
.PHONY: help install dev run test lint format clean sync lock update
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Available commands:"
|
||||||
|
@echo " make install - Install dependencies"
|
||||||
|
@echo " make dev - Install dev dependencies"
|
||||||
|
@echo " make run - Run the application"
|
||||||
|
@echo " make format - Format code"
|
||||||
|
@echo " make sync - Sync dependencies with lock file"
|
||||||
|
@echo " make lock - Update lock file"
|
||||||
|
@echo " make update - Update dependencies"
|
||||||
|
|
||||||
|
install:
|
||||||
|
@uv pip install .
|
||||||
|
|
||||||
|
dev:
|
||||||
|
@uv pip install .[dev]
|
||||||
|
|
||||||
|
run: install
|
||||||
|
@uv run octosuite
|
||||||
|
|
||||||
|
format:
|
||||||
|
@uv run black .
|
||||||
|
|
||||||
|
sync:
|
||||||
|
@uv sync
|
||||||
|
|
||||||
|
lock:
|
||||||
|
@uv lock
|
||||||
|
|
||||||
|
update:
|
||||||
|
@uv lock --upgrade
|
||||||
|
@uv sync
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
import csv
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import typing as t
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pyfiglet
|
||||||
|
from prompt_toolkit.shortcuts import message_dialog
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.text import Text
|
||||||
|
from rich.tree import Tree
|
||||||
|
from update_checker import UpdateChecker
|
||||||
|
|
||||||
|
__pkg__ = "octosuite"
|
||||||
|
__version__ = "4.0.0"
|
||||||
|
__author__ = "Ritchie Mwewa"
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"console",
|
||||||
|
"__pkg__",
|
||||||
|
"__author__",
|
||||||
|
"__version__",
|
||||||
|
"preview_response",
|
||||||
|
"export_response",
|
||||||
|
"check_updates",
|
||||||
|
"clear_screen",
|
||||||
|
"text_banner",
|
||||||
|
"set_menu_title",
|
||||||
|
]
|
||||||
|
|
||||||
|
console = Console(log_time=False)
|
||||||
|
|
||||||
|
|
||||||
|
def preview_response(data: t.Union[dict, list], source: str, _type: str):
|
||||||
|
if isinstance(data, dict):
|
||||||
|
tree = Tree(
|
||||||
|
label=f"\n[bold]{data.get('name') or data.get('login') or data.get('id') or 'Data'}[/bold]",
|
||||||
|
guide_style="#444444",
|
||||||
|
highlight=True,
|
||||||
|
)
|
||||||
|
fill_tree(tree=tree, data=data)
|
||||||
|
console.print(tree)
|
||||||
|
print()
|
||||||
|
|
||||||
|
elif isinstance(data, list):
|
||||||
|
preview_data = data[:5]
|
||||||
|
tree = Tree(
|
||||||
|
label=f"\nPreview: First {len(preview_data)} of {len(data)} {_type} from {source}",
|
||||||
|
guide_style="#444444",
|
||||||
|
highlight=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
for item in preview_data:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
branch_label = (
|
||||||
|
item.get("full_name")
|
||||||
|
or item.get("name")
|
||||||
|
or item.get("login")
|
||||||
|
or item.get("id")
|
||||||
|
or "Item"
|
||||||
|
)
|
||||||
|
branch = tree.add(label=f"[bold]{branch_label}[/bold]", highlight=True)
|
||||||
|
fill_tree(tree=branch, data=item)
|
||||||
|
|
||||||
|
console.print(tree)
|
||||||
|
print()
|
||||||
|
else:
|
||||||
|
console.print(data)
|
||||||
|
|
||||||
|
|
||||||
|
def export_response(data, data_type, source, file_formats, output_dir="../exports"):
|
||||||
|
"""Export data to selected formats using built-in libraries."""
|
||||||
|
# Create output directory if it doesn't exist
|
||||||
|
output_dir = Path(output_dir)
|
||||||
|
output_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Generate filename with timestamp
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
safe_source = source.replace("/", "_")
|
||||||
|
filename = f"{safe_source}_{data_type}_{timestamp}"
|
||||||
|
|
||||||
|
# Normalize data to list of dicts
|
||||||
|
if isinstance(data, dict):
|
||||||
|
data_list = [data]
|
||||||
|
else:
|
||||||
|
data_list = data
|
||||||
|
|
||||||
|
# Export to all selected formats
|
||||||
|
exported_files = []
|
||||||
|
for file_format in file_formats:
|
||||||
|
filepath = output_dir / f"{filename}.{file_format}"
|
||||||
|
|
||||||
|
if file_format == "json":
|
||||||
|
with open(filepath, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data_list, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
elif file_format == "csv":
|
||||||
|
if data_list:
|
||||||
|
# Get all unique keys from all dicts
|
||||||
|
keys = set()
|
||||||
|
for item in data_list:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
keys.update(item.keys())
|
||||||
|
keys = sorted(keys)
|
||||||
|
|
||||||
|
with open(filepath, "w", newline="", encoding="utf-8") as f:
|
||||||
|
writer = csv.DictWriter(f, fieldnames=keys)
|
||||||
|
writer.writeheader()
|
||||||
|
for item in data_list:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
writer.writerow(item)
|
||||||
|
|
||||||
|
elif file_format == "html":
|
||||||
|
with open(filepath, "w", encoding="utf-8") as file:
|
||||||
|
file.write("<!DOCTYPE html>\n<html>\n<head>\n")
|
||||||
|
file.write('<meta charset="UTF-8">\n')
|
||||||
|
file.write("<style>table { border-collapse: collapse; width: 100%; }\n")
|
||||||
|
file.write(
|
||||||
|
"th, td { border: 1px solid black; padding: 8px; text-align: left; }\n"
|
||||||
|
)
|
||||||
|
file.write("th { background-color: #f2f2f2; }</style>\n")
|
||||||
|
file.write("</head>\n<body>\n")
|
||||||
|
file.write('<table class="table table-striped" border="1">\n')
|
||||||
|
|
||||||
|
if data_list:
|
||||||
|
# Get all unique keys
|
||||||
|
keys = set()
|
||||||
|
for item in data_list:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
keys.update(item.keys())
|
||||||
|
keys = sorted(keys)
|
||||||
|
|
||||||
|
# Write header
|
||||||
|
file.write("<thead>\n<tr>\n")
|
||||||
|
for key in keys:
|
||||||
|
file.write(f"<th>{key}</th>\n")
|
||||||
|
file.write("</tr>\n</thead>\n")
|
||||||
|
|
||||||
|
# Write rows
|
||||||
|
file.write("<tbody>\n")
|
||||||
|
for item in data_list:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
file.write("<tr>\n")
|
||||||
|
for key in keys:
|
||||||
|
value = item.get(key, "")
|
||||||
|
file.write(f"<td>{value}</td>\n")
|
||||||
|
file.write("</tr>\n")
|
||||||
|
file.write("</tbody>\n")
|
||||||
|
|
||||||
|
file.write("</table>\n</body>\n</html>")
|
||||||
|
|
||||||
|
exported_files.append(str(filepath))
|
||||||
|
|
||||||
|
# Show success message
|
||||||
|
console.print("\n[green]✓ Export successful![/green]")
|
||||||
|
for filepath in exported_files:
|
||||||
|
console.print(f" • {filepath}")
|
||||||
|
|
||||||
|
return exported_files
|
||||||
|
|
||||||
|
|
||||||
|
def fill_tree(tree: Tree, data: t.Union[dict, list]) -> Tree:
|
||||||
|
if isinstance(data, dict):
|
||||||
|
for key, value in data.items():
|
||||||
|
if isinstance(value, dict) or isinstance(value, list):
|
||||||
|
branch = tree.add(f"{key}")
|
||||||
|
fill_tree(tree=branch, data=value)
|
||||||
|
else:
|
||||||
|
tree.add(f"[dim]{key.replace('_', ' ').title()}[/dim]: {value}")
|
||||||
|
|
||||||
|
elif isinstance(data, list):
|
||||||
|
for item in data:
|
||||||
|
if isinstance(item, dict) or isinstance(item, list):
|
||||||
|
fill_tree(tree=tree, data=item)
|
||||||
|
else:
|
||||||
|
tree.add(str(item))
|
||||||
|
|
||||||
|
else:
|
||||||
|
tree.add(str(data))
|
||||||
|
|
||||||
|
return tree
|
||||||
|
|
||||||
|
|
||||||
|
def check_updates():
|
||||||
|
checker = UpdateChecker()
|
||||||
|
result = checker.check(__pkg__, __version__)
|
||||||
|
if result:
|
||||||
|
message_dialog(title="Update Available", text=result).run()
|
||||||
|
else:
|
||||||
|
message_dialog(
|
||||||
|
title="Up to Date",
|
||||||
|
text=f"You're running the current version, {__version__}",
|
||||||
|
).run()
|
||||||
|
|
||||||
|
|
||||||
|
def clear_screen():
|
||||||
|
"""Clear the terminal screen."""
|
||||||
|
subprocess.run(["cls" if os.name == "nt" else "clear"])
|
||||||
|
|
||||||
|
|
||||||
|
def text_banner(text: str):
|
||||||
|
clear_screen()
|
||||||
|
|
||||||
|
ascii_text = pyfiglet.figlet_format(text=text, font="chunky")
|
||||||
|
banner_text = Text(ascii_text)
|
||||||
|
|
||||||
|
length = len(ascii_text)
|
||||||
|
for i in range(length):
|
||||||
|
ratio = i / max(length - 1, 1)
|
||||||
|
r = int(255 - 127 * ratio)
|
||||||
|
g = int(200 - 200 * ratio)
|
||||||
|
b = 255
|
||||||
|
|
||||||
|
banner_text.stylize(f"rgb({r},{g},{b})", i, i + 1)
|
||||||
|
console.print(banner_text)
|
||||||
|
|
||||||
|
|
||||||
|
def set_menu_title(menu_type: t.Literal["home", "user", "org", "repo", "search"]):
|
||||||
|
title: str = __pkg__.title()
|
||||||
|
title += f" | {menu_type.title()}"
|
||||||
|
console.set_window_title(title=title)
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import re
|
|
||||||
import typing as t
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
BASE_URL = "https://api.github.com"
|
|
||||||
|
|
||||||
|
|
||||||
class Api:
|
|
||||||
def __init__(self): ...
|
|
||||||
|
|
||||||
def get(self, url: str, params: t.Optional[dict] = None) -> t.Union[dict, list]:
|
|
||||||
response = requests.get(url, params=params)
|
|
||||||
response.raise_for_status()
|
|
||||||
return self._sanitise_response(response=response.json())
|
|
||||||
|
|
||||||
def _sanitise_response(self, response: t.Union[dict, list]) -> t.Union[dict, list]:
|
|
||||||
pattern = re.compile(r"https://api\.github\.com")
|
|
||||||
|
|
||||||
if isinstance(response, list):
|
|
||||||
return [self._sanitise_response(response=item) for item in response]
|
|
||||||
|
|
||||||
if isinstance(response, dict):
|
|
||||||
keys_to_remove = [
|
|
||||||
key
|
|
||||||
for key, value in response.items()
|
|
||||||
if (isinstance(value, str) and pattern.search(value)) or value is None
|
|
||||||
]
|
|
||||||
for key in keys_to_remove:
|
|
||||||
response.pop(key)
|
|
||||||
|
|
||||||
# Recursively clean nested dicts/lists
|
|
||||||
for key, value in response.items():
|
|
||||||
if isinstance(value, (dict, list)):
|
|
||||||
response[key] = self._sanitise_response(response=value)
|
|
||||||
|
|
||||||
return response
|
|
||||||
77
octosuite/core/github.py
Normal file
77
octosuite/core/github.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import typing as t
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from requests import Response
|
||||||
|
|
||||||
|
from .. import __version__
|
||||||
|
|
||||||
|
BASE_URL = "https://api.github.com"
|
||||||
|
|
||||||
|
|
||||||
|
class GitHub:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
user_agent: str = (
|
||||||
|
f"octosuite/{__version__} "
|
||||||
|
f"(Python {sys.version.split()[0]}; "
|
||||||
|
f"https://github.com/bellingcat/octosuite) requests/{requests.__version__}"
|
||||||
|
),
|
||||||
|
):
|
||||||
|
self.user_agent = user_agent
|
||||||
|
|
||||||
|
def get(
|
||||||
|
self, url: str, params: t.Optional[dict] = None, return_response: bool = False
|
||||||
|
) -> t.Union[dict, list, Response]:
|
||||||
|
response = requests.get(
|
||||||
|
url=url, params=params, headers={"User-Agent": self.user_agent}
|
||||||
|
)
|
||||||
|
|
||||||
|
if return_response:
|
||||||
|
return response
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return self._sanitise_response(response=response.json())
|
||||||
|
return []
|
||||||
|
|
||||||
|
def is_valid_entity(
|
||||||
|
self, entity_type: t.Literal["user", "org", "repo"], **kwargs
|
||||||
|
) -> bool:
|
||||||
|
"""Validate if a GitHub entity exists."""
|
||||||
|
try:
|
||||||
|
if entity_type == "user":
|
||||||
|
url = f"https://api.github.com/users/{kwargs.get('username')}"
|
||||||
|
elif entity_type == "org":
|
||||||
|
url = f"https://api.github.com/orgs/{kwargs.get('username')}"
|
||||||
|
elif entity_type == "repo":
|
||||||
|
url = f"https://api.github.com/repos/{kwargs.get('repo_owner')}/{kwargs.get('repo_name')}"
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
response = self.get(url=url, return_response=True)
|
||||||
|
return response.status_code == 200
|
||||||
|
except requests.RequestException:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _sanitise_response(self, response: t.Union[dict, list]) -> t.Union[dict, list]:
|
||||||
|
pattern = re.compile(r"https://api\.github\.com")
|
||||||
|
|
||||||
|
if isinstance(response, list):
|
||||||
|
return [self._sanitise_response(response=item) for item in response]
|
||||||
|
|
||||||
|
if isinstance(response, dict):
|
||||||
|
keys_to_remove = [
|
||||||
|
key
|
||||||
|
for key, value in response.items()
|
||||||
|
if (isinstance(value, str) and pattern.search(value)) or value is None
|
||||||
|
]
|
||||||
|
for key in keys_to_remove:
|
||||||
|
response.pop(key)
|
||||||
|
|
||||||
|
# Recursively clean nested dicts/lists
|
||||||
|
for key, value in response.items():
|
||||||
|
if isinstance(value, (dict, list)):
|
||||||
|
response[key] = self._sanitise_response(response=value)
|
||||||
|
|
||||||
|
return response
|
||||||
@@ -1,187 +1,238 @@
|
|||||||
from .api import Api, BASE_URL
|
from requests import exceptions
|
||||||
|
|
||||||
|
from .github import GitHub, BASE_URL
|
||||||
|
|
||||||
|
github = GitHub()
|
||||||
|
|
||||||
|
__all__ = ["User", "Org", "Repo", "Search"]
|
||||||
|
|
||||||
|
|
||||||
api = Api()
|
class GitHubEntity:
|
||||||
|
"""Base class for GitHub entities with common functionality."""
|
||||||
|
|
||||||
|
def __init__(self, source: str):
|
||||||
|
self.endpoint = None
|
||||||
|
self.source = source
|
||||||
|
|
||||||
|
def exists(self) -> bool:
|
||||||
|
"""Check if the entity exists on GitHub."""
|
||||||
|
try:
|
||||||
|
response = github.get(url=self.endpoint, return_response=True)
|
||||||
|
return response.status_code == 200
|
||||||
|
except exceptions.RequestException as err:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class User:
|
class User(GitHubEntity):
|
||||||
def __init__(self, name: str):
|
def __init__(self, name: str):
|
||||||
|
super().__init__(source=name)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.endpoint = f"{BASE_URL}/users/{name}"
|
self.endpoint = f"{BASE_URL}/users/{name}"
|
||||||
|
|
||||||
def profile(self) -> dict:
|
def profile(self) -> dict:
|
||||||
profile = api.get(url=self.endpoint)
|
profile = github.get(url=self.endpoint)
|
||||||
return profile
|
return profile
|
||||||
|
|
||||||
def repos(self, page: int, per_page: int) -> list:
|
def repos(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
repos = api.get(url=f"{self.endpoint}/repos", params=params)
|
repos = github.get(url=f"{self.endpoint}/repos", params=params)
|
||||||
return repos
|
return repos
|
||||||
|
|
||||||
def subscriptions(self, page: int, per_page: int) -> list:
|
def subscriptions(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
subscriptions = api.get(url=f"{self.endpoint}/subscriptions", params=params)
|
subscriptions = github.get(url=f"{self.endpoint}/subscriptions", params=params)
|
||||||
return subscriptions
|
return subscriptions
|
||||||
|
|
||||||
def starred(self, page: int, per_page: int) -> list:
|
def starred(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
starred = api.get(url=f"{self.endpoint}/starred", params=params)
|
starred = github.get(url=f"{self.endpoint}/starred", params=params)
|
||||||
return starred
|
return starred
|
||||||
|
|
||||||
def followers(self, page: int, per_page: int) -> list:
|
def followers(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
users = api.get(url=f"{self.endpoint}/followers", params=params)
|
users = github.get(url=f"{self.endpoint}/followers", params=params)
|
||||||
return users
|
return users
|
||||||
|
|
||||||
def following(self, page: int, per_page: int) -> list:
|
def following(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
users = api.get(url=f"{self.endpoint}/following", params=params)
|
users = github.get(url=f"{self.endpoint}/following", params=params)
|
||||||
return users
|
return users
|
||||||
|
|
||||||
def follows(self, user: str) -> bool: ...
|
def follows(self, user: str) -> bool: ...
|
||||||
|
|
||||||
def orgs(self, page: int, per_page: int) -> list:
|
def orgs(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
orgs = api.get(url=f"{self.endpoint}/orgs", params=params)
|
orgs = github.get(url=f"{self.endpoint}/orgs", params=params)
|
||||||
return orgs
|
return orgs
|
||||||
|
|
||||||
def gists(self, page: int, per_page: int) -> list:
|
def gists(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
gists = api.get(url=f"{self.endpoint}/gists", params=params)
|
gists = github.get(url=f"{self.endpoint}/gists", params=params)
|
||||||
return gists
|
return gists
|
||||||
|
|
||||||
def events(self, page: int, per_page: int) -> list:
|
def events(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
events = api.get(url=f"{self.endpoint}/events", params=params)
|
events = github.get(url=f"{self.endpoint}/events", params=params)
|
||||||
return events
|
return events
|
||||||
|
|
||||||
def received_events(self, page: int, per_page: int) -> list:
|
def received_events(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
received_events = api.get(url=f"{self.endpoint}/received_events", params=params)
|
received_events = github.get(
|
||||||
|
url=f"{self.endpoint}/received_events", params=params
|
||||||
|
)
|
||||||
return received_events
|
return received_events
|
||||||
|
|
||||||
|
|
||||||
class Org:
|
class Org(GitHubEntity):
|
||||||
def __init__(self, name: str):
|
def __init__(self, name: str):
|
||||||
|
super().__init__(source=name)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.endpoint = f"{BASE_URL}/orgs/{name}"
|
self.endpoint = f"{BASE_URL}/orgs/{name}"
|
||||||
|
|
||||||
def profile(self) -> dict:
|
def profile(self) -> dict:
|
||||||
profile = api.get(url=self.endpoint)
|
profile = github.get(url=self.endpoint)
|
||||||
return profile
|
return profile
|
||||||
|
|
||||||
def repos(self, page: int, per_page: int) -> list:
|
def repos(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
repos = api.get(url=f"{self.endpoint}/repos", params=params)
|
repos = github.get(url=f"{self.endpoint}/repos", params=params)
|
||||||
return repos
|
return repos
|
||||||
|
|
||||||
def events(self, page: int, per_page: int) -> list:
|
def events(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
events = api.get(url=f"{self.endpoint}/events", params=params)
|
events = github.get(url=f"{self.endpoint}/events", params=params)
|
||||||
return events
|
return events
|
||||||
|
|
||||||
def hooks(self, page: int, per_page: int) -> list:
|
def hooks(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
hooks = api.get(url=f"{self.endpoint}/hooks", params=params)
|
hooks = github.get(url=f"{self.endpoint}/hooks", params=params)
|
||||||
return hooks
|
return hooks
|
||||||
|
|
||||||
def issues(self, page: int, per_page: int) -> list:
|
def issues(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
issues = api.get(url=f"{self.endpoint}/issues", params=params)
|
issues = github.get(url=f"{self.endpoint}/issues", params=params)
|
||||||
return issues
|
return issues
|
||||||
|
|
||||||
def members(self, page: int, per_page: int) -> list:
|
def members(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
members = api.get(url=f"{self.endpoint}/members", params=params)
|
members = github.get(url=f"{self.endpoint}/members", params=params)
|
||||||
return members
|
return members
|
||||||
|
|
||||||
|
|
||||||
class Repo:
|
class Repo(GitHubEntity):
|
||||||
def __init__(self, name: str, owner: str):
|
def __init__(self, name: str, owner: str):
|
||||||
|
super().__init__(source=f"{owner}/{name}")
|
||||||
self.name = name
|
self.name = name
|
||||||
self.owner = owner
|
self.owner = owner
|
||||||
self.endpoint = f"{BASE_URL}/repos/{owner}/{name}"
|
self.endpoint = f"{BASE_URL}/repos/{owner}/{name}"
|
||||||
|
|
||||||
def profile(self) -> dict:
|
def profile(self) -> dict:
|
||||||
profile = api.get(url=self.endpoint)
|
profile = github.get(url=self.endpoint)
|
||||||
return profile
|
return profile
|
||||||
|
|
||||||
def forks(self, page: int, per_page: int) -> list:
|
def forks(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
forks = api.get(url=f"{self.endpoint}/forks", params=params)
|
forks = github.get(url=f"{self.endpoint}/forks", params=params)
|
||||||
return forks
|
return forks
|
||||||
|
|
||||||
def issue_events(self, page: int, per_page: int) -> list:
|
def issue_events(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
issue_events = api.get(url=f"{self.endpoint}/issue_events", params=params)
|
issue_events = github.get(url=f"{self.endpoint}/issue_events", params=params)
|
||||||
return issue_events
|
return issue_events
|
||||||
|
|
||||||
def events(self, page: int, per_page: int) -> list:
|
def events(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
events = api.get(url=f"{self.endpoint}/events", params=params)
|
events = github.get(url=f"{self.endpoint}/events", params=params)
|
||||||
return events
|
return events
|
||||||
|
|
||||||
def assignees(self, page: int, per_page: int) -> list:
|
def assignees(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
assignees = api.get(url=f"{self.endpoint}/assignees", params=params)
|
assignees = github.get(url=f"{self.endpoint}/assignees", params=params)
|
||||||
return assignees
|
return assignees
|
||||||
|
|
||||||
def branches(self, page: int, per_page: int) -> list:
|
def branches(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
branches = api.get(url=f"{self.endpoint}/branches", params=params)
|
branches = github.get(url=f"{self.endpoint}/branches", params=params)
|
||||||
return branches
|
return branches
|
||||||
|
|
||||||
def tags(self, page: int, per_page: int) -> list:
|
def tags(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
tags = api.get(url=f"{self.endpoint}/tags", params=params)
|
tags = github.get(url=f"{self.endpoint}/tags", params=params)
|
||||||
return tags
|
return tags
|
||||||
|
|
||||||
def languages(self) -> dict:
|
def languages(self) -> dict:
|
||||||
languages = api.get(url=f"{self.endpoint}/languages")
|
languages = github.get(url=f"{self.endpoint}/languages")
|
||||||
return languages
|
return languages
|
||||||
|
|
||||||
def stargazers(self, page: int, per_page: int) -> list:
|
def stargazers(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
stargazers = api.get(url=f"{self.endpoint}/stargazers", params=params)
|
stargazers = github.get(url=f"{self.endpoint}/stargazers", params=params)
|
||||||
return stargazers
|
return stargazers
|
||||||
|
|
||||||
def subscribers(self, page: int, per_page: int) -> list:
|
def subscribers(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
subscribers = api.get(url=f"{self.endpoint}/subscribers", params=params)
|
subscribers = github.get(url=f"{self.endpoint}/subscribers", params=params)
|
||||||
return subscribers
|
return subscribers
|
||||||
|
|
||||||
def commits(self, page: int, per_page: int) -> list:
|
def commits(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
commits = api.get(url=f"{self.endpoint}/commits", params=params)
|
commits = github.get(url=f"{self.endpoint}/commits", params=params)
|
||||||
return commits
|
return commits
|
||||||
|
|
||||||
def comments(self, page: int, per_page: int) -> list:
|
def comments(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
comments = api.get(url=f"{self.endpoint}/comments", params=params)
|
comments = github.get(url=f"{self.endpoint}/comments", params=params)
|
||||||
return comments
|
return comments
|
||||||
|
|
||||||
def contents(self, path: str) -> list:
|
def contents(self, path: str) -> list:
|
||||||
contents = api.get(url=f"{self.endpoint}/contents/{path}")
|
contents = github.get(url=f"{self.endpoint}/contents/{path}")
|
||||||
return contents
|
return contents
|
||||||
|
|
||||||
def issues(self, page: int, per_page: int) -> list:
|
def issues(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
issues = api.get(url=f"{self.endpoint}/issues", params=params)
|
issues = github.get(url=f"{self.endpoint}/issues", params=params)
|
||||||
return issues
|
return issues
|
||||||
|
|
||||||
def releases(self, page: int, per_page: int) -> list:
|
def releases(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
releases = api.get(url=f"{self.endpoint}/releases", params=params)
|
releases = github.get(url=f"{self.endpoint}/releases", params=params)
|
||||||
return releases
|
return releases
|
||||||
|
|
||||||
def deployments(self, page: int, per_page: int) -> list:
|
def deployments(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
deployments = api.get(url=f"{self.endpoint}/deployments", params=params)
|
deployments = github.get(url=f"{self.endpoint}/deployments", params=params)
|
||||||
return deployments
|
return deployments
|
||||||
|
|
||||||
def labels(self, page: int, per_page: int) -> list:
|
def labels(self, page: int, per_page: int) -> list:
|
||||||
params = {"page": page, "per_page": per_page}
|
params = {"page": page, "per_page": per_page}
|
||||||
labels = api.get(url=f"{self.endpoint}/labels", params=params)
|
labels = github.get(url=f"{self.endpoint}/labels", params=params)
|
||||||
return labels
|
return labels
|
||||||
|
|
||||||
|
|
||||||
|
class Search:
|
||||||
|
def __init__(self, query: str, page: int, per_page: int):
|
||||||
|
self.query = query
|
||||||
|
self.page = page
|
||||||
|
self.per_page = per_page
|
||||||
|
self.endpoint = f"{BASE_URL}/search"
|
||||||
|
self.params = {"q": query, "page": page, "per_page": per_page}
|
||||||
|
|
||||||
|
def repos(self) -> list:
|
||||||
|
repos = github.get(url=f"{self.endpoint}/repositories", params=self.params)
|
||||||
|
return repos
|
||||||
|
|
||||||
|
def users(self) -> list:
|
||||||
|
users = github.get(url=f"{self.endpoint}/users", params=self.params)
|
||||||
|
return users
|
||||||
|
|
||||||
|
def commits(self) -> list:
|
||||||
|
commits = github.get(url=f"{self.endpoint}/commits", params=self.params)
|
||||||
|
return commits
|
||||||
|
|
||||||
|
def issues(self) -> list:
|
||||||
|
issues = github.get(url=f"{self.endpoint}/issues", params=self.params)
|
||||||
|
return issues
|
||||||
|
|
||||||
|
def topics(self) -> list:
|
||||||
|
topics = github.get(url=f"{self.endpoint}/topics", params=self.params)
|
||||||
|
return topics
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from . import console, __pkg__, __version__
|
||||||
from .tui.menus import Menus
|
from .tui.menus import Menus
|
||||||
from .tui.prompts import Prompts
|
|
||||||
|
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
try:
|
try:
|
||||||
prompts = Prompts()
|
console.set_window_title(title=f"{__pkg__.title()} v{__version__}")
|
||||||
menu = Menus(prompts=prompts)
|
menu = Menus()
|
||||||
menu.main()
|
menu.main()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|||||||
44
octosuite/tui/dialogs.py
Normal file
44
octosuite/tui/dialogs.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from prompt_toolkit.shortcuts import button_dialog, message_dialog
|
||||||
|
|
||||||
|
LICENSE_NOTICE = f"""Copyright (c) {datetime.now().year} Bellingcat
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE."""
|
||||||
|
|
||||||
|
|
||||||
|
class Dialogs:
|
||||||
|
def __init__(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def quit() -> bool:
|
||||||
|
try:
|
||||||
|
result = button_dialog(
|
||||||
|
title="Quit",
|
||||||
|
text="This will close the session, continue?",
|
||||||
|
buttons=[("Yes", True), ("No", False)],
|
||||||
|
).run()
|
||||||
|
return result if result is not None else False
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def license():
|
||||||
|
message_dialog(title="MIT License", text=LICENSE_NOTICE).run()
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
import questionary as q
|
import questionary as q
|
||||||
from prompt_toolkit.styles import Style
|
from questionary import Style
|
||||||
|
|
||||||
|
|
||||||
class Prompts:
|
class Prompts:
|
||||||
@@ -9,21 +9,29 @@ class Prompts:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def prompt(message: str, instruction: t.Optional[str] = None) -> str:
|
def prompt(
|
||||||
|
message: str,
|
||||||
|
instruction: t.Optional[str] = None,
|
||||||
|
style: t.Optional[Style] = None,
|
||||||
|
qmark: t.Optional[str] = "?",
|
||||||
|
) -> str:
|
||||||
return q.text(
|
return q.text(
|
||||||
message=message,
|
message=message,
|
||||||
instruction=instruction,
|
instruction=instruction,
|
||||||
|
style=style,
|
||||||
|
qmark=qmark,
|
||||||
|
validate=lambda text: len(text.strip()) > 0 or "Input cannot be empty",
|
||||||
).ask()
|
).ask()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def pagination_params() -> dict:
|
def pagination_params() -> dict:
|
||||||
"""Prompt user for pagination parameters."""
|
"""Prompt user for pagination parameters."""
|
||||||
try:
|
try:
|
||||||
page = q.text(message="Page", instruction="defaults to 1", qmark="#").ask()
|
page = q.text(message="Page", default="1", qmark="n").ask()
|
||||||
per_page = q.text(
|
per_page = q.text(
|
||||||
message="Per Page",
|
message="Per Page",
|
||||||
instruction="default and max is 100",
|
default="100",
|
||||||
qmark="#",
|
qmark="n",
|
||||||
).ask()
|
).ask()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -34,21 +42,3 @@ class Prompts:
|
|||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
print("Invalid input, using defaults (page=1, per_page=100)")
|
print("Invalid input, using defaults (page=1, per_page=100)")
|
||||||
return {"page": 1, "per_page": 100}
|
return {"page": 1, "per_page": 100}
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def quit() -> bool:
|
|
||||||
try:
|
|
||||||
if q.confirm(
|
|
||||||
"This will close the session, continue?",
|
|
||||||
default=True,
|
|
||||||
style=Style(
|
|
||||||
[
|
|
||||||
("qmark", "fg:red bold"),
|
|
||||||
("question", "fg:red bold"),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
).ask():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
return True
|
|
||||||
|
|||||||
@@ -10,10 +10,9 @@ authors = [
|
|||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rich>=14.2.0",
|
"rich>=14.2.0",
|
||||||
"requests>=2.32.5",
|
|
||||||
"questionary>=2.1.1",
|
"questionary>=2.1.1",
|
||||||
"pyfiglet>=1.0.4",
|
"pyfiglet>=1.0.4",
|
||||||
"pandas>=2.3.3"
|
"update-checker>=0.18.0"
|
||||||
]
|
]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
@@ -28,5 +27,10 @@ homepage = "https://bellingcat.com"
|
|||||||
issues = "https://github.com/bellingcat/octosuite/issues"
|
issues = "https://github.com/bellingcat/octosuite/issues"
|
||||||
repository = "https://github.com/bellingcat/octosuite"
|
repository = "https://github.com/bellingcat/octosuite"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"black>=25.12.0",
|
||||||
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
octosuite = "octosuite.main:start"
|
octosuite = "octosuite.main:start"
|
||||||
197
uv.lock
generated
197
uv.lock
generated
@@ -1,6 +1,33 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 2
|
revision = 2
|
||||||
requires-python = ">=3.14"
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "black"
|
||||||
|
version = "25.12.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "click" },
|
||||||
|
{ name = "mypy-extensions" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "pathspec" },
|
||||||
|
{ name = "platformdirs" },
|
||||||
|
{ name = "pytokens" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c4/d9/07b458a3f1c525ac392b5edc6b191ff140b596f9d77092429417a54e249d/black-25.12.0.tar.gz", hash = "sha256:8d3dd9cea14bff7ddc0eb243c811cdb1a011ebb4800a5f0335a01a68654796a7", size = 659264, upload-time = "2025-12-08T01:40:52.501Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/52/c551e36bc95495d2aa1a37d50566267aa47608c81a53f91daa809e03293f/black-25.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a05ddeb656534c3e27a05a29196c962877c83fa5503db89e68857d1161ad08a5", size = 1923809, upload-time = "2025-12-08T01:46:55.126Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/f7/aac9b014140ee56d247e707af8db0aae2e9efc28d4a8aba92d0abd7ae9d1/black-25.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ec77439ef3e34896995503865a85732c94396edcc739f302c5673a2315e1e7f", size = 1742384, upload-time = "2025-12-08T01:49:37.022Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/98/38aaa018b2ab06a863974c12b14a6266badc192b20603a81b738c47e902e/black-25.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e509c858adf63aa61d908061b52e580c40eae0dfa72415fa47ac01b12e29baf", size = 1798761, upload-time = "2025-12-08T01:46:05.386Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/3a/a8ac542125f61574a3f015b521ca83b47321ed19bb63fe6d7560f348bfe1/black-25.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:252678f07f5bac4ff0d0e9b261fbb029fa530cfa206d0a636a34ab445ef8ca9d", size = 1429180, upload-time = "2025-12-08T01:45:34.903Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/2d/bdc466a3db9145e946762d52cd55b1385509d9f9004fec1c97bdc8debbfb/black-25.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bc5b1c09fe3c931ddd20ee548511c64ebf964ada7e6f0763d443947fd1c603ce", size = 1239350, upload-time = "2025-12-08T01:46:09.458Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/46/1d8f2542210c502e2ae1060b2e09e47af6a5e5963cb78e22ec1a11170b28/black-25.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0a0953b134f9335c2434864a643c842c44fba562155c738a2a37a4d61f00cad5", size = 1917015, upload-time = "2025-12-08T01:53:27.987Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/37/68accadf977672beb8e2c64e080f568c74159c1aaa6414b4cd2aef2d7906/black-25.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2355bbb6c3b76062870942d8cc450d4f8ac71f9c93c40122762c8784df49543f", size = 1741830, upload-time = "2025-12-08T01:54:36.861Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/76/03608a9d8f0faad47a3af3a3c8c53af3367f6c0dd2d23a84710456c7ac56/black-25.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9678bd991cc793e81d19aeeae57966ee02909877cb65838ccffef24c3ebac08f", size = 1791450, upload-time = "2025-12-08T01:44:52.581Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/99/b2a4bd7dfaea7964974f947e1c76d6886d65fe5d24f687df2d85406b2609/black-25.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:97596189949a8aad13ad12fcbb4ae89330039b96ad6742e6f6b45e75ad5cfd83", size = 1452042, upload-time = "2025-12-08T01:46:13.188Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/7c/d9825de75ae5dd7795d007681b752275ea85a1c5d83269b4b9c754c2aaab/black-25.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:778285d9ea197f34704e3791ea9404cd6d07595745907dd2ce3da7a13627b29b", size = 1267446, upload-time = "2025-12-08T01:46:14.497Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/11/21331aed19145a952ad28fca2756a1433ee9308079bd03bd898e903a2e53/black-25.12.0-py3-none-any.whl", hash = "sha256:48ceb36c16dbc84062740049eef990bb2ce07598272e673c17d1a7720c71c828", size = 206191, upload-time = "2025-12-08T01:40:50.963Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
@@ -17,6 +44,22 @@ version = "3.4.4"
|
|||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
|
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
|
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
|
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
|
||||||
@@ -36,6 +79,27 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
|
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.11"
|
version = "3.11"
|
||||||
@@ -67,80 +131,65 @@ wheels = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "numpy"
|
name = "mypy-extensions"
|
||||||
version = "2.4.0"
|
version = "1.1.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a4/7a/6a3d14e205d292b738db449d0de649b373a59edb0d0b4493821d0a3e8718/numpy-2.4.0.tar.gz", hash = "sha256:6e504f7b16118198f138ef31ba24d985b124c2c469fe8467007cf30fd992f934", size = 20685720, upload-time = "2025-12-20T16:18:19.023Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/ab/ed/52eac27de39d5e5a6c9aadabe672bc06f55e24a3d9010cd1183948055d76/numpy-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c95eb6db2884917d86cde0b4d4cf31adf485c8ec36bf8696dd66fa70de96f36b", size = 16647476, upload-time = "2025-12-20T16:17:17.671Z" },
|
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/77/c0/990ce1b7fcd4e09aeaa574e2a0a839589e4b08b2ca68070f1acb1fea6736/numpy-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:65167da969cd1ec3a1df31cb221ca3a19a8aaa25370ecb17d428415e93c1935e", size = 12374563, upload-time = "2025-12-20T16:17:20.216Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/37/7c/8c5e389c6ae8f5fd2277a988600d79e9625db3fff011a2d87ac80b881a4c/numpy-2.4.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3de19cfecd1465d0dcf8a5b5ea8b3155b42ed0b639dba4b71e323d74f2a3be5e", size = 5203107, upload-time = "2025-12-20T16:17:22.47Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e6/94/ca5b3bd6a8a70a5eec9a0b8dd7f980c1eff4b8a54970a9a7fef248ef564f/numpy-2.4.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6c05483c3136ac4c91b4e81903cb53a8707d316f488124d0398499a4f8e8ef51", size = 6538067, upload-time = "2025-12-20T16:17:24.001Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/79/43/993eb7bb5be6761dde2b3a3a594d689cec83398e3f58f4758010f3b85727/numpy-2.4.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36667db4d6c1cea79c8930ab72fadfb4060feb4bfe724141cd4bd064d2e5f8ce", size = 14411926, upload-time = "2025-12-20T16:17:25.822Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/03/75/d4c43b61de473912496317a854dac54f1efec3eeb158438da6884b70bb90/numpy-2.4.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a818668b674047fd88c4cddada7ab8f1c298812783e8328e956b78dc4807f9f", size = 16354295, upload-time = "2025-12-20T16:17:28.308Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b8/0a/b54615b47ee8736a6461a4bb6749128dd3435c5a759d5663f11f0e9af4ac/numpy-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ee32359fb7543b7b7bd0b2f46294db27e29e7bbdf70541e81b190836cd83ded", size = 16190242, upload-time = "2025-12-20T16:17:30.993Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/98/ce/ea207769aacad6246525ec6c6bbd66a2bf56c72443dc10e2f90feed29290/numpy-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e493962256a38f58283de033d8af176c5c91c084ea30f15834f7545451c42059", size = 18280875, upload-time = "2025-12-20T16:17:33.327Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/17/ef/ec409437aa962ea372ed601c519a2b141701683ff028f894b7466f0ab42b/numpy-2.4.0-cp314-cp314-win32.whl", hash = "sha256:6bbaebf0d11567fa8926215ae731e1d58e6ec28a8a25235b8a47405d301332db", size = 6002530, upload-time = "2025-12-20T16:17:35.729Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5f/4a/5cb94c787a3ed1ac65e1271b968686521169a7b3ec0b6544bb3ca32960b0/numpy-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d857f55e7fdf7c38ab96c4558c95b97d1c685be6b05c249f5fdafcbd6f9899e", size = 12435890, upload-time = "2025-12-20T16:17:37.599Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/48/a0/04b89db963af9de1104975e2544f30de89adbf75b9e75f7dd2599be12c79/numpy-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:bb50ce5fb202a26fd5404620e7ef820ad1ab3558b444cb0b55beb7ef66cd2d63", size = 10591892, upload-time = "2025-12-20T16:17:39.649Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/53/e5/d74b5ccf6712c06c7a545025a6a71bfa03bdc7e0568b405b0d655232fd92/numpy-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:355354388cba60f2132df297e2d53053d4063f79077b67b481d21276d61fc4df", size = 12494312, upload-time = "2025-12-20T16:17:41.714Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/08/3ca9cc2ddf54dfee7ae9a6479c071092a228c68aef08252aa08dac2af002/numpy-2.4.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:1d8f9fde5f6dc1b6fc34df8162f3b3079365468703fee7f31d4e0cc8c63baed9", size = 5322862, upload-time = "2025-12-20T16:17:44.145Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/87/74/0bb63a68394c0c1e52670cfff2e309afa41edbe11b3327d9af29e4383f34/numpy-2.4.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e0434aa22c821f44eeb4c650b81c7fbdd8c0122c6c4b5a576a76d5a35625ecd9", size = 6644986, upload-time = "2025-12-20T16:17:46.203Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/06/8f/9264d9bdbcf8236af2823623fe2f3981d740fc3461e2787e231d97c38c28/numpy-2.4.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40483b2f2d3ba7aad426443767ff5632ec3156ef09742b96913787d13c336471", size = 14457958, upload-time = "2025-12-20T16:17:48.017Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8c/d9/f9a69ae564bbc7236a35aa883319364ef5fd41f72aa320cc1cbe66148fe2/numpy-2.4.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6a7664ddd9746e20b7325351fe1a8408d0a2bf9c63b5e898290ddc8f09544", size = 16398394, upload-time = "2025-12-20T16:17:50.409Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/34/c7/39241501408dde7f885d241a98caba5421061a2c6d2b2197ac5e3aa842d8/numpy-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ecb0019d44f4cdb50b676c5d0cb4b1eae8e15d1ed3d3e6639f986fc92b2ec52c", size = 16241044, upload-time = "2025-12-20T16:17:52.661Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/95/cae7effd90e065a95e59fe710eeee05d7328ed169776dfdd9f789e032125/numpy-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d0ffd9e2e4441c96a9c91ec1783285d80bf835b677853fc2770a89d50c1e48ac", size = 18321772, upload-time = "2025-12-20T16:17:54.947Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/96/df/3c6c279accd2bfb968a76298e5b276310bd55d243df4fa8ac5816d79347d/numpy-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:77f0d13fa87036d7553bf81f0e1fe3ce68d14c9976c9851744e4d3e91127e95f", size = 6148320, upload-time = "2025-12-20T16:17:57.249Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/92/8d/f23033cce252e7a75cae853d17f582e86534c46404dea1c8ee094a9d6d84/numpy-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b1f5b45829ac1848893f0ddf5cb326110604d6df96cdc255b0bf9edd154104d4", size = 12623460, upload-time = "2025-12-20T16:17:58.963Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/4f/1f8475907d1a7c4ef9020edf7f39ea2422ec896849245f00688e4b268a71/numpy-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:23a3e9d1a6f360267e8fbb38ba5db355a6a7e9be71d7fce7ab3125e88bb646c8", size = 10661799, upload-time = "2025-12-20T16:18:01.078Z" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "octosuite"
|
name = "octosuite"
|
||||||
version = "4.0.0"
|
version = "4.0.0"
|
||||||
source = { editable = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "pandas" },
|
|
||||||
{ name = "pyfiglet" },
|
{ name = "pyfiglet" },
|
||||||
{ name = "questionary" },
|
{ name = "questionary" },
|
||||||
{ name = "requests" },
|
|
||||||
{ name = "rich" },
|
{ name = "rich" },
|
||||||
|
{ name = "update-checker" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "black" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "pandas", specifier = ">=2.3.3,<3.0.0" },
|
{ name = "black", marker = "extra == 'dev'", specifier = ">=25.12.0" },
|
||||||
{ name = "pyfiglet", specifier = ">=1.0.4,<2.0.0" },
|
{ name = "pyfiglet", specifier = ">=1.0.4" },
|
||||||
{ name = "questionary", specifier = ">=2.1.1,<3.0.0" },
|
{ name = "questionary", specifier = ">=2.1.1" },
|
||||||
{ name = "requests", specifier = ">=2.32.5,<3.0.0" },
|
{ name = "rich", specifier = ">=14.2.0" },
|
||||||
{ name = "rich", specifier = ">=14.2.0,<15.0.0" },
|
{ name = "update-checker", specifier = ">=0.18.0" },
|
||||||
|
]
|
||||||
|
provides-extras = ["dev"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "25.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pandas"
|
name = "pathspec"
|
||||||
version = "2.3.3"
|
version = "0.12.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
|
||||||
{ name = "numpy" },
|
|
||||||
{ name = "python-dateutil" },
|
|
||||||
{ name = "pytz" },
|
|
||||||
{ name = "tzdata" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" }
|
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" },
|
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" },
|
]
|
||||||
{ url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" },
|
[[package]]
|
||||||
{ url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" },
|
name = "platformdirs"
|
||||||
{ url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" },
|
version = "4.5.1"
|
||||||
{ url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" },
|
source = { registry = "https://pypi.org/simple" }
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" },
|
sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" }
|
||||||
{ url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" },
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" },
|
{ url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -174,24 +223,12 @@ wheels = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dateutil"
|
name = "pytokens"
|
||||||
version = "2.9.0.post0"
|
version = "0.3.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
sdist = { url = "https://files.pythonhosted.org/packages/4e/8d/a762be14dae1c3bf280202ba3172020b2b0b4c537f94427435f19c413b72/pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", size = 17644, upload-time = "2025-11-05T13:36:35.34Z" }
|
||||||
{ name = "six" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
|
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
{ url = "https://files.pythonhosted.org/packages/84/25/d9db8be44e205a124f6c98bc0324b2bb149b7431c53877fc6d1038dddaf5/pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3", size = 12195, upload-time = "2025-11-05T13:36:33.183Z" },
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pytz"
|
|
||||||
version = "2025.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -235,21 +272,15 @@ wheels = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "six"
|
name = "update-checker"
|
||||||
version = "1.17.0"
|
version = "0.18.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
dependencies = [
|
||||||
wheels = [
|
{ name = "requests" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
|
||||||
]
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5c/0b/1bec4a6cc60d33ce93d11a7bcf1aeffc7ad0aa114986073411be31395c6f/update_checker-0.18.0.tar.gz", hash = "sha256:6a2d45bb4ac585884a6b03f9eade9161cedd9e8111545141e9aa9058932acb13", size = 6699, upload-time = "2020-08-04T07:08:50.429Z" }
|
||||||
[[package]]
|
|
||||||
name = "tzdata"
|
|
||||||
version = "2025.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" }
|
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
|
{ url = "https://files.pythonhosted.org/packages/0c/ba/8dd7fa5f0b1c6a8ac62f8f57f7e794160c1f86f31c6d0fb00f582372a3e4/update_checker-0.18.0-py3-none-any.whl", hash = "sha256:cbba64760a36fe2640d80d85306e8fe82b6816659190993b7bdabadee4d4bbfd", size = 7008, upload-time = "2020-08-04T07:08:49.51Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user