update
This commit is contained in:
100
ytracking/ultralytics/hub/__init__.py
Normal file
100
ytracking/ultralytics/hub/__init__.py
Normal file
@ -0,0 +1,100 @@
|
||||
# Ultralytics YOLO 🚀, AGPL-3.0 license
|
||||
|
||||
import requests
|
||||
|
||||
from ultralytics.data.utils import HUBDatasetStats
|
||||
from ultralytics.hub.auth import Auth
|
||||
from ultralytics.hub.utils import HUB_API_ROOT, HUB_WEB_ROOT, PREFIX
|
||||
from ultralytics.utils import LOGGER, SETTINGS
|
||||
|
||||
|
||||
def login(api_key=''):
|
||||
"""
|
||||
Log in to the Ultralytics HUB API using the provided API key.
|
||||
|
||||
Args:
|
||||
api_key (str, optional): May be an API key or a combination API key and model ID, i.e. key_id
|
||||
|
||||
Example:
|
||||
```python
|
||||
from ultralytics import hub
|
||||
|
||||
hub.login('API_KEY')
|
||||
```
|
||||
"""
|
||||
Auth(api_key, verbose=True)
|
||||
|
||||
|
||||
def logout():
|
||||
"""
|
||||
Log out of Ultralytics HUB by removing the API key from the settings file. To log in again, use 'yolo hub login'.
|
||||
|
||||
Example:
|
||||
```python
|
||||
from ultralytics import hub
|
||||
|
||||
hub.logout()
|
||||
```
|
||||
"""
|
||||
SETTINGS['api_key'] = ''
|
||||
SETTINGS.save()
|
||||
LOGGER.info(f"{PREFIX}logged out ✅. To log in again, use 'yolo hub login'.")
|
||||
|
||||
|
||||
def reset_model(model_id=''):
|
||||
"""Reset a trained model to an untrained state."""
|
||||
r = requests.post(f'{HUB_API_ROOT}/model-reset', json={'apiKey': Auth().api_key, 'modelId': model_id})
|
||||
if r.status_code == 200:
|
||||
LOGGER.info(f'{PREFIX}Model reset successfully')
|
||||
return
|
||||
LOGGER.warning(f'{PREFIX}Model reset failure {r.status_code} {r.reason}')
|
||||
|
||||
|
||||
def export_fmts_hub():
|
||||
"""Returns a list of HUB-supported export formats."""
|
||||
from ultralytics.engine.exporter import export_formats
|
||||
return list(export_formats()['Argument'][1:]) + ['ultralytics_tflite', 'ultralytics_coreml']
|
||||
|
||||
|
||||
def export_model(model_id='', format='torchscript'):
|
||||
"""Export a model to all formats."""
|
||||
assert format in export_fmts_hub(), f"Unsupported export format '{format}', valid formats are {export_fmts_hub()}"
|
||||
r = requests.post(f'{HUB_API_ROOT}/v1/models/{model_id}/export',
|
||||
json={'format': format},
|
||||
headers={'x-api-key': Auth().api_key})
|
||||
assert r.status_code == 200, f'{PREFIX}{format} export failure {r.status_code} {r.reason}'
|
||||
LOGGER.info(f'{PREFIX}{format} export started ✅')
|
||||
|
||||
|
||||
def get_export(model_id='', format='torchscript'):
|
||||
"""Get an exported model dictionary with download URL."""
|
||||
assert format in export_fmts_hub(), f"Unsupported export format '{format}', valid formats are {export_fmts_hub()}"
|
||||
r = requests.post(f'{HUB_API_ROOT}/get-export',
|
||||
json={
|
||||
'apiKey': Auth().api_key,
|
||||
'modelId': model_id,
|
||||
'format': format})
|
||||
assert r.status_code == 200, f'{PREFIX}{format} get_export failure {r.status_code} {r.reason}'
|
||||
return r.json()
|
||||
|
||||
|
||||
def check_dataset(path='', task='detect'):
|
||||
"""
|
||||
Function for error-checking HUB dataset Zip file before upload. It checks a dataset for errors before it is
|
||||
uploaded to the HUB. Usage examples are given below.
|
||||
|
||||
Args:
|
||||
path (str, optional): Path to data.zip (with data.yaml inside data.zip). Defaults to ''.
|
||||
task (str, optional): Dataset task. Options are 'detect', 'segment', 'pose', 'classify'. Defaults to 'detect'.
|
||||
|
||||
Example:
|
||||
```python
|
||||
from ultralytics.hub import check_dataset
|
||||
|
||||
check_dataset('path/to/coco8.zip', task='detect') # detect dataset
|
||||
check_dataset('path/to/coco8-seg.zip', task='segment') # segment dataset
|
||||
check_dataset('path/to/coco8-pose.zip', task='pose') # pose dataset
|
||||
```
|
||||
"""
|
||||
HUBDatasetStats(path=path, task=task).get_json()
|
||||
LOGGER.info(f'Checks completed correctly ✅. Upload this dataset to {HUB_WEB_ROOT}/datasets/.')
|
BIN
ytracking/ultralytics/hub/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
ytracking/ultralytics/hub/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
ytracking/ultralytics/hub/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
ytracking/ultralytics/hub/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
ytracking/ultralytics/hub/__pycache__/auth.cpython-38.pyc
Normal file
BIN
ytracking/ultralytics/hub/__pycache__/auth.cpython-38.pyc
Normal file
Binary file not shown.
BIN
ytracking/ultralytics/hub/__pycache__/auth.cpython-39.pyc
Normal file
BIN
ytracking/ultralytics/hub/__pycache__/auth.cpython-39.pyc
Normal file
Binary file not shown.
BIN
ytracking/ultralytics/hub/__pycache__/utils.cpython-38.pyc
Normal file
BIN
ytracking/ultralytics/hub/__pycache__/utils.cpython-38.pyc
Normal file
Binary file not shown.
BIN
ytracking/ultralytics/hub/__pycache__/utils.cpython-39.pyc
Normal file
BIN
ytracking/ultralytics/hub/__pycache__/utils.cpython-39.pyc
Normal file
Binary file not shown.
119
ytracking/ultralytics/hub/auth.py
Normal file
119
ytracking/ultralytics/hub/auth.py
Normal file
@ -0,0 +1,119 @@
|
||||
# Ultralytics YOLO 🚀, AGPL-3.0 license
|
||||
|
||||
import requests
|
||||
|
||||
from ultralytics.hub.utils import HUB_API_ROOT, HUB_WEB_ROOT, PREFIX, request_with_credentials
|
||||
from ultralytics.utils import LOGGER, SETTINGS, emojis, is_colab
|
||||
|
||||
API_KEY_URL = f'{HUB_WEB_ROOT}/settings?tab=api+keys'
|
||||
|
||||
|
||||
class Auth:
|
||||
id_token = api_key = model_key = False
|
||||
|
||||
def __init__(self, api_key='', verbose=False):
|
||||
"""
|
||||
Initialize the Auth class with an optional API key.
|
||||
|
||||
Args:
|
||||
api_key (str, optional): May be an API key or a combination API key and model ID, i.e. key_id
|
||||
"""
|
||||
# Split the input API key in case it contains a combined key_model and keep only the API key part
|
||||
api_key = api_key.split('_')[0]
|
||||
|
||||
# Set API key attribute as value passed or SETTINGS API key if none passed
|
||||
self.api_key = api_key or SETTINGS.get('api_key', '')
|
||||
|
||||
# If an API key is provided
|
||||
if self.api_key:
|
||||
# If the provided API key matches the API key in the SETTINGS
|
||||
if self.api_key == SETTINGS.get('api_key'):
|
||||
# Log that the user is already logged in
|
||||
if verbose:
|
||||
LOGGER.info(f'{PREFIX}Authenticated ✅')
|
||||
return
|
||||
else:
|
||||
# Attempt to authenticate with the provided API key
|
||||
success = self.authenticate()
|
||||
# If the API key is not provided and the environment is a Google Colab notebook
|
||||
elif is_colab():
|
||||
# Attempt to authenticate using browser cookies
|
||||
success = self.auth_with_cookies()
|
||||
else:
|
||||
# Request an API key
|
||||
success = self.request_api_key()
|
||||
|
||||
# Update SETTINGS with the new API key after successful authentication
|
||||
if success:
|
||||
SETTINGS.update({'api_key': self.api_key})
|
||||
# Log that the new login was successful
|
||||
if verbose:
|
||||
LOGGER.info(f'{PREFIX}New authentication successful ✅')
|
||||
elif verbose:
|
||||
LOGGER.info(f'{PREFIX}Retrieve API key from {API_KEY_URL}')
|
||||
|
||||
def request_api_key(self, max_attempts=3):
|
||||
"""
|
||||
Prompt the user to input their API key. Returns the model ID.
|
||||
"""
|
||||
import getpass
|
||||
for attempts in range(max_attempts):
|
||||
LOGGER.info(f'{PREFIX}Login. Attempt {attempts + 1} of {max_attempts}')
|
||||
input_key = getpass.getpass(f'Enter API key from {API_KEY_URL} ')
|
||||
self.api_key = input_key.split('_')[0] # remove model id if present
|
||||
if self.authenticate():
|
||||
return True
|
||||
raise ConnectionError(emojis(f'{PREFIX}Failed to authenticate ❌'))
|
||||
|
||||
def authenticate(self) -> bool:
|
||||
"""
|
||||
Attempt to authenticate with the server using either id_token or API key.
|
||||
|
||||
Returns:
|
||||
bool: True if authentication is successful, False otherwise.
|
||||
"""
|
||||
try:
|
||||
if header := self.get_auth_header():
|
||||
r = requests.post(f'{HUB_API_ROOT}/v1/auth', headers=header)
|
||||
if not r.json().get('success', False):
|
||||
raise ConnectionError('Unable to authenticate.')
|
||||
return True
|
||||
raise ConnectionError('User has not authenticated locally.')
|
||||
except ConnectionError:
|
||||
self.id_token = self.api_key = False # reset invalid
|
||||
LOGGER.warning(f'{PREFIX}Invalid API key ⚠️')
|
||||
return False
|
||||
|
||||
def auth_with_cookies(self) -> bool:
|
||||
"""
|
||||
Attempt to fetch authentication via cookies and set id_token.
|
||||
User must be logged in to HUB and running in a supported browser.
|
||||
|
||||
Returns:
|
||||
bool: True if authentication is successful, False otherwise.
|
||||
"""
|
||||
if not is_colab():
|
||||
return False # Currently only works with Colab
|
||||
try:
|
||||
authn = request_with_credentials(f'{HUB_API_ROOT}/v1/auth/auto')
|
||||
if authn.get('success', False):
|
||||
self.id_token = authn.get('data', {}).get('idToken', None)
|
||||
self.authenticate()
|
||||
return True
|
||||
raise ConnectionError('Unable to fetch browser authentication details.')
|
||||
except ConnectionError:
|
||||
self.id_token = False # reset invalid
|
||||
return False
|
||||
|
||||
def get_auth_header(self):
|
||||
"""
|
||||
Get the authentication header for making API requests.
|
||||
|
||||
Returns:
|
||||
(dict): The authentication header if id_token or API key is set, None otherwise.
|
||||
"""
|
||||
if self.id_token:
|
||||
return {'authorization': f'Bearer {self.id_token}'}
|
||||
elif self.api_key:
|
||||
return {'x-api-key': self.api_key}
|
||||
# else returns None
|
190
ytracking/ultralytics/hub/session.py
Normal file
190
ytracking/ultralytics/hub/session.py
Normal file
@ -0,0 +1,190 @@
|
||||
# Ultralytics YOLO 🚀, AGPL-3.0 license
|
||||
|
||||
import signal
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from time import sleep
|
||||
|
||||
import requests
|
||||
|
||||
from ultralytics.hub.utils import HUB_API_ROOT, HUB_WEB_ROOT, PREFIX, smart_request
|
||||
from ultralytics.utils import LOGGER, __version__, checks, emojis, is_colab, threaded
|
||||
from ultralytics.utils.errors import HUBModelError
|
||||
|
||||
AGENT_NAME = f'python-{__version__}-colab' if is_colab() else f'python-{__version__}-local'
|
||||
|
||||
|
||||
class HUBTrainingSession:
|
||||
"""
|
||||
HUB training session for Ultralytics HUB YOLO models. Handles model initialization, heartbeats, and checkpointing.
|
||||
|
||||
Args:
|
||||
url (str): Model identifier used to initialize the HUB training session.
|
||||
|
||||
Attributes:
|
||||
agent_id (str): Identifier for the instance communicating with the server.
|
||||
model_id (str): Identifier for the YOLOv5 model being trained.
|
||||
model_url (str): URL for the model in Ultralytics HUB.
|
||||
api_url (str): API URL for the model in Ultralytics HUB.
|
||||
auth_header (dict): Authentication header for the Ultralytics HUB API requests.
|
||||
rate_limits (dict): Rate limits for different API calls (in seconds).
|
||||
timers (dict): Timers for rate limiting.
|
||||
metrics_queue (dict): Queue for the model's metrics.
|
||||
model (dict): Model data fetched from Ultralytics HUB.
|
||||
alive (bool): Indicates if the heartbeat loop is active.
|
||||
"""
|
||||
|
||||
def __init__(self, url):
|
||||
"""
|
||||
Initialize the HUBTrainingSession with the provided model identifier.
|
||||
|
||||
Args:
|
||||
url (str): Model identifier used to initialize the HUB training session.
|
||||
It can be a URL string or a model key with specific format.
|
||||
|
||||
Raises:
|
||||
ValueError: If the provided model identifier is invalid.
|
||||
ConnectionError: If connecting with global API key is not supported.
|
||||
"""
|
||||
|
||||
from ultralytics.hub.auth import Auth
|
||||
|
||||
# Parse input
|
||||
if url.startswith(f'{HUB_WEB_ROOT}/models/'):
|
||||
url = url.split(f'{HUB_WEB_ROOT}/models/')[-1]
|
||||
if [len(x) for x in url.split('_')] == [42, 20]:
|
||||
key, model_id = url.split('_')
|
||||
elif len(url) == 20:
|
||||
key, model_id = '', url
|
||||
else:
|
||||
raise HUBModelError(f"model='{url}' not found. Check format is correct, i.e. "
|
||||
f"model='{HUB_WEB_ROOT}/models/MODEL_ID' and try again.")
|
||||
|
||||
# Authorize
|
||||
auth = Auth(key)
|
||||
self.agent_id = None # identifies which instance is communicating with server
|
||||
self.model_id = model_id
|
||||
self.model_url = f'{HUB_WEB_ROOT}/models/{model_id}'
|
||||
self.api_url = f'{HUB_API_ROOT}/v1/models/{model_id}'
|
||||
self.auth_header = auth.get_auth_header()
|
||||
self.rate_limits = {'metrics': 3.0, 'ckpt': 900.0, 'heartbeat': 300.0} # rate limits (seconds)
|
||||
self.timers = {} # rate limit timers (seconds)
|
||||
self.metrics_queue = {} # metrics queue
|
||||
self.model = self._get_model()
|
||||
self.alive = True
|
||||
self._start_heartbeat() # start heartbeats
|
||||
self._register_signal_handlers()
|
||||
LOGGER.info(f'{PREFIX}View model at {self.model_url} 🚀')
|
||||
|
||||
def _register_signal_handlers(self):
|
||||
"""Register signal handlers for SIGTERM and SIGINT signals to gracefully handle termination."""
|
||||
signal.signal(signal.SIGTERM, self._handle_signal)
|
||||
signal.signal(signal.SIGINT, self._handle_signal)
|
||||
|
||||
def _handle_signal(self, signum, frame):
|
||||
"""
|
||||
Handle kill signals and prevent heartbeats from being sent on Colab after termination.
|
||||
This method does not use frame, it is included as it is passed by signal.
|
||||
"""
|
||||
if self.alive is True:
|
||||
LOGGER.info(f'{PREFIX}Kill signal received! ❌')
|
||||
self._stop_heartbeat()
|
||||
sys.exit(signum)
|
||||
|
||||
def _stop_heartbeat(self):
|
||||
"""Terminate the heartbeat loop."""
|
||||
self.alive = False
|
||||
|
||||
def upload_metrics(self):
|
||||
"""Upload model metrics to Ultralytics HUB."""
|
||||
payload = {'metrics': self.metrics_queue.copy(), 'type': 'metrics'}
|
||||
smart_request('post', self.api_url, json=payload, headers=self.auth_header, code=2)
|
||||
|
||||
def _get_model(self):
|
||||
"""Fetch and return model data from Ultralytics HUB."""
|
||||
api_url = f'{HUB_API_ROOT}/v1/models/{self.model_id}'
|
||||
|
||||
try:
|
||||
response = smart_request('get', api_url, headers=self.auth_header, thread=False, code=0)
|
||||
data = response.json().get('data', None)
|
||||
|
||||
if data.get('status', None) == 'trained':
|
||||
raise ValueError(emojis(f'Model is already trained and uploaded to {self.model_url} 🚀'))
|
||||
|
||||
if not data.get('data', None):
|
||||
raise ValueError('Dataset may still be processing. Please wait a minute and try again.') # RF fix
|
||||
self.model_id = data['id']
|
||||
|
||||
if data['status'] == 'new': # new model to start training
|
||||
self.train_args = {
|
||||
# TODO: deprecate 'batch_size' key for 'batch' in 3Q23
|
||||
'batch': data['batch' if ('batch' in data) else 'batch_size'],
|
||||
'epochs': data['epochs'],
|
||||
'imgsz': data['imgsz'],
|
||||
'patience': data['patience'],
|
||||
'device': data['device'],
|
||||
'cache': data['cache'],
|
||||
'data': data['data']}
|
||||
self.model_file = data.get('cfg') or data.get('weights') # cfg for pretrained=False
|
||||
self.model_file = checks.check_yolov5u_filename(self.model_file, verbose=False) # YOLOv5->YOLOv5u
|
||||
elif data['status'] == 'training': # existing model to resume training
|
||||
self.train_args = {'data': data['data'], 'resume': True}
|
||||
self.model_file = data['resume']
|
||||
|
||||
return data
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
raise ConnectionRefusedError('ERROR: The HUB server is not online. Please try again later.') from e
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
def upload_model(self, epoch, weights, is_best=False, map=0.0, final=False):
|
||||
"""
|
||||
Upload a model checkpoint to Ultralytics HUB.
|
||||
|
||||
Args:
|
||||
epoch (int): The current training epoch.
|
||||
weights (str): Path to the model weights file.
|
||||
is_best (bool): Indicates if the current model is the best one so far.
|
||||
map (float): Mean average precision of the model.
|
||||
final (bool): Indicates if the model is the final model after training.
|
||||
"""
|
||||
if Path(weights).is_file():
|
||||
with open(weights, 'rb') as f:
|
||||
file = f.read()
|
||||
else:
|
||||
LOGGER.warning(f'{PREFIX}WARNING ⚠️ Model upload issue. Missing model {weights}.')
|
||||
file = None
|
||||
url = f'{self.api_url}/upload'
|
||||
# url = 'http://httpbin.org/post' # for debug
|
||||
data = {'epoch': epoch}
|
||||
if final:
|
||||
data.update({'type': 'final', 'map': map})
|
||||
smart_request('post',
|
||||
url,
|
||||
data=data,
|
||||
files={'best.pt': file},
|
||||
headers=self.auth_header,
|
||||
retry=10,
|
||||
timeout=3600,
|
||||
thread=False,
|
||||
progress=True,
|
||||
code=4)
|
||||
else:
|
||||
data.update({'type': 'epoch', 'isBest': bool(is_best)})
|
||||
smart_request('post', url, data=data, files={'last.pt': file}, headers=self.auth_header, code=3)
|
||||
|
||||
@threaded
|
||||
def _start_heartbeat(self):
|
||||
"""Begin a threaded heartbeat loop to report the agent's status to Ultralytics HUB."""
|
||||
while self.alive:
|
||||
r = smart_request('post',
|
||||
f'{HUB_API_ROOT}/v1/agent/heartbeat/models/{self.model_id}',
|
||||
json={
|
||||
'agent': AGENT_NAME,
|
||||
'agentId': self.agent_id},
|
||||
headers=self.auth_header,
|
||||
retry=0,
|
||||
code=5,
|
||||
thread=False) # already in a thread
|
||||
self.agent_id = r.json().get('data', {}).get('agentId', None)
|
||||
sleep(self.rate_limits['heartbeat'])
|
222
ytracking/ultralytics/hub/utils.py
Normal file
222
ytracking/ultralytics/hub/utils.py
Normal file
@ -0,0 +1,222 @@
|
||||
# Ultralytics YOLO 🚀, AGPL-3.0 license
|
||||
|
||||
import os
|
||||
import platform
|
||||
import random
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
from ultralytics.utils import (ENVIRONMENT, LOGGER, ONLINE, RANK, SETTINGS, TESTS_RUNNING, TQDM, TryExcept, __version__,
|
||||
colorstr, get_git_origin_url, is_colab, is_git_dir, is_pip_package)
|
||||
from ultralytics.utils.downloads import GITHUB_ASSETS_NAMES
|
||||
|
||||
PREFIX = colorstr('Ultralytics HUB: ')
|
||||
HELP_MSG = 'If this issue persists please visit https://github.com/ultralytics/hub/issues for assistance.'
|
||||
HUB_API_ROOT = os.environ.get('ULTRALYTICS_HUB_API', 'https://api.ultralytics.com')
|
||||
HUB_WEB_ROOT = os.environ.get('ULTRALYTICS_HUB_WEB', 'https://hub.ultralytics.com')
|
||||
|
||||
|
||||
def request_with_credentials(url: str) -> any:
|
||||
"""
|
||||
Make an AJAX request with cookies attached in a Google Colab environment.
|
||||
|
||||
Args:
|
||||
url (str): The URL to make the request to.
|
||||
|
||||
Returns:
|
||||
(any): The response data from the AJAX request.
|
||||
|
||||
Raises:
|
||||
OSError: If the function is not run in a Google Colab environment.
|
||||
"""
|
||||
if not is_colab():
|
||||
raise OSError('request_with_credentials() must run in a Colab environment')
|
||||
from google.colab import output # noqa
|
||||
from IPython import display # noqa
|
||||
display.display(
|
||||
display.Javascript("""
|
||||
window._hub_tmp = new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject("Failed authenticating existing browser session"), 5000)
|
||||
fetch("%s", {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
})
|
||||
.then((response) => resolve(response.json()))
|
||||
.then((json) => {
|
||||
clearTimeout(timeout);
|
||||
}).catch((err) => {
|
||||
clearTimeout(timeout);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
""" % url))
|
||||
return output.eval_js('_hub_tmp')
|
||||
|
||||
|
||||
def requests_with_progress(method, url, **kwargs):
|
||||
"""
|
||||
Make an HTTP request using the specified method and URL, with an optional progress bar.
|
||||
|
||||
Args:
|
||||
method (str): The HTTP method to use (e.g. 'GET', 'POST').
|
||||
url (str): The URL to send the request to.
|
||||
**kwargs (dict): Additional keyword arguments to pass to the underlying `requests.request` function.
|
||||
|
||||
Returns:
|
||||
(requests.Response): The response object from the HTTP request.
|
||||
|
||||
Note:
|
||||
If 'progress' is set to True, the progress bar will display the download progress
|
||||
for responses with a known content length.
|
||||
"""
|
||||
progress = kwargs.pop('progress', False)
|
||||
if not progress:
|
||||
return requests.request(method, url, **kwargs)
|
||||
response = requests.request(method, url, stream=True, **kwargs)
|
||||
total = int(response.headers.get('content-length', 0)) # total size
|
||||
try:
|
||||
pbar = TQDM(total=total, unit='B', unit_scale=True, unit_divisor=1024)
|
||||
for data in response.iter_content(chunk_size=1024):
|
||||
pbar.update(len(data))
|
||||
pbar.close()
|
||||
except requests.exceptions.ChunkedEncodingError: # avoid 'Connection broken: IncompleteRead' warnings
|
||||
response.close()
|
||||
return response
|
||||
|
||||
|
||||
def smart_request(method, url, retry=3, timeout=30, thread=True, code=-1, verbose=True, progress=False, **kwargs):
|
||||
"""
|
||||
Makes an HTTP request using the 'requests' library, with exponential backoff retries up to a specified timeout.
|
||||
|
||||
Args:
|
||||
method (str): The HTTP method to use for the request. Choices are 'post' and 'get'.
|
||||
url (str): The URL to make the request to.
|
||||
retry (int, optional): Number of retries to attempt before giving up. Default is 3.
|
||||
timeout (int, optional): Timeout in seconds after which the function will give up retrying. Default is 30.
|
||||
thread (bool, optional): Whether to execute the request in a separate daemon thread. Default is True.
|
||||
code (int, optional): An identifier for the request, used for logging purposes. Default is -1.
|
||||
verbose (bool, optional): A flag to determine whether to print out to console or not. Default is True.
|
||||
progress (bool, optional): Whether to show a progress bar during the request. Default is False.
|
||||
**kwargs (dict): Keyword arguments to be passed to the requests function specified in method.
|
||||
|
||||
Returns:
|
||||
(requests.Response): The HTTP response object. If the request is executed in a separate thread, returns None.
|
||||
"""
|
||||
retry_codes = (408, 500) # retry only these codes
|
||||
|
||||
@TryExcept(verbose=verbose)
|
||||
def func(func_method, func_url, **func_kwargs):
|
||||
"""Make HTTP requests with retries and timeouts, with optional progress tracking."""
|
||||
r = None # response
|
||||
t0 = time.time() # initial time for timer
|
||||
for i in range(retry + 1):
|
||||
if (time.time() - t0) > timeout:
|
||||
break
|
||||
r = requests_with_progress(func_method, func_url, **func_kwargs) # i.e. get(url, data, json, files)
|
||||
if r.status_code < 300: # return codes in the 2xx range are generally considered "good" or "successful"
|
||||
break
|
||||
try:
|
||||
m = r.json().get('message', 'No JSON message.')
|
||||
except AttributeError:
|
||||
m = 'Unable to read JSON.'
|
||||
if i == 0:
|
||||
if r.status_code in retry_codes:
|
||||
m += f' Retrying {retry}x for {timeout}s.' if retry else ''
|
||||
elif r.status_code == 429: # rate limit
|
||||
h = r.headers # response headers
|
||||
m = f"Rate limit reached ({h['X-RateLimit-Remaining']}/{h['X-RateLimit-Limit']}). " \
|
||||
f"Please retry after {h['Retry-After']}s."
|
||||
if verbose:
|
||||
LOGGER.warning(f'{PREFIX}{m} {HELP_MSG} ({r.status_code} #{code})')
|
||||
if r.status_code not in retry_codes:
|
||||
return r
|
||||
time.sleep(2 ** i) # exponential standoff
|
||||
return r
|
||||
|
||||
args = method, url
|
||||
kwargs['progress'] = progress
|
||||
if thread:
|
||||
threading.Thread(target=func, args=args, kwargs=kwargs, daemon=True).start()
|
||||
else:
|
||||
return func(*args, **kwargs)
|
||||
|
||||
|
||||
class Events:
|
||||
"""
|
||||
A class for collecting anonymous event analytics. Event analytics are enabled when sync=True in settings and
|
||||
disabled when sync=False. Run 'yolo settings' to see and update settings YAML file.
|
||||
|
||||
Attributes:
|
||||
url (str): The URL to send anonymous events.
|
||||
rate_limit (float): The rate limit in seconds for sending events.
|
||||
metadata (dict): A dictionary containing metadata about the environment.
|
||||
enabled (bool): A flag to enable or disable Events based on certain conditions.
|
||||
"""
|
||||
|
||||
url = 'https://www.google-analytics.com/mp/collect?measurement_id=G-X8NCJYTQXM&api_secret=QLQrATrNSwGRFRLE-cbHJw'
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initializes the Events object with default values for events, rate_limit, and metadata.
|
||||
"""
|
||||
self.events = [] # events list
|
||||
self.rate_limit = 60.0 # rate limit (seconds)
|
||||
self.t = 0.0 # rate limit timer (seconds)
|
||||
self.metadata = {
|
||||
'cli': Path(sys.argv[0]).name == 'yolo',
|
||||
'install': 'git' if is_git_dir() else 'pip' if is_pip_package() else 'other',
|
||||
'python': '.'.join(platform.python_version_tuple()[:2]), # i.e. 3.10
|
||||
'version': __version__,
|
||||
'env': ENVIRONMENT,
|
||||
'session_id': round(random.random() * 1E15),
|
||||
'engagement_time_msec': 1000}
|
||||
self.enabled = \
|
||||
SETTINGS['sync'] and \
|
||||
RANK in (-1, 0) and \
|
||||
not TESTS_RUNNING and \
|
||||
ONLINE and \
|
||||
(is_pip_package() or get_git_origin_url() == 'https://github.com/ultralytics/ultralytics.git')
|
||||
|
||||
def __call__(self, cfg):
|
||||
"""
|
||||
Attempts to add a new event to the events list and send events if the rate limit is reached.
|
||||
|
||||
Args:
|
||||
cfg (IterableSimpleNamespace): The configuration object containing mode and task information.
|
||||
"""
|
||||
if not self.enabled:
|
||||
# Events disabled, do nothing
|
||||
return
|
||||
|
||||
# Attempt to add to events
|
||||
if len(self.events) < 25: # Events list limited to 25 events (drop any events past this)
|
||||
params = {
|
||||
**self.metadata, 'task': cfg.task,
|
||||
'model': cfg.model if cfg.model in GITHUB_ASSETS_NAMES else 'custom'}
|
||||
if cfg.mode == 'export':
|
||||
params['format'] = cfg.format
|
||||
self.events.append({'name': cfg.mode, 'params': params})
|
||||
|
||||
# Check rate limit
|
||||
t = time.time()
|
||||
if (t - self.t) < self.rate_limit:
|
||||
# Time is under rate limiter, wait to send
|
||||
return
|
||||
|
||||
# Time is over rate limiter, send now
|
||||
data = {'client_id': SETTINGS['uuid'], 'events': self.events} # SHA-256 anonymized UUID hash and events list
|
||||
|
||||
# POST equivalent to requests.post(self.url, json=data)
|
||||
smart_request('post', self.url, json=data, retry=0, verbose=False)
|
||||
|
||||
# Reset events and rate limit timer
|
||||
self.events = []
|
||||
self.t = t
|
||||
|
||||
|
||||
# Run below code on hub/utils init -------------------------------------------------------------------------------------
|
||||
events = Events()
|
Reference in New Issue
Block a user