123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- # 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 tqdm import tqdm
- from ultralytics.utils import (ENVIRONMENT, LOGGER, ONLINE, RANK, SETTINGS, TESTS_RUNNING, TQDM_BAR_FORMAT, 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, bar_format=TQDM_BAR_FORMAT)
- 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()
|