atomicshop 2.15.11__py3-none-any.whl → 3.10.5__py3-none-any.whl
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.
- atomicshop/__init__.py +1 -1
- atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
- atomicshop/a_mains/dns_gateway_setting.py +11 -0
- atomicshop/a_mains/get_local_tcp_ports.py +85 -0
- atomicshop/a_mains/github_wrapper.py +11 -0
- atomicshop/a_mains/install_ca_certificate.py +172 -0
- atomicshop/a_mains/process_from_port.py +119 -0
- atomicshop/a_mains/set_default_dns_gateway.py +90 -0
- atomicshop/a_mains/update_config_toml.py +38 -0
- atomicshop/basics/ansi_escape_codes.py +3 -1
- atomicshop/basics/argparse_template.py +2 -0
- atomicshop/basics/booleans.py +27 -30
- atomicshop/basics/bytes_arrays.py +43 -0
- atomicshop/basics/classes.py +149 -1
- atomicshop/basics/enums.py +2 -2
- atomicshop/basics/exceptions.py +5 -1
- atomicshop/basics/list_of_classes.py +29 -0
- atomicshop/basics/multiprocesses.py +374 -50
- atomicshop/basics/strings.py +72 -3
- atomicshop/basics/threads.py +14 -0
- atomicshop/basics/tracebacks.py +13 -3
- atomicshop/certificates.py +153 -52
- atomicshop/config_init.py +11 -6
- atomicshop/console_user_response.py +7 -14
- atomicshop/consoles.py +9 -0
- atomicshop/datetimes.py +1 -1
- atomicshop/diff_check.py +3 -3
- atomicshop/dns.py +128 -3
- atomicshop/etws/_pywintrace_fix.py +17 -0
- atomicshop/etws/trace.py +40 -42
- atomicshop/etws/traces/trace_dns.py +56 -44
- atomicshop/etws/traces/trace_tcp.py +130 -0
- atomicshop/file_io/csvs.py +27 -5
- atomicshop/file_io/docxs.py +34 -17
- atomicshop/file_io/file_io.py +31 -17
- atomicshop/file_io/jsons.py +49 -0
- atomicshop/file_io/tomls.py +139 -0
- atomicshop/filesystem.py +616 -291
- atomicshop/get_process_list.py +3 -3
- atomicshop/http_parse.py +149 -93
- atomicshop/ip_addresses.py +6 -1
- atomicshop/mitm/centered_settings.py +132 -0
- atomicshop/mitm/config_static.py +207 -0
- atomicshop/mitm/config_toml_editor.py +55 -0
- atomicshop/mitm/connection_thread_worker.py +875 -357
- atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
- atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
- atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
- atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
- atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
- atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
- atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
- atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
- atomicshop/mitm/engines/create_module_template.py +58 -14
- atomicshop/mitm/import_config.py +359 -139
- atomicshop/mitm/initialize_engines.py +160 -80
- atomicshop/mitm/message.py +64 -23
- atomicshop/mitm/mitm_main.py +892 -0
- atomicshop/mitm/recs_files.py +183 -0
- atomicshop/mitm/shared_functions.py +4 -10
- atomicshop/mitm/ssh_tester.py +82 -0
- atomicshop/mitm/statistic_analyzer.py +136 -40
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
- atomicshop/monitor/checks/dns.py +1 -1
- atomicshop/networks.py +671 -0
- atomicshop/on_exit.py +39 -9
- atomicshop/package_mains_processor.py +84 -0
- atomicshop/permissions/permissions.py +22 -0
- atomicshop/permissions/ubuntu_permissions.py +239 -0
- atomicshop/permissions/win_permissions.py +33 -0
- atomicshop/print_api.py +24 -42
- atomicshop/process.py +24 -6
- atomicshop/process_poller/process_pool.py +0 -1
- atomicshop/process_poller/simple_process_pool.py +204 -5
- atomicshop/python_file_patcher.py +1 -1
- atomicshop/python_functions.py +27 -75
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +158 -172
- atomicshop/system_resource_monitor.py +61 -47
- atomicshop/system_resources.py +8 -8
- atomicshop/tempfiles.py +1 -2
- atomicshop/urls.py +6 -0
- atomicshop/venvs.py +28 -0
- atomicshop/versioning.py +27 -0
- atomicshop/web.py +98 -27
- atomicshop/web_apis/google_custom_search.py +44 -0
- atomicshop/web_apis/google_llm.py +188 -0
- atomicshop/websocket_parse.py +450 -0
- atomicshop/wrappers/certauthw/certauth.py +1 -0
- atomicshop/wrappers/cryptographyw.py +29 -8
- atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
- atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
- atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -2
- atomicshop/wrappers/ctyping/setup_device.py +466 -0
- atomicshop/wrappers/ctyping/win_console.py +39 -0
- atomicshop/wrappers/dockerw/dockerw.py +113 -2
- atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
- atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
- atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
- atomicshop/wrappers/factw/get_file_data.py +12 -5
- atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
- atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
- atomicshop/wrappers/githubw.py +537 -54
- atomicshop/wrappers/loggingw/consts.py +1 -1
- atomicshop/wrappers/loggingw/filters.py +23 -0
- atomicshop/wrappers/loggingw/formatters.py +12 -0
- atomicshop/wrappers/loggingw/handlers.py +214 -107
- atomicshop/wrappers/loggingw/loggers.py +19 -0
- atomicshop/wrappers/loggingw/loggingw.py +860 -22
- atomicshop/wrappers/loggingw/reading.py +134 -112
- atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
- atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
- atomicshop/wrappers/netshw.py +271 -0
- atomicshop/wrappers/playwrightw/engine.py +34 -19
- atomicshop/wrappers/playwrightw/infra.py +5 -0
- atomicshop/wrappers/playwrightw/javascript.py +7 -3
- atomicshop/wrappers/playwrightw/keyboard.py +14 -0
- atomicshop/wrappers/playwrightw/scenarios.py +172 -5
- atomicshop/wrappers/playwrightw/waits.py +9 -7
- atomicshop/wrappers/powershell_networking.py +80 -0
- atomicshop/wrappers/psutilw/processes.py +37 -1
- atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
- atomicshop/wrappers/pyopensslw.py +9 -2
- atomicshop/wrappers/pywin32w/cert_store.py +116 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
- atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
- atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
- atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
- atomicshop/wrappers/socketw/accepter.py +21 -7
- atomicshop/wrappers/socketw/certificator.py +216 -150
- atomicshop/wrappers/socketw/creator.py +190 -50
- atomicshop/wrappers/socketw/dns_server.py +491 -182
- atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
- atomicshop/wrappers/socketw/process_getter.py +86 -0
- atomicshop/wrappers/socketw/receiver.py +144 -102
- atomicshop/wrappers/socketw/sender.py +65 -35
- atomicshop/wrappers/socketw/sni.py +334 -165
- atomicshop/wrappers/socketw/socket_base.py +134 -0
- atomicshop/wrappers/socketw/socket_client.py +137 -95
- atomicshop/wrappers/socketw/socket_server_tester.py +11 -7
- atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
- atomicshop/wrappers/socketw/ssl_base.py +15 -14
- atomicshop/wrappers/socketw/statistics_csv.py +148 -17
- atomicshop/wrappers/sysmonw.py +1 -1
- atomicshop/wrappers/ubuntu_terminal.py +65 -26
- atomicshop/wrappers/win_auditw.py +189 -0
- atomicshop/wrappers/winregw/__init__.py +0 -0
- atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
- atomicshop/wrappers/winregw/winreg_network.py +232 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
- atomicshop-3.10.5.dist-info/RECORD +306 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
- atomicshop/_basics_temp.py +0 -101
- atomicshop/a_installs/win/fibratus.py +0 -9
- atomicshop/a_installs/win/mongodb.py +0 -9
- atomicshop/a_installs/win/pycharm.py +0 -9
- atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
- atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
- atomicshop/addons/mains/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
- atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
- atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
- atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
- atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
- atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
- atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
- atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
- atomicshop/addons/package_setup/Setup.cmd +0 -7
- atomicshop/archiver/_search_in_zip.py +0 -189
- atomicshop/archiver/archiver.py +0 -34
- atomicshop/archiver/search_in_archive.py +0 -250
- atomicshop/archiver/sevenz_app_w.py +0 -86
- atomicshop/archiver/sevenzs.py +0 -44
- atomicshop/archiver/zips.py +0 -293
- atomicshop/file_types.py +0 -24
- atomicshop/mitm/config_editor.py +0 -37
- atomicshop/mitm/engines/create_module_template_example.py +0 -13
- atomicshop/mitm/initialize_mitm_server.py +0 -268
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/permissions.py +0 -151
- atomicshop/script_as_string_processor.py +0 -38
- atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
- atomicshop/ssh_scripts/process_from_port.py +0 -27
- atomicshop/wrappers/_process_wrapper_curl.py +0 -27
- atomicshop/wrappers/_process_wrapper_tar.py +0 -21
- atomicshop/wrappers/dockerw/install_docker.py +0 -209
- atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
- atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
- atomicshop/wrappers/ffmpegw.py +0 -125
- atomicshop/wrappers/fibratusw/install.py +0 -81
- atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
- atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
- atomicshop/wrappers/msiw.py +0 -149
- atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/psutilw/networks.py +0 -45
- atomicshop/wrappers/pycharmw.py +0 -81
- atomicshop/wrappers/socketw/base.py +0 -59
- atomicshop/wrappers/socketw/get_process.py +0 -107
- atomicshop/wrappers/wslw.py +0 -191
- atomicshop-2.15.11.dist-info/RECORD +0 -302
- /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
- /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
- /atomicshop/{archiver → permissions}/__init__.py +0 -0
- /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
- /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
- /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
atomicshop/wrappers/githubw.py
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
import requests
|
|
2
2
|
import fnmatch
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
from typing import Literal
|
|
3
6
|
|
|
4
|
-
from .. import web, urls
|
|
7
|
+
from .. import web, urls, filesystem
|
|
5
8
|
from ..print_api import print_api
|
|
6
|
-
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MoreThanOneReleaseFoundError(Exception):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
class NoReleaseFoundError(Exception):
|
|
15
|
+
pass
|
|
7
16
|
|
|
8
17
|
|
|
9
18
|
class GitHubWrapper:
|
|
10
|
-
# You also can use '.tar.gz' as extension.
|
|
11
19
|
def __init__(
|
|
12
20
|
self,
|
|
13
21
|
user_name: str = None,
|
|
14
22
|
repo_name: str = None,
|
|
15
23
|
repo_url: str = None,
|
|
16
|
-
branch: str = '
|
|
17
|
-
|
|
24
|
+
branch: str = 'main',
|
|
25
|
+
path: str = None,
|
|
26
|
+
pat: str = None,
|
|
27
|
+
branch_file_extension: Literal[
|
|
28
|
+
'zip',
|
|
29
|
+
'tar.gz'] = 'zip'
|
|
18
30
|
):
|
|
19
31
|
"""
|
|
20
32
|
This class is a wrapper for GitHub repositories. It can download the branch file from the repository and extract
|
|
@@ -27,7 +39,11 @@ class GitHubWrapper:
|
|
|
27
39
|
You can provide the full url to the repository directly and then extract the user_name and repo_name from it
|
|
28
40
|
with the 'build_links_from_repo_url' function.
|
|
29
41
|
:param branch: str, the branch name. The default is 'master'.
|
|
30
|
-
:param
|
|
42
|
+
:param path: str, the path to the file/folder inside the repo that we'll do certain actions on.
|
|
43
|
+
Actions example: get_latest_commit_comment, download_path_from_branch.
|
|
44
|
+
:param pat: str, the personal access token to the repo.
|
|
45
|
+
:param branch_file_extension: str, the branch file extension. The default is 'zip'.
|
|
46
|
+
You also can use 'tar.gz' as extension.
|
|
31
47
|
|
|
32
48
|
================================================================================================================
|
|
33
49
|
Usage to download the 'master' branch file:
|
|
@@ -62,7 +78,7 @@ class GitHubWrapper:
|
|
|
62
78
|
Usage to download the latest release where the file name is 'test_file.zip':
|
|
63
79
|
git_wrapper = GitHubWrapper(user_name='user_name', repo_name='repo_name')
|
|
64
80
|
git_wrapper.download_and_extract_latest_release(
|
|
65
|
-
target_directory='target_directory',
|
|
81
|
+
target_directory='target_directory', asset_pattern='test_*.zip')
|
|
66
82
|
================================================================================================================
|
|
67
83
|
Usage to get the latest release json:
|
|
68
84
|
git_wrapper = GitHubWrapper(user_name='user_name', repo_name='repo_name')
|
|
@@ -77,17 +93,32 @@ class GitHubWrapper:
|
|
|
77
93
|
self.repo_name: str = repo_name
|
|
78
94
|
self.repo_url: str = repo_url
|
|
79
95
|
self.branch: str = branch
|
|
96
|
+
self.path: str = path
|
|
97
|
+
self.pat: str = pat
|
|
80
98
|
self.branch_file_extension: str = branch_file_extension
|
|
81
99
|
|
|
82
100
|
# Default variables.
|
|
83
|
-
self.
|
|
84
|
-
|
|
101
|
+
if self.branch_file_extension == 'zip':
|
|
102
|
+
self.branch_type_directory: str = 'zipball'
|
|
103
|
+
elif self.branch_file_extension == 'tar.gz':
|
|
104
|
+
self.branch_type_directory: str = 'tarball'
|
|
105
|
+
else:
|
|
106
|
+
raise ValueError(f"Unsupported branch file extension: {self.branch_file_extension}")
|
|
107
|
+
|
|
85
108
|
self.domain: str = 'github.com'
|
|
86
109
|
|
|
87
110
|
# Initialize variables.
|
|
88
111
|
self.branch_download_link: str = str()
|
|
89
112
|
self.branch_downloaded_folder_name: str = str()
|
|
113
|
+
self.branch_file_name: str = str()
|
|
114
|
+
self.api_url: str = str()
|
|
90
115
|
self.latest_release_json_url: str = str()
|
|
116
|
+
self.commits_url: str = str()
|
|
117
|
+
self.contents_url: str = str()
|
|
118
|
+
|
|
119
|
+
self.releases_url: str = str()
|
|
120
|
+
self.releases_per_page: int = 100
|
|
121
|
+
self.releases_starting_page: int = 1
|
|
91
122
|
|
|
92
123
|
if self.user_name and self.repo_name and not self.repo_url:
|
|
93
124
|
self.build_links_from_user_and_repo()
|
|
@@ -95,21 +126,35 @@ class GitHubWrapper:
|
|
|
95
126
|
if self.repo_url and not self.user_name and not self.repo_name:
|
|
96
127
|
self.build_links_from_repo_url()
|
|
97
128
|
|
|
129
|
+
def _get_headers(self) -> dict:
|
|
130
|
+
"""
|
|
131
|
+
Returns headers for the GitHub API requests. If a personal access token (PAT) is provided, it adds the
|
|
132
|
+
'Authorization' header.
|
|
133
|
+
"""
|
|
134
|
+
headers = {}
|
|
135
|
+
if self.pat:
|
|
136
|
+
headers['Authorization'] = f'token {self.pat}'
|
|
137
|
+
return headers
|
|
138
|
+
|
|
98
139
|
def build_links_from_user_and_repo(self, **kwargs):
|
|
99
140
|
if not self.user_name or not self.repo_name:
|
|
100
|
-
|
|
101
|
-
print_api(message, color="red", error_type=True, **kwargs)
|
|
141
|
+
raise ValueError("'user_name' or 'repo_name' is empty.")
|
|
102
142
|
|
|
103
143
|
self.repo_url = f'https://{self.domain}/{self.user_name}/{self.repo_name}'
|
|
104
|
-
self.branch_download_link = f'{self.repo_url}/{self.archive_directory}/{self.branch_file_name}'
|
|
105
144
|
self.branch_downloaded_folder_name = f'{self.repo_name}-{self.branch}'
|
|
106
|
-
self.
|
|
107
|
-
|
|
145
|
+
self.branch_file_name: str = f'{self.repo_name}-{self.branch}.{self.branch_file_extension}'
|
|
146
|
+
|
|
147
|
+
self.api_url = f'https://api.{self.domain}/repos/{self.user_name}/{self.repo_name}'
|
|
148
|
+
|
|
149
|
+
self.latest_release_json_url: str = f'{self.api_url}/releases/latest'
|
|
150
|
+
self.releases_url: str = f'{self.api_url}/releases'
|
|
151
|
+
self.commits_url: str = f'{self.api_url}/commits'
|
|
152
|
+
self.contents_url: str = f'{self.api_url}/contents'
|
|
153
|
+
self.branch_download_link = f'{self.api_url}/{self.branch_type_directory}/{self.branch}'
|
|
108
154
|
|
|
109
155
|
def build_links_from_repo_url(self, **kwargs):
|
|
110
156
|
if not self.repo_url:
|
|
111
|
-
|
|
112
|
-
print_api(message, color="red", error_type=True, **kwargs)
|
|
157
|
+
raise ValueError("'repo_url' is empty.")
|
|
113
158
|
|
|
114
159
|
repo_url_parsed = urls.url_parser(self.repo_url)
|
|
115
160
|
self.check_github_domain(repo_url_parsed['netloc'])
|
|
@@ -118,50 +163,297 @@ class GitHubWrapper:
|
|
|
118
163
|
|
|
119
164
|
self.build_links_from_user_and_repo()
|
|
120
165
|
|
|
121
|
-
def check_github_domain(
|
|
166
|
+
def check_github_domain(
|
|
167
|
+
self,
|
|
168
|
+
domain: str
|
|
169
|
+
):
|
|
122
170
|
if self.domain not in domain:
|
|
123
171
|
print_api(
|
|
124
172
|
f'This is not [{self.domain}] domain.', color="red", error_type=True)
|
|
125
173
|
|
|
174
|
+
def download_file(
|
|
175
|
+
self,
|
|
176
|
+
file_name: str,
|
|
177
|
+
target_dir: str
|
|
178
|
+
) -> str:
|
|
179
|
+
"""
|
|
180
|
+
Download a single repo file to a local directory.
|
|
181
|
+
|
|
182
|
+
:param file_name: string, Full repo-relative path to the file. Example:
|
|
183
|
+
"eng.traineddata"
|
|
184
|
+
"script\\English.script"
|
|
185
|
+
:param target_dir: string, Local directory to save into.
|
|
186
|
+
|
|
187
|
+
:return: The local path to the downloaded file.
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
# Normalize to GitHub path format
|
|
191
|
+
file_path = file_name.replace("\\", "/").strip("/")
|
|
192
|
+
|
|
193
|
+
headers = self._get_headers()
|
|
194
|
+
url = f"{self.contents_url}/{file_path}"
|
|
195
|
+
params = {"ref": self.branch}
|
|
196
|
+
|
|
197
|
+
resp = requests.get(url, headers=headers, params=params)
|
|
198
|
+
resp.raise_for_status()
|
|
199
|
+
item = resp.json()
|
|
200
|
+
|
|
201
|
+
# Expect a single file object
|
|
202
|
+
if isinstance(item, list) or item.get("type") != "file":
|
|
203
|
+
raise ValueError(f"'{file_name}' is not a file in branch '{self.branch}'.")
|
|
204
|
+
|
|
205
|
+
download_url = item.get("download_url")
|
|
206
|
+
if not download_url:
|
|
207
|
+
raise ValueError(f"Unable to obtain download URL for '{file_name}'.")
|
|
208
|
+
|
|
209
|
+
os.makedirs(target_dir, exist_ok=True)
|
|
210
|
+
local_name = item.get("name") or os.path.basename(file_path)
|
|
211
|
+
|
|
212
|
+
from .. import web # ensure available in your module structure
|
|
213
|
+
web.download(
|
|
214
|
+
file_url=download_url,
|
|
215
|
+
target_directory=target_dir,
|
|
216
|
+
file_name=local_name,
|
|
217
|
+
headers=headers,
|
|
218
|
+
)
|
|
219
|
+
return os.path.join(target_dir, local_name)
|
|
220
|
+
|
|
221
|
+
def download_directory(
|
|
222
|
+
self,
|
|
223
|
+
folder_name: str,
|
|
224
|
+
target_dir: str
|
|
225
|
+
) -> None:
|
|
226
|
+
"""
|
|
227
|
+
Recursively download a repo directory to a local directory.
|
|
228
|
+
|
|
229
|
+
:param folder_name: string, Repo-relative directory path to download (e.g., "tests/langs").
|
|
230
|
+
:param target_dir: string, Local directory to save the folder tree into.
|
|
231
|
+
"""
|
|
232
|
+
headers = self._get_headers()
|
|
233
|
+
root_path = folder_name.replace("\\", "/").strip("/")
|
|
234
|
+
|
|
235
|
+
def _walk_dir(rel_path: str, local_dir: str) -> None:
|
|
236
|
+
contents_url = f"{self.contents_url}/{rel_path}" if rel_path else self.contents_url
|
|
237
|
+
params = {"ref": self.branch}
|
|
238
|
+
|
|
239
|
+
response = requests.get(contents_url, headers=headers, params=params)
|
|
240
|
+
response.raise_for_status()
|
|
241
|
+
items = response.json()
|
|
242
|
+
|
|
243
|
+
# If a file path was passed accidentally, delegate to download_file
|
|
244
|
+
if isinstance(items, dict) and items.get("type") == "file":
|
|
245
|
+
self.download_file(rel_path, local_dir)
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
if not isinstance(items, list):
|
|
249
|
+
raise ValueError(f"Unexpected response shape when listing '{rel_path or '/'}'.")
|
|
250
|
+
|
|
251
|
+
os.makedirs(local_dir, exist_ok=True)
|
|
252
|
+
|
|
253
|
+
for item in items:
|
|
254
|
+
name = item["name"]
|
|
255
|
+
if item["type"] == "file":
|
|
256
|
+
self.download_file(f"{rel_path}/{name}" if rel_path else name, local_dir)
|
|
257
|
+
elif item["type"] == "dir":
|
|
258
|
+
_walk_dir(f"{rel_path}/{name}" if rel_path else name, os.path.join(local_dir, name))
|
|
259
|
+
# ignore symlinks/submodules if present
|
|
260
|
+
|
|
261
|
+
_walk_dir(root_path, target_dir)
|
|
262
|
+
|
|
126
263
|
def download_and_extract_branch(
|
|
127
264
|
self,
|
|
128
265
|
target_directory: str,
|
|
129
266
|
archive_remove_first_directory: bool = False,
|
|
267
|
+
download_each_file: bool = False,
|
|
130
268
|
**kwargs
|
|
131
269
|
):
|
|
132
270
|
"""
|
|
133
271
|
This function will download the branch file from GitHub, extract the file and remove the file, leaving
|
|
134
272
|
only the extracted folder.
|
|
273
|
+
If the 'path' was specified during the initialization of the class, only the path will be downloaded.
|
|
135
274
|
|
|
136
|
-
:param target_directory:
|
|
137
|
-
:param archive_remove_first_directory: boolean,
|
|
275
|
+
:param target_directory: str, the target directory to download the branch/path.
|
|
276
|
+
:param archive_remove_first_directory: boolean, available only if 'path' was not specified during the initialization
|
|
277
|
+
Sets if archive extract function will extract the archive
|
|
138
278
|
without first directory in the archive. Check reference in the
|
|
139
|
-
'
|
|
279
|
+
'dkarchiver.arch_wrappers.zips.extract_archive_with_zipfile' function.
|
|
280
|
+
:param download_each_file: bool, available only if 'path' was specified during the initialization of the class.
|
|
281
|
+
Sets if each file will be downloaded separately.
|
|
282
|
+
|
|
283
|
+
True: Meaning the directory '/home/user/Downloads/files/' will be created and each file will be downloaded
|
|
284
|
+
('file1.txt', 'file2.txt', 'file3.txt') separately to this directory.
|
|
285
|
+
False: The branch file will be downloaded to temp directory then the provided path
|
|
286
|
+
will be extracted from there, then the downloaded branch directory will be removed.
|
|
140
287
|
:return:
|
|
141
288
|
"""
|
|
142
289
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
290
|
+
headers: dict = self._get_headers()
|
|
291
|
+
|
|
292
|
+
if not download_each_file:
|
|
293
|
+
if self.path:
|
|
294
|
+
download_target_directory = tempfile.mkdtemp()
|
|
295
|
+
current_archive_remove_first_directory = True
|
|
296
|
+
else:
|
|
297
|
+
download_target_directory = target_directory
|
|
298
|
+
current_archive_remove_first_directory = archive_remove_first_directory
|
|
299
|
+
|
|
300
|
+
# Download the repo to current working directory, extract and remove the archive.
|
|
301
|
+
web.download_and_extract_file(
|
|
302
|
+
file_url=self.branch_download_link,
|
|
303
|
+
file_name=self.branch_file_name,
|
|
304
|
+
target_directory=download_target_directory,
|
|
305
|
+
archive_remove_first_directory=current_archive_remove_first_directory,
|
|
306
|
+
headers=headers,
|
|
307
|
+
**kwargs)
|
|
308
|
+
|
|
309
|
+
if self.path:
|
|
310
|
+
source_path: str = f"{download_target_directory}{os.sep}{self.path}"
|
|
311
|
+
|
|
312
|
+
if not archive_remove_first_directory:
|
|
313
|
+
target_directory = os.path.join(target_directory, self.path)
|
|
314
|
+
filesystem.create_directory(target_directory)
|
|
315
|
+
|
|
316
|
+
# Move the path to the target directory.
|
|
317
|
+
filesystem.move_folder_contents_to_folder(
|
|
318
|
+
source_path, target_directory)
|
|
319
|
+
|
|
320
|
+
# Remove the downloaded branch directory.
|
|
321
|
+
filesystem.remove_directory(download_target_directory)
|
|
322
|
+
else:
|
|
323
|
+
if archive_remove_first_directory:
|
|
324
|
+
current_target_directory = target_directory
|
|
325
|
+
else:
|
|
326
|
+
current_target_directory = os.path.join(target_directory, self.path)
|
|
327
|
+
|
|
328
|
+
self.download_directory(self.path, current_target_directory)
|
|
329
|
+
|
|
330
|
+
def get_releases_json(
|
|
331
|
+
self,
|
|
332
|
+
asset_pattern: str = None,
|
|
333
|
+
latest: bool = False,
|
|
334
|
+
per_page: int = None,
|
|
335
|
+
starting_page: int = None,
|
|
336
|
+
all_assets: bool = False,
|
|
337
|
+
):
|
|
338
|
+
"""
|
|
339
|
+
This function will get the releases json.
|
|
340
|
+
:param asset_pattern: str, the string pattern to search in the asset names of releases. Wildcards can be used.
|
|
341
|
+
If there is a match, the release will be added to the result list.
|
|
342
|
+
:param latest: bool, if True, will get only the latest release.
|
|
343
|
+
If 'asset_pattern' is provided, 'latest' will find the latest release matching the pattern.
|
|
344
|
+
Of course if you want to get it from all releases, you must set 'all_assets' to True.
|
|
345
|
+
:param per_page: int, the number of releases per page. Default is 100.
|
|
346
|
+
:param starting_page: int, the starting page number. Default is 1.
|
|
347
|
+
:param all_assets: bool, if True, will get all releases matching the pattern across all pages
|
|
348
|
+
OR all assets if no pattern is provided.
|
|
349
|
+
:return:
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
# If 'latest' is True and no 'asset_pattern' is provided, we only need to get 1 release from page 1.
|
|
353
|
+
# No need to get more assets than the first one.
|
|
354
|
+
if latest and not asset_pattern:
|
|
355
|
+
per_page = 1
|
|
356
|
+
starting_page = 1
|
|
357
|
+
all_assets = False
|
|
358
|
+
# In all other cases, get the releases according to the provided parameters or defaults.
|
|
359
|
+
else:
|
|
360
|
+
if not per_page:
|
|
361
|
+
per_page = self.releases_per_page
|
|
362
|
+
|
|
363
|
+
if not starting_page:
|
|
364
|
+
starting_page = self.releases_starting_page
|
|
365
|
+
|
|
366
|
+
headers: dict = self._get_headers()
|
|
367
|
+
|
|
368
|
+
params: dict = {
|
|
369
|
+
'per_page': per_page,
|
|
370
|
+
'page': starting_page
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
all_releases = []
|
|
374
|
+
while True:
|
|
375
|
+
response = requests.get(self.releases_url, headers=headers, params=params)
|
|
376
|
+
releases = response.json()
|
|
377
|
+
# If no releases found on current page, there will be none on the next as well, break the loop.
|
|
378
|
+
if not releases:
|
|
379
|
+
break
|
|
380
|
+
|
|
381
|
+
# If 'asset_pattern' is provided, filter releases to only those that have matching assets.
|
|
382
|
+
if asset_pattern:
|
|
383
|
+
for release in releases:
|
|
384
|
+
assets = release.get('assets', [])
|
|
385
|
+
matching_assets = [asset for asset in assets if fnmatch.fnmatch(asset.get('name', ''), asset_pattern)]
|
|
386
|
+
if matching_assets:
|
|
387
|
+
all_releases.append(release)
|
|
388
|
+
|
|
389
|
+
if latest:
|
|
390
|
+
return all_releases
|
|
391
|
+
else:
|
|
392
|
+
all_releases.extend(releases)
|
|
393
|
+
|
|
394
|
+
if not all_assets:
|
|
395
|
+
break
|
|
396
|
+
|
|
397
|
+
params['page'] += 1
|
|
398
|
+
|
|
399
|
+
return all_releases
|
|
400
|
+
|
|
401
|
+
def get_latest_release_json(
|
|
402
|
+
self,
|
|
403
|
+
asset_pattern: str = None
|
|
404
|
+
) -> dict:
|
|
405
|
+
"""
|
|
406
|
+
This function will get the latest releases json.
|
|
407
|
+
:param asset_pattern: str, the string pattern to search in the asset names of releases. Wildcards can be used.
|
|
408
|
+
If there is a match, the release will be added to the result list.
|
|
409
|
+
:return: dict, the latest release json.
|
|
410
|
+
"""
|
|
411
|
+
|
|
412
|
+
if asset_pattern:
|
|
413
|
+
releases = self.get_releases_json(
|
|
414
|
+
asset_pattern=asset_pattern,
|
|
415
|
+
latest=True,
|
|
416
|
+
all_assets=True
|
|
417
|
+
)
|
|
418
|
+
else:
|
|
419
|
+
releases = self.get_releases_json(latest=True)
|
|
420
|
+
|
|
421
|
+
if not releases:
|
|
422
|
+
return {}
|
|
423
|
+
else:
|
|
424
|
+
return releases[0]
|
|
425
|
+
|
|
426
|
+
def get_latest_release_version(
|
|
427
|
+
self,
|
|
428
|
+
asset_pattern: str = None
|
|
429
|
+
) -> str:
|
|
430
|
+
"""
|
|
431
|
+
This function will get the latest release version number.
|
|
432
|
+
|
|
433
|
+
:param asset_pattern: str, the string pattern to search in the asset names of releases. Wildcards can be used.
|
|
434
|
+
If there is a match, the release will be added to the result list.
|
|
435
|
+
:return: str, the latest release version number.
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
latest_release_json: dict = self.get_latest_release_json(asset_pattern=asset_pattern)
|
|
439
|
+
latest_release_version: str = latest_release_json['tag_name']
|
|
440
|
+
return latest_release_version
|
|
149
441
|
|
|
150
442
|
def get_latest_release_url(
|
|
151
443
|
self,
|
|
152
|
-
|
|
444
|
+
asset_pattern: str,
|
|
153
445
|
exclude_string: str = None,
|
|
154
446
|
**kwargs):
|
|
155
447
|
"""
|
|
156
448
|
This function will return the latest release url.
|
|
157
|
-
:param
|
|
449
|
+
:param asset_pattern: str, the string pattern to search in the latest release. Wildcards can be used.
|
|
158
450
|
:param exclude_string: str, the string to exclude from the search. No wildcards can be used.
|
|
159
451
|
:param kwargs: dict, the print arguments for the 'print_api' function.
|
|
160
452
|
:return: str, the latest release url.
|
|
161
453
|
"""
|
|
162
454
|
|
|
163
455
|
# Get the 'assets' key of the latest release json.
|
|
164
|
-
github_latest_releases_list = self.
|
|
456
|
+
github_latest_releases_list = self.get_latest_release_json()['assets']
|
|
165
457
|
|
|
166
458
|
# Get only download urls of the latest releases.
|
|
167
459
|
download_urls: list = list()
|
|
@@ -174,44 +466,51 @@ class GitHubWrapper:
|
|
|
174
466
|
if exclude_string in download_url:
|
|
175
467
|
download_urls.remove(download_url)
|
|
176
468
|
|
|
177
|
-
# Find urls against '
|
|
178
|
-
found_urls = fnmatch.filter(download_urls,
|
|
469
|
+
# Find urls against 'asset_pattern'.
|
|
470
|
+
found_urls: list = fnmatch.filter(download_urls, asset_pattern)
|
|
179
471
|
|
|
180
472
|
# If more than 1 url answer the criteria, we can't download it. The user must be more specific in his input
|
|
181
473
|
# strings.
|
|
182
474
|
if len(found_urls) > 1:
|
|
183
475
|
message = f'More than 1 result found in JSON response, try changing search string or extension.\n' \
|
|
184
476
|
f'{found_urls}'
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
477
|
+
raise MoreThanOneReleaseFoundError(message)
|
|
478
|
+
elif len(found_urls) == 0:
|
|
479
|
+
message = f'No result found in JSON response, try changing search string or extension.'
|
|
480
|
+
raise NoReleaseFoundError(message)
|
|
481
|
+
else:
|
|
482
|
+
return found_urls[0]
|
|
188
483
|
|
|
189
484
|
def download_latest_release(
|
|
190
485
|
self,
|
|
191
486
|
target_directory: str,
|
|
192
|
-
|
|
487
|
+
asset_pattern: str,
|
|
193
488
|
exclude_string: str = None,
|
|
194
|
-
**kwargs
|
|
489
|
+
**kwargs
|
|
490
|
+
) -> str:
|
|
195
491
|
"""
|
|
196
492
|
This function will download the latest release from the GitHub repository.
|
|
197
493
|
:param target_directory: str, the target directory to download the file.
|
|
198
|
-
:param
|
|
494
|
+
:param asset_pattern: str, the string pattern to search in the latest release. Wildcards can be used.
|
|
199
495
|
:param exclude_string: str, the string to exclude from the search. No wildcards can be used.
|
|
200
|
-
The 'excluded_string' will be filtered before the '
|
|
496
|
+
The 'excluded_string' will be filtered before the 'asset_pattern' entries.
|
|
201
497
|
:param kwargs: dict, the print arguments for the 'print_api' function.
|
|
202
|
-
:return:
|
|
498
|
+
:return: str, the downloaded file path.
|
|
203
499
|
"""
|
|
204
500
|
|
|
501
|
+
headers: dict = self._get_headers()
|
|
502
|
+
|
|
205
503
|
# Get the latest release url.
|
|
206
|
-
found_url = self.get_latest_release_url(
|
|
504
|
+
found_url = self.get_latest_release_url(asset_pattern=asset_pattern, exclude_string=exclude_string, **kwargs)
|
|
207
505
|
|
|
208
|
-
downloaded_file_path = web.download(
|
|
506
|
+
downloaded_file_path = web.download(
|
|
507
|
+
file_url=found_url, target_directory=target_directory, headers=headers, **kwargs)
|
|
209
508
|
return downloaded_file_path
|
|
210
509
|
|
|
211
510
|
def download_and_extract_latest_release(
|
|
212
511
|
self,
|
|
213
512
|
target_directory: str,
|
|
214
|
-
|
|
513
|
+
asset_pattern: str,
|
|
215
514
|
exclude_string: str = None,
|
|
216
515
|
archive_remove_first_directory: bool = False,
|
|
217
516
|
**kwargs):
|
|
@@ -219,35 +518,219 @@ class GitHubWrapper:
|
|
|
219
518
|
This function will download the latest release from the GitHub repository, extract the file and remove the file,
|
|
220
519
|
leaving only the extracted folder.
|
|
221
520
|
:param target_directory: str, the target directory to download and extract the file.
|
|
222
|
-
:param
|
|
521
|
+
:param asset_pattern: str, the string pattern to search in the latest release. Wildcards can be used.
|
|
223
522
|
:param exclude_string: str, the string to exclude from the search. No wildcards can be used.
|
|
224
523
|
:param archive_remove_first_directory: bool, sets if archive extract function will extract the archive
|
|
225
524
|
without first directory in the archive. Check reference in the
|
|
226
|
-
'
|
|
525
|
+
'dkarchiver.arch_wrappers.zips.extract_archive_with_zipfile' function.
|
|
227
526
|
:param kwargs: dict, the print arguments for the 'print_api' function.
|
|
228
527
|
:return:
|
|
229
528
|
"""
|
|
230
529
|
|
|
530
|
+
headers: dict = self._get_headers()
|
|
531
|
+
|
|
231
532
|
# Get the latest release url.
|
|
232
|
-
found_url = self.get_latest_release_url(
|
|
533
|
+
found_url = self.get_latest_release_url(asset_pattern=asset_pattern, exclude_string=exclude_string, **kwargs)
|
|
233
534
|
|
|
234
535
|
web.download_and_extract_file(
|
|
235
536
|
file_url=found_url,
|
|
236
537
|
target_directory=target_directory,
|
|
237
538
|
archive_remove_first_directory=archive_remove_first_directory,
|
|
539
|
+
headers=headers,
|
|
238
540
|
**kwargs)
|
|
239
541
|
|
|
240
|
-
def
|
|
542
|
+
def get_latest_commit(self) -> dict:
|
|
241
543
|
"""
|
|
242
|
-
This function
|
|
243
|
-
|
|
544
|
+
This function retrieves the latest commit on the specified branch.
|
|
545
|
+
It uses the GitHub API endpoint for commits.
|
|
546
|
+
|
|
547
|
+
:return: dict, the latest commit data.
|
|
244
548
|
"""
|
|
245
|
-
response = requests.get(self.latest_release_json_url)
|
|
246
|
-
return response.json()
|
|
247
549
|
|
|
248
|
-
|
|
550
|
+
headers: dict = self._get_headers()
|
|
551
|
+
|
|
552
|
+
# Use query parameters to filter commits by branch (sha) and limit results to 1
|
|
553
|
+
params: dict = {
|
|
554
|
+
'sha': self.branch,
|
|
555
|
+
'per_page': 1
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if self.path:
|
|
559
|
+
params['path'] = self.path
|
|
560
|
+
|
|
561
|
+
response = requests.get(self.commits_url, headers=headers, params=params)
|
|
562
|
+
response.raise_for_status() # Raises an HTTPError if the HTTP request returned an unsuccessful status code.
|
|
563
|
+
|
|
564
|
+
commits = response.json()
|
|
565
|
+
if not commits:
|
|
566
|
+
return {}
|
|
567
|
+
|
|
568
|
+
latest_commit = commits[0]
|
|
569
|
+
return latest_commit
|
|
570
|
+
|
|
571
|
+
def get_latest_commit_message(self):
|
|
249
572
|
"""
|
|
250
|
-
This function
|
|
251
|
-
|
|
573
|
+
This function retrieves the commit message (comment) of the latest commit on the specified branch.
|
|
574
|
+
It uses the GitHub API endpoint for commits.
|
|
575
|
+
|
|
576
|
+
:return: str, the commit message of the latest commit.
|
|
577
|
+
"""
|
|
578
|
+
|
|
579
|
+
latest_commit: dict = self.get_latest_commit()
|
|
580
|
+
|
|
581
|
+
commit_message = latest_commit.get("commit", {}).get("message", "")
|
|
582
|
+
return commit_message
|
|
583
|
+
|
|
584
|
+
def list_files(
|
|
585
|
+
self,
|
|
586
|
+
pattern: str = "*",
|
|
587
|
+
recursive: bool = True,
|
|
588
|
+
path: str | None = None,
|
|
589
|
+
) -> list[str]:
|
|
590
|
+
"""
|
|
591
|
+
List files in the repository (or in a specific subfolder).
|
|
592
|
+
|
|
593
|
+
:param pattern: Glob-style pattern (e.g., "*.ex*", "*test*.py"). Matching is done
|
|
594
|
+
against the file's base name (not the full path).
|
|
595
|
+
:param recursive: If True, include files in all subfolders (returns full repo-relative
|
|
596
|
+
paths). If False, list only the immediate files in the chosen folder.
|
|
597
|
+
:param path: Optional subfolder to list from (e.g., "tests/langs"). If omitted,
|
|
598
|
+
uses self.path if set, otherwise the repo root.
|
|
599
|
+
|
|
600
|
+
:return: A list of repo-relative file paths that match the pattern.
|
|
252
601
|
"""
|
|
253
|
-
|
|
602
|
+
headers = self._get_headers()
|
|
603
|
+
base_path = (path or self.path or "").strip("/")
|
|
604
|
+
|
|
605
|
+
if recursive:
|
|
606
|
+
# Use the Git Trees API to fetch all files in one call, then filter.
|
|
607
|
+
tree_url = f"{self.api_url}/git/trees/{self.branch}"
|
|
608
|
+
params = {"recursive": "1"}
|
|
609
|
+
resp = requests.get(tree_url, headers=headers, params=params)
|
|
610
|
+
resp.raise_for_status()
|
|
611
|
+
data = resp.json()
|
|
612
|
+
|
|
613
|
+
files = []
|
|
614
|
+
for entry in data.get("tree", []):
|
|
615
|
+
if entry.get("type") != "blob":
|
|
616
|
+
continue # only files
|
|
617
|
+
entry_path = entry.get("path", "")
|
|
618
|
+
# If a base_path was provided, keep only files under it
|
|
619
|
+
if base_path and not entry_path.startswith(base_path + "/") and entry_path != base_path:
|
|
620
|
+
continue
|
|
621
|
+
# Match pattern against the *file name* (basename)
|
|
622
|
+
if fnmatch.fnmatch(os.path.basename(entry_path), pattern):
|
|
623
|
+
files.append(entry_path)
|
|
624
|
+
return files
|
|
625
|
+
|
|
626
|
+
else:
|
|
627
|
+
# Non-recursive: use the Contents API to list a single directory.
|
|
628
|
+
# If base_path is empty, list the repo root.
|
|
629
|
+
if base_path:
|
|
630
|
+
contents_url = f"{self.contents_url}/{base_path}"
|
|
631
|
+
else:
|
|
632
|
+
contents_url = self.contents_url
|
|
633
|
+
|
|
634
|
+
params = {"ref": self.branch}
|
|
635
|
+
resp = requests.get(contents_url, headers=headers, params=params)
|
|
636
|
+
resp.raise_for_status()
|
|
637
|
+
items = resp.json()
|
|
638
|
+
|
|
639
|
+
# The Contents API returns a dict when the path points to a single file;
|
|
640
|
+
# normalize to a list to simplify handling.
|
|
641
|
+
if isinstance(items, dict):
|
|
642
|
+
items = [items]
|
|
643
|
+
|
|
644
|
+
files = []
|
|
645
|
+
for item in items:
|
|
646
|
+
if item.get("type") == "file":
|
|
647
|
+
name = item.get("name", "")
|
|
648
|
+
if fnmatch.fnmatch(name, pattern):
|
|
649
|
+
# item["path"] is the full repo-relative path we want to return
|
|
650
|
+
files.append(item.get("path", name))
|
|
651
|
+
return files
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def _make_parser():
|
|
655
|
+
import argparse
|
|
656
|
+
|
|
657
|
+
parser = argparse.ArgumentParser(description='GitHub Wrapper')
|
|
658
|
+
parser.add_argument(
|
|
659
|
+
'-u', '--repo_url', type=str, required=True,
|
|
660
|
+
help='The repository url. Example: https://github.com/{user_name}/{repo_name}')
|
|
661
|
+
parser.add_argument(
|
|
662
|
+
'-b', '--branch', type=str, required=True,
|
|
663
|
+
help='The branch name. The specific branch from the repo you want to operate on.')
|
|
664
|
+
parser.add_argument(
|
|
665
|
+
'-p', '--path', type=str, default=None,
|
|
666
|
+
help="The path to the file/folder inside the repo that we'll do certain actions on.\n"
|
|
667
|
+
"Available actions: get_latest_commit_message, download_path_from_branch.")
|
|
668
|
+
parser.add_argument(
|
|
669
|
+
'-t', '--target_directory', type=str, default=None,
|
|
670
|
+
help='The target directory to download the file/folder.'
|
|
671
|
+
)
|
|
672
|
+
parser.add_argument(
|
|
673
|
+
'--pat', type=str, default=None,
|
|
674
|
+
help='The personal access token to the repo.')
|
|
675
|
+
parser.add_argument(
|
|
676
|
+
'-glcm', '--get_latest_commit_message', action='store_true', default=False,
|
|
677
|
+
help='Sets if the latest commit message comment will be printed.')
|
|
678
|
+
parser.add_argument(
|
|
679
|
+
'-glcj', '--get_latest_commit_json', action='store_true', default=False,
|
|
680
|
+
help='Sets if the latest commit json will be printed.')
|
|
681
|
+
parser.add_argument(
|
|
682
|
+
'-db', '--download_branch', action='store_true', default=False,
|
|
683
|
+
help='Sets if the branch will be downloaded. In conjunction with path, only the path will be downloaded.')
|
|
684
|
+
|
|
685
|
+
return parser
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def github_wrapper_main(
|
|
689
|
+
repo_url: str,
|
|
690
|
+
branch: str,
|
|
691
|
+
path: str = None,
|
|
692
|
+
target_directory: str = None,
|
|
693
|
+
pat: str = None,
|
|
694
|
+
get_latest_commit_message: bool = False,
|
|
695
|
+
get_latest_commit_json: bool = False,
|
|
696
|
+
download_branch: bool = False
|
|
697
|
+
):
|
|
698
|
+
"""
|
|
699
|
+
This function is the main function for the GitHubWrapper class.
|
|
700
|
+
:param repo_url: str, the repository url.
|
|
701
|
+
Example: https://github.com/{user_name}/{repo_name}
|
|
702
|
+
:param branch: str, the branch name. The specific branch from the repo you want to operate on.
|
|
703
|
+
:param path: str, the path to the file/folder for which the commit message should be retrieved.
|
|
704
|
+
:param target_directory: str, the target directory to download the file/folder.
|
|
705
|
+
:param pat: str, the personal access token to the repo.
|
|
706
|
+
:param get_latest_commit_json: bool, sets if the latest commit json will be printed.
|
|
707
|
+
:param get_latest_commit_message: bool, sets if the latest commit message comment will be printed.
|
|
708
|
+
:param download_branch: bool, sets if the branch will be downloaded. In conjunction with path, only the path will be
|
|
709
|
+
downloaded.
|
|
710
|
+
:return:
|
|
711
|
+
"""
|
|
712
|
+
|
|
713
|
+
git_wrapper = GitHubWrapper(repo_url=repo_url, branch=branch, path=path, pat=pat)
|
|
714
|
+
|
|
715
|
+
if get_latest_commit_message:
|
|
716
|
+
commit_comment = git_wrapper.get_latest_commit_message()
|
|
717
|
+
print_api(commit_comment)
|
|
718
|
+
return 0
|
|
719
|
+
|
|
720
|
+
if get_latest_commit_json:
|
|
721
|
+
latest_commit_json = git_wrapper.get_latest_commit()
|
|
722
|
+
print_api(latest_commit_json)
|
|
723
|
+
return 0
|
|
724
|
+
|
|
725
|
+
if download_branch:
|
|
726
|
+
git_wrapper.download_and_extract_branch(
|
|
727
|
+
target_directory=target_directory, download_each_file=False, archive_remove_first_directory=True)
|
|
728
|
+
|
|
729
|
+
return 0
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
def github_wrapper_main_with_args():
|
|
733
|
+
main_parser = _make_parser()
|
|
734
|
+
args = main_parser.parse_args()
|
|
735
|
+
|
|
736
|
+
return github_wrapper_main(**vars(args))
|