123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- # Ultralytics YOLO 🚀, AGPL-3.0 license
- import os
- import platform
- import random
- import threading
- import time
- from pathlib import Path
- import requests
- from ultralytics.utils import (
- ARGV,
- ENVIRONMENT,
- IS_COLAB,
- IS_GIT_DIR,
- IS_PIP_PACKAGE,
- LOGGER,
- ONLINE,
- RANK,
- SETTINGS,
- TESTS_RUNNING,
- TQDM,
- TryExcept,
- __version__,
- colorstr,
- get_git_origin_url,
- )
- from ultralytics.utils.downloads import GITHUB_ASSETS_NAMES
- 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")
- PREFIX = colorstr("Ultralytics HUB: ")
- HELP_MSG = "If this issue persists please visit https://github.com/ultralytics/hub/issues for assistance."
- 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 (any): 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.
- - If 'progress' is a number then progress bar will display assuming content length = progress.
- """
- 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) if isinstance(progress, bool) else progress) # 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 (any): 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(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()
|