Refactor HTTP related operations

This commit is contained in:
Ivan Kravets
2020-08-22 17:48:49 +03:00
parent aa186382a8
commit d92c1d3442
29 changed files with 206 additions and 233 deletions

View File

@ -15,4 +15,8 @@ disable=
useless-object-inheritance, useless-object-inheritance,
useless-import-alias, useless-import-alias,
fixme, fixme,
bad-option-value bad-option-value,
; PY2 Compat
super-with-arguments,
raise-missing-from

View File

@ -26,7 +26,7 @@ from os import environ, getenv, listdir, remove
from os.path import dirname, isdir, isfile, join, realpath from os.path import dirname, isdir, isfile, join, realpath
from time import time from time import time
from platformio import __version__, exception, fs, proc, util from platformio import __version__, exception, fs, proc
from platformio.compat import WINDOWS, dump_json_to_unicode, hashlib_encode_data from platformio.compat import WINDOWS, dump_json_to_unicode, hashlib_encode_data
from platformio.package.lockfile import LockFile from platformio.package.lockfile import LockFile
from platformio.project.helpers import ( from platformio.project.helpers import (
@ -394,6 +394,9 @@ def is_disabled_progressbar():
def get_cid(): def get_cid():
# pylint: disable=import-outside-toplevel
from platformio.clients.http import fetch_remote_content
cid = get_state_item("cid") cid = get_state_item("cid")
if cid: if cid:
return cid return cid
@ -403,7 +406,7 @@ def get_cid():
elif getenv("CHE_API", getenv("CHE_API_ENDPOINT")): elif getenv("CHE_API", getenv("CHE_API_ENDPOINT")):
try: try:
uid = json.loads( uid = json.loads(
util.fetch_remote_content( fetch_remote_content(
"{api}/user?token={token}".format( "{api}/user?token={token}".format(
api=getenv("CHE_API", getenv("CHE_API_ENDPOINT")), api=getenv("CHE_API", getenv("CHE_API_ENDPOINT")),
token=getenv("USER_TOKEN"), token=getenv("USER_TOKEN"),

View File

@ -67,7 +67,7 @@ class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
token = self.fetch_authentication_token() token = self.fetch_authentication_token()
headers["Authorization"] = "Bearer %s" % token headers["Authorization"] = "Bearer %s" % token
kwargs["headers"] = headers kwargs["headers"] = headers
return self.request_json_data(*args, **kwargs) return self.fetch_json_data(*args, **kwargs)
def login(self, username, password): def login(self, username, password):
try: try:
@ -79,7 +79,7 @@ class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
app.get_state_item("account", {}).get("email", "") app.get_state_item("account", {}).get("email", "")
) )
data = self.request_json_data( data = self.fetch_json_data(
"post", "/v1/login", data={"username": username, "password": password}, "post", "/v1/login", data={"username": username, "password": password},
) )
app.set_state_item("account", data) app.set_state_item("account", data)
@ -95,7 +95,7 @@ class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
app.get_state_item("account", {}).get("email", "") app.get_state_item("account", {}).get("email", "")
) )
result = self.request_json_data( result = self.fetch_json_data(
"post", "post",
"/v1/login/code", "/v1/login/code",
data={"client_id": client_id, "code": code, "redirect_uri": redirect_uri}, data={"client_id": client_id, "code": code, "redirect_uri": redirect_uri},
@ -107,7 +107,7 @@ class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
refresh_token = self.get_refresh_token() refresh_token = self.get_refresh_token()
self.delete_local_session() self.delete_local_session()
try: try:
self.request_json_data( self.fetch_json_data(
"post", "/v1/logout", data={"refresh_token": refresh_token}, "post", "/v1/logout", data={"refresh_token": refresh_token},
) )
except AccountError: except AccountError:
@ -133,7 +133,7 @@ class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
app.get_state_item("account", {}).get("email", "") app.get_state_item("account", {}).get("email", "")
) )
return self.request_json_data( return self.fetch_json_data(
"post", "post",
"/v1/registration", "/v1/registration",
data={ data={
@ -153,7 +153,7 @@ class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
).get("auth_token") ).get("auth_token")
def forgot_password(self, username): def forgot_password(self, username):
return self.request_json_data( return self.fetch_json_data(
"post", "/v1/forgot", data={"username": username}, "post", "/v1/forgot", data={"username": username},
) )
@ -278,7 +278,7 @@ class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
return auth.get("access_token") return auth.get("access_token")
if auth.get("refresh_token"): if auth.get("refresh_token"):
try: try:
data = self.request_json_data( data = self.fetch_json_data(
"post", "post",
"/v1/login", "/v1/login",
headers={ headers={

View File

@ -12,11 +12,24 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import json
import os
import socket
import requests.adapters import requests.adapters
from requests.packages.urllib3.util.retry import Retry # pylint:disable=import-error from requests.packages.urllib3.util.retry import Retry # pylint:disable=import-error
from platformio import DEFAULT_REQUESTS_TIMEOUT, app, util from platformio import DEFAULT_REQUESTS_TIMEOUT, app, util
from platformio.exception import PlatformioException from platformio.exception import PlatformioException, UserSideException
PING_REMOTE_HOSTS = [
"140.82.118.3", # Github.com
"35.231.145.151", # Gitlab.com
"88.198.170.159", # platformio.org
"github.com",
"platformio.org",
]
class HTTPClientError(PlatformioException): class HTTPClientError(PlatformioException):
@ -29,6 +42,15 @@ class HTTPClientError(PlatformioException):
return self.message return self.message
class InternetIsOffline(UserSideException):
MESSAGE = (
"You are not connected to the Internet.\n"
"PlatformIO needs the Internet connection to"
" download dependent packages or to work with PIO Account."
)
class HTTPClient(object): class HTTPClient(object):
def __init__( def __init__(
self, base_url, self, base_url,
@ -57,7 +79,7 @@ class HTTPClient(object):
def send_request(self, method, path, **kwargs): def send_request(self, method, path, **kwargs):
# check Internet before and resolve issue with 60 seconds timeout # check Internet before and resolve issue with 60 seconds timeout
# print(self, method, path, kwargs) # print(self, method, path, kwargs)
util.internet_on(raise_exception=True) ensure_internet_on(raise_exception=True)
# set default timeout # set default timeout
if "timeout" not in kwargs: if "timeout" not in kwargs:
@ -68,9 +90,18 @@ class HTTPClient(object):
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e: except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
raise HTTPClientError(str(e)) raise HTTPClientError(str(e))
def request_json_data(self, *args, **kwargs): def fetch_json_data(self, *args, **kwargs):
response = self.send_request(*args, **kwargs) cache_valid = kwargs.pop("cache_valid") if "cache_valid" in kwargs else None
return self.raise_error_from_response(response) if not cache_valid:
return self.raise_error_from_response(self.send_request(*args, **kwargs))
cache_key = app.ContentCache.key_from_args(*args, kwargs)
with app.ContentCache() as cc:
result = cc.get(cache_key)
if result is not None:
return json.loads(result)
response = self.send_request(*args, **kwargs)
cc.set(cache_key, response.text, cache_valid)
return self.raise_error_from_response(response)
@staticmethod @staticmethod
def raise_error_from_response(response, expected_codes=(200, 201, 202)): def raise_error_from_response(response, expected_codes=(200, 201, 202)):
@ -84,3 +115,48 @@ class HTTPClient(object):
except (KeyError, ValueError): except (KeyError, ValueError):
message = response.text message = response.text
raise HTTPClientError(message, response) raise HTTPClientError(message, response)
#
# Helpers
#
@util.memoized(expire="10s")
def _internet_on():
timeout = 2
socket.setdefaulttimeout(timeout)
for host in PING_REMOTE_HOSTS:
try:
for var in ("HTTP_PROXY", "HTTPS_PROXY"):
if not os.getenv(var) and not os.getenv(var.lower()):
continue
requests.get("http://%s" % host, allow_redirects=False, timeout=timeout)
return True
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, 80))
s.close()
return True
except: # pylint: disable=bare-except
pass
return False
def ensure_internet_on(raise_exception=False):
result = _internet_on()
if raise_exception and not result:
raise InternetIsOffline()
return result
def fetch_remote_content(*args, **kwargs):
kwargs["headers"] = kwargs.get("headers", {})
if "User-Agent" not in kwargs["headers"]:
kwargs["headers"]["User-Agent"] = app.get_user_agent()
if "timeout" not in kwargs:
kwargs["timeout"] = DEFAULT_REQUESTS_TIMEOUT
r = requests.get(*args, **kwargs)
r.raise_for_status()
return r.text

View File

@ -21,7 +21,7 @@ from os.path import isfile
import click import click
from platformio import app, exception, fs, proc, util from platformio import app, exception, fs, proc
from platformio.commands.debug import helpers from platformio.commands.debug import helpers
from platformio.commands.debug.exception import DebugInvalidOptionsError from platformio.commands.debug.exception import DebugInvalidOptionsError
from platformio.package.manager.core import inject_contrib_pysite from platformio.package.manager.core import inject_contrib_pysite
@ -130,7 +130,7 @@ def cli(ctx, project_dir, project_conf, environment, verbose, interface, __unpro
nl=False, nl=False,
) )
stream = helpers.GDBMIConsoleStream() stream = helpers.GDBMIConsoleStream()
with util.capture_std_streams(stream): with proc.capture_std_streams(stream):
helpers.predebug_project(ctx, project_dir, env_name, preload, verbose) helpers.predebug_project(ctx, project_dir, env_name, preload, verbose)
stream.close() stream.close()
else: else:

View File

@ -23,6 +23,7 @@ import click
from twisted.internet import defer # pylint: disable=import-error from twisted.internet import defer # pylint: disable=import-error
from platformio import DEFAULT_REQUESTS_TIMEOUT, app, fs, util from platformio import DEFAULT_REQUESTS_TIMEOUT, app, fs, util
from platformio.clients.http import ensure_internet_on
from platformio.commands.home import helpers from platformio.commands.home import helpers
from platformio.compat import PY2, get_filesystem_encoding, glob_recursive from platformio.compat import PY2, get_filesystem_encoding, glob_recursive
@ -47,7 +48,7 @@ class OSRPC(object):
defer.returnValue(result) defer.returnValue(result)
# check internet before and resolve issue with 60 seconds timeout # check internet before and resolve issue with 60 seconds timeout
util.internet_on(raise_exception=True) ensure_internet_on(raise_exception=True)
session = helpers.requests_session() session = helpers.requests_session()
if data: if data:

View File

@ -28,7 +28,7 @@ from platformio.commands.lib.helpers import (
save_project_libdeps, save_project_libdeps,
) )
from platformio.compat import dump_json_to_unicode from platformio.compat import dump_json_to_unicode
from platformio.package.exception import UnknownPackageError from platformio.package.exception import NotGlobalLibDir, UnknownPackageError
from platformio.package.manager.library import LibraryPackageManager from platformio.package.manager.library import LibraryPackageManager
from platformio.package.meta import PackageItem, PackageSpec from platformio.package.meta import PackageItem, PackageSpec
from platformio.proc import is_ci from platformio.proc import is_ci
@ -97,7 +97,7 @@ def cli(ctx, **options):
) )
if not storage_dirs: if not storage_dirs:
raise exception.NotGlobalLibDir( raise NotGlobalLibDir(
get_project_dir(), get_project_global_lib_dir(), ctx.invoked_subcommand get_project_dir(), get_project_global_lib_dir(), ctx.invoked_subcommand
) )

View File

@ -20,7 +20,7 @@ from io import BytesIO
from twisted.spread import pb # pylint: disable=import-error from twisted.spread import pb # pylint: disable=import-error
from platformio import util from platformio import fs
from platformio.commands.remote.client.async_base import AsyncClientBase from platformio.commands.remote.client.async_base import AsyncClientBase
from platformio.commands.remote.projectsync import PROJECT_SYNC_STAGE, ProjectSync from platformio.commands.remote.projectsync import PROJECT_SYNC_STAGE, ProjectSync
from platformio.compat import hashlib_encode_data from platformio.compat import hashlib_encode_data
@ -64,7 +64,7 @@ class RunOrTestClient(AsyncClientBase):
return "%s-%s" % (os.path.basename(path), h.hexdigest()) return "%s-%s" % (os.path.basename(path), h.hexdigest())
def add_project_items(self, psync): def add_project_items(self, psync):
with util.cd(self.options["project_dir"]): with fs.cd(self.options["project_dir"]):
cfg = ProjectConfig.get_instance( cfg = ProjectConfig.get_instance(
os.path.join(self.options["project_dir"], "platformio.ini") os.path.join(self.options["project_dir"], "platformio.ini")
) )

View File

@ -19,7 +19,8 @@ from zipfile import ZipFile
import click import click
from platformio import VERSION, __version__, app, exception, util from platformio import VERSION, __version__, app, exception
from platformio.clients.http import fetch_remote_content
from platformio.compat import WINDOWS from platformio.compat import WINDOWS
from platformio.proc import exec_command, get_pythonexe_path from platformio.proc import exec_command, get_pythonexe_path
from platformio.project.helpers import get_project_cache_dir from platformio.project.helpers import get_project_cache_dir
@ -130,7 +131,7 @@ def get_latest_version():
def get_develop_latest_version(): def get_develop_latest_version():
version = None version = None
content = util.fetch_remote_content( content = fetch_remote_content(
"https://raw.githubusercontent.com/platformio/platformio" "https://raw.githubusercontent.com/platformio/platformio"
"/develop/platformio/__init__.py" "/develop/platformio/__init__.py"
) )
@ -150,5 +151,5 @@ def get_develop_latest_version():
def get_pypi_latest_version(): def get_pypi_latest_version():
content = util.fetch_remote_content("https://pypi.org/pypi/platformio/json") content = fetch_remote_content("https://pypi.org/pypi/platformio/json")
return json.loads(content)["info"]["version"] return json.loads(content)["info"]["version"]

View File

@ -30,10 +30,6 @@ class ReturnErrorCode(PlatformioException):
MESSAGE = "{0}" MESSAGE = "{0}"
class LockFileTimeoutError(PlatformioException):
pass
class MinitermException(PlatformioException): class MinitermException(PlatformioException):
pass pass
@ -47,61 +43,6 @@ class AbortedByUser(UserSideException):
MESSAGE = "Aborted by user" MESSAGE = "Aborted by user"
# Package Manager
class PlatformIOPackageException(PlatformioException):
pass
class UnknownPackage(UserSideException):
MESSAGE = "Detected unknown package '{0}'"
class MissingPackageManifest(PlatformIOPackageException):
MESSAGE = "Could not find one of '{0}' manifest files in the package"
class UndefinedPackageVersion(PlatformIOPackageException):
MESSAGE = (
"Could not find a version that satisfies the requirement '{0}'"
" for your system '{1}'"
)
class PackageInstallError(PlatformIOPackageException):
MESSAGE = (
"Could not install '{0}' with version requirements '{1}' "
"for your system '{2}'.\n\n"
"Please try this solution -> http://bit.ly/faq-package-manager"
)
#
# Library
#
class NotGlobalLibDir(UserSideException):
MESSAGE = (
"The `{0}` is not a PlatformIO project.\n\n"
"To manage libraries in global storage `{1}`,\n"
"please use `platformio lib --global {2}` or specify custom storage "
"`platformio lib --storage-dir /path/to/storage/ {2}`.\n"
"Check `platformio lib --help` for details."
)
class InvalidLibConfURL(UserSideException):
MESSAGE = "Invalid library config URL '{0}'"
# #
# UDEV Rules # UDEV Rules
# #
@ -143,20 +84,6 @@ class GetLatestVersionError(PlatformioException):
MESSAGE = "Can not retrieve the latest PlatformIO version" MESSAGE = "Can not retrieve the latest PlatformIO version"
class APIRequestError(PlatformioException):
MESSAGE = "[API] {0}"
class InternetIsOffline(UserSideException):
MESSAGE = (
"You are not connected to the Internet.\n"
"PlatformIO needs the Internet connection to"
" download dependent packages or to work with PIO Account."
)
class InvalidSettingName(UserSideException): class InvalidSettingName(UserSideException):
MESSAGE = "Invalid setting with the name '{0}'" MESSAGE = "Invalid setting with the name '{0}'"

View File

@ -176,6 +176,10 @@ def expanduser(path):
return os.environ["USERPROFILE"] + path[1:] return os.environ["USERPROFILE"] + path[1:]
def change_filemtime(path, mtime):
os.utime(path, (mtime, mtime))
def rmtree(path): def rmtree(path):
def _onerror(func, path, __): def _onerror(func, path, __):
try: try:

View File

@ -20,6 +20,7 @@ import click
import semantic_version import semantic_version
from platformio import __version__, app, exception, fs, telemetry, util from platformio import __version__, app, exception, fs, telemetry, util
from platformio.clients import http
from platformio.commands import PlatformioCLI from platformio.commands import PlatformioCLI
from platformio.commands.lib.command import CTX_META_STORAGE_DIRS_KEY from platformio.commands.lib.command import CTX_META_STORAGE_DIRS_KEY
from platformio.commands.lib.command import lib_update as cmd_lib_update from platformio.commands.lib.command import lib_update as cmd_lib_update
@ -53,9 +54,9 @@ def on_platformio_end(ctx, result): # pylint: disable=unused-argument
check_internal_updates(ctx, "platforms") check_internal_updates(ctx, "platforms")
check_internal_updates(ctx, "libraries") check_internal_updates(ctx, "libraries")
except ( except (
exception.InternetIsOffline, http.HTTPClientError,
http.InternetIsOffline,
exception.GetLatestVersionError, exception.GetLatestVersionError,
exception.APIRequestError,
): ):
click.secho( click.secho(
"Failed to check for PlatformIO upgrades. " "Failed to check for PlatformIO upgrades. "
@ -221,7 +222,7 @@ def check_platformio_upgrade():
last_check["platformio_upgrade"] = int(time()) last_check["platformio_upgrade"] = int(time())
app.set_state_item("last_check", last_check) app.set_state_item("last_check", last_check)
util.internet_on(raise_exception=True) http.ensure_internet_on(raise_exception=True)
# Update PlatformIO's Core packages # Update PlatformIO's Core packages
update_core_packages(silent=True) update_core_packages(silent=True)
@ -268,7 +269,7 @@ def check_internal_updates(ctx, what): # pylint: disable=too-many-branches
last_check[what + "_update"] = int(time()) last_check[what + "_update"] = int(time())
app.set_state_item("last_check", last_check) app.set_state_item("last_check", last_check)
util.internet_on(raise_exception=True) http.ensure_internet_on(raise_exception=True)
outdated_items = [] outdated_items = []
pm = PlatformPackageManager() if what == "platforms" else LibraryPackageManager() pm = PlatformPackageManager() if what == "platforms" else LibraryPackageManager()

View File

@ -21,7 +21,7 @@ from time import mktime
import click import click
import requests import requests
from platformio import DEFAULT_REQUESTS_TIMEOUT, app, fs, util from platformio import DEFAULT_REQUESTS_TIMEOUT, app, fs
from platformio.package.exception import PackageException from platformio.package.exception import PackageException
@ -134,7 +134,7 @@ class FileDownloader(object):
def _preserve_filemtime(self, lmdate): def _preserve_filemtime(self, lmdate):
timedata = parsedate_tz(lmdate) timedata = parsedate_tz(lmdate)
lmtime = mktime(timedata[:9]) lmtime = mktime(timedata[:9])
util.change_filemtime(self._destination, lmtime) fs.change_filemtime(self._destination, lmtime)
def __del__(self): def __del__(self):
if self._request: if self._request:

View File

@ -58,3 +58,14 @@ class UnknownPackageError(UserSideException):
"Could not find a package with '{0}' requirements for your system '%s'" "Could not find a package with '{0}' requirements for your system '%s'"
% util.get_systype() % util.get_systype()
) )
class NotGlobalLibDir(UserSideException):
MESSAGE = (
"The `{0}` is not a PlatformIO project.\n\n"
"To manage libraries in global storage `{1}`,\n"
"please use `platformio lib --global {2}` or specify custom storage "
"`platformio lib --storage-dir /path/to/storage/ {2}`.\n"
"Check `platformio lib --help` for details."
)

View File

@ -15,7 +15,7 @@
import os import os
from time import sleep, time from time import sleep, time
from platformio import exception from platformio.exception import PlatformioException
LOCKFILE_TIMEOUT = 3600 # in seconds, 1 hour LOCKFILE_TIMEOUT = 3600 # in seconds, 1 hour
LOCKFILE_DELAY = 0.2 LOCKFILE_DELAY = 0.2
@ -36,7 +36,11 @@ except ImportError:
LOCKFILE_CURRENT_INTERFACE = None LOCKFILE_CURRENT_INTERFACE = None
class LockFileExists(Exception): class LockFileExists(PlatformioException):
pass
class LockFileTimeoutError(PlatformioException):
pass pass
@ -88,7 +92,7 @@ class LockFile(object):
sleep(self.delay) sleep(self.delay)
elapsed += self.delay elapsed += self.delay
raise exception.LockFileTimeoutError() raise LockFileTimeoutError()
def release(self): def release(self):
self._unlock() self._unlock()

View File

@ -16,10 +16,10 @@ import os
import click import click
from platformio import util
from platformio.package.exception import UnknownPackageError from platformio.package.exception import UnknownPackageError
from platformio.package.meta import PackageItem, PackageOutdatedResult, PackageSpec from platformio.package.meta import PackageItem, PackageOutdatedResult, PackageSpec
from platformio.package.vcsclient import VCSBaseException, VCSClientFactory from platformio.package.vcsclient import VCSBaseException, VCSClientFactory
from platformio.clients.http import ensure_internet_on
class PackageManagerUpdateMixin(object): class PackageManagerUpdateMixin(object):
@ -97,7 +97,7 @@ class PackageManagerUpdateMixin(object):
), ),
nl=False, nl=False,
) )
if not util.internet_on(): if not ensure_internet_on():
if not silent: if not silent:
click.echo("[%s]" % (click.style("Off-line", fg="yellow"))) click.echo("[%s]" % (click.style("Off-line", fg="yellow")))
return pkg return pkg

View File

@ -17,7 +17,7 @@ import os
import subprocess import subprocess
import sys import sys
from platformio import __core_packages__, exception, util from platformio import __core_packages__, fs, exception, util
from platformio.compat import PY2 from platformio.compat import PY2
from platformio.package.manager.tool import ToolPackageManager from platformio.package.manager.tool import ToolPackageManager
from platformio.package.meta import PackageSpec from platformio.package.meta import PackageSpec
@ -93,7 +93,7 @@ def inject_contrib_pysite(verify_openssl=False):
def build_contrib_pysite_deps(target_dir): def build_contrib_pysite_deps(target_dir):
if os.path.isdir(target_dir): if os.path.isdir(target_dir):
util.rmtree_(target_dir) fs.rmtree(target_dir)
os.makedirs(target_dir) os.makedirs(target_dir)
with open(os.path.join(target_dir, "package.json"), "w") as fp: with open(os.path.join(target_dir, "package.json"), "w") as fp:
json.dump( json.dump(

View File

@ -13,7 +13,7 @@
# limitations under the License. # limitations under the License.
from platformio import util from platformio import util
from platformio.exception import APIRequestError, InternetIsOffline from platformio.clients.http import HTTPClientError, InternetIsOffline
from platformio.package.exception import UnknownPackageError from platformio.package.exception import UnknownPackageError
from platformio.package.manager.base import BasePackageManager from platformio.package.manager.base import BasePackageManager
from platformio.package.manager.tool import ToolPackageManager from platformio.package.manager.tool import ToolPackageManager
@ -176,7 +176,7 @@ class PlatformPackageManager(BasePackageManager): # pylint: disable=too-many-an
key = "%s:%s" % (board["platform"], board["id"]) key = "%s:%s" % (board["platform"], board["id"])
if key not in know_boards: if key not in know_boards:
boards.append(board) boards.append(board)
except (APIRequestError, InternetIsOffline): except (HTTPClientError, InternetIsOffline):
pass pass
return sorted(boards, key=lambda b: b["name"]) return sorted(boards, key=lambda b: b["name"])

View File

@ -20,6 +20,7 @@ import re
import tarfile import tarfile
from platformio import util from platformio import util
from platformio.clients.http import fetch_remote_content
from platformio.compat import get_object_members, string_types from platformio.compat import get_object_members, string_types
from platformio.package.exception import ManifestParserError, UnknownManifestError from platformio.package.exception import ManifestParserError, UnknownManifestError
from platformio.project.helpers import is_platformio_project from platformio.project.helpers import is_platformio_project
@ -106,7 +107,7 @@ class ManifestParserFactory(object):
@staticmethod @staticmethod
def new_from_url(remote_url): def new_from_url(remote_url):
content = util.fetch_remote_content(remote_url) content = fetch_remote_content(remote_url)
return ManifestParserFactory.new( return ManifestParserFactory.new(
content, content,
ManifestFileType.from_uri(remote_url) or ManifestFileType.LIBRARY_JSON, ManifestFileType.from_uri(remote_url) or ManifestFileType.LIBRARY_JSON,

View File

@ -21,7 +21,7 @@ import requests
import semantic_version import semantic_version
from marshmallow import Schema, ValidationError, fields, validate, validates from marshmallow import Schema, ValidationError, fields, validate, validates
from platformio import util from platformio.clients.http import fetch_remote_content
from platformio.package.exception import ManifestValidationError from platformio.package.exception import ManifestValidationError
from platformio.util import memoized from platformio.util import memoized
@ -256,4 +256,4 @@ class ManifestSchema(BaseSchema):
"https://raw.githubusercontent.com/spdx/license-list-data" "https://raw.githubusercontent.com/spdx/license-list-data"
"/v%s/json/licenses.json" % version "/v%s/json/licenses.json" % version
) )
return json.loads(util.fetch_remote_content(spdx_data_url)) return json.loads(fetch_remote_content(spdx_data_url))

View File

@ -19,7 +19,7 @@ from zipfile import ZipFile
import click import click
from platformio import util from platformio import fs
from platformio.package.exception import PackageException from platformio.package.exception import PackageException
@ -109,7 +109,7 @@ class ZIPArchiver(BaseArchiver):
@staticmethod @staticmethod
def preserve_mtime(item, dest_dir): def preserve_mtime(item, dest_dir):
util.change_filemtime( fs.change_filemtime(
os.path.join(dest_dir, item.filename), os.path.join(dest_dir, item.filename),
mktime(tuple(item.date_time) + tuple([0, 0, 0])), mktime(tuple(item.date_time) + tuple([0, 0, 0])),
) )

View File

@ -15,6 +15,7 @@
import os import os
import subprocess import subprocess
import sys import sys
from contextlib import contextmanager
from threading import Thread from threading import Thread
from platformio import exception from platformio import exception
@ -137,6 +138,17 @@ def exec_command(*args, **kwargs):
return result return result
@contextmanager
def capture_std_streams(stdout, stderr=None):
_stdout = sys.stdout
_stderr = sys.stderr
sys.stdout = stdout
sys.stderr = stderr or stdout
yield
sys.stdout = _stdout
sys.stderr = _stderr
def is_ci(): def is_ci():
return os.getenv("CI", "").lower() == "true" return os.getenv("CI", "").lower() == "true"

View File

@ -124,7 +124,7 @@ class MeasurementProtocol(TelemetryBase):
caller_id = str(app.get_session_var("caller_id")) caller_id = str(app.get_session_var("caller_id"))
self["cd1"] = util.get_systype() self["cd1"] = util.get_systype()
self["cd4"] = ( self["cd4"] = (
1 if (not util.is_ci() and (caller_id or not is_container())) else 0 1 if (not is_ci() and (caller_id or not is_container())) else 0
) )
if caller_id: if caller_id:
self["cd5"] = caller_id.lower() self["cd5"] = caller_id.lower()

View File

@ -19,26 +19,17 @@ import math
import os import os
import platform import platform
import re import re
import socket
import sys import sys
import time import time
from contextlib import contextmanager
from functools import wraps from functools import wraps
from glob import glob from glob import glob
import click import click
import requests
from platformio import DEFAULT_REQUESTS_TIMEOUT, __apiurl__, __version__, exception from platformio import __version__, exception, proc
from platformio.commands import PlatformioCLI
from platformio.compat import PY2, WINDOWS from platformio.compat import PY2, WINDOWS
from platformio.fs import cd # pylint: disable=unused-import
from platformio.fs import load_json # pylint: disable=unused-import from platformio.fs import load_json # pylint: disable=unused-import
from platformio.fs import rmtree as rmtree_ # pylint: disable=unused-import
from platformio.proc import exec_command # pylint: disable=unused-import from platformio.proc import exec_command # pylint: disable=unused-import
from platformio.proc import is_ci # pylint: disable=unused-import
# KEEP unused imports for backward compatibility with PIO Core 3.0 API
class memoized(object): class memoized(object):
@ -97,17 +88,6 @@ def singleton(cls):
return get_instance return get_instance
@contextmanager
def capture_std_streams(stdout, stderr=None):
_stdout = sys.stdout
_stderr = sys.stderr
sys.stdout = stdout
sys.stderr = stderr or stdout
yield
sys.stdout = _stdout
sys.stderr = _stderr
def get_systype(): def get_systype():
type_ = platform.system().lower() type_ = platform.system().lower()
arch = platform.machine().lower() arch = platform.machine().lower()
@ -116,16 +96,6 @@ def get_systype():
return "%s_%s" % (type_, arch) if arch else type_ return "%s_%s" % (type_, arch) if arch else type_
def pioversion_to_intstr():
vermatch = re.match(r"^([\d\.]+)", __version__)
assert vermatch
return [int(i) for i in vermatch.group(1).split(".")[:3]]
def change_filemtime(path, mtime):
os.utime(path, (mtime, mtime))
def get_serial_ports(filter_hwid=False): def get_serial_ports(filter_hwid=False):
try: try:
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
@ -164,7 +134,7 @@ def get_logical_devices():
items = [] items = []
if WINDOWS: if WINDOWS:
try: try:
result = exec_command( result = proc.exec_command(
["wmic", "logicaldisk", "get", "name,VolumeName"] ["wmic", "logicaldisk", "get", "name,VolumeName"]
).get("out", "") ).get("out", "")
devicenamere = re.compile(r"^([A-Z]{1}\:)\s*(\S+)?") devicenamere = re.compile(r"^([A-Z]{1}\:)\s*(\S+)?")
@ -177,12 +147,12 @@ def get_logical_devices():
except WindowsError: # pylint: disable=undefined-variable except WindowsError: # pylint: disable=undefined-variable
pass pass
# try "fsutil" # try "fsutil"
result = exec_command(["fsutil", "fsinfo", "drives"]).get("out", "") result = proc.exec_command(["fsutil", "fsinfo", "drives"]).get("out", "")
for device in re.findall(r"[A-Z]:\\", result): for device in re.findall(r"[A-Z]:\\", result):
items.append({"path": device, "name": None}) items.append({"path": device, "name": None})
return items return items
result = exec_command(["df"]).get("out") result = proc.exec_command(["df"]).get("out")
devicenamere = re.compile(r"^/.+\d+\%\s+([a-z\d\-_/]+)$", flags=re.I) devicenamere = re.compile(r"^/.+\d+\%\s+([a-z\d\-_/]+)$", flags=re.I)
for line in result.split("\n"): for line in result.split("\n"):
match = devicenamere.match(line.strip()) match = devicenamere.match(line.strip())
@ -370,60 +340,27 @@ def get_api_result(url, params=None, data=None, auth=None, cache_valid=None):
) )
PING_REMOTE_HOSTS = [ def pioversion_to_intstr():
"140.82.118.3", # Github.com vermatch = re.match(r"^([\d\.]+)", __version__)
"35.231.145.151", # Gitlab.com assert vermatch
"88.198.170.159", # platformio.org return [int(i) for i in vermatch.group(1).split(".")[:3]]
"github.com",
"platformio.org",
]
@memoized(expire="10s")
def _internet_on():
timeout = 2
socket.setdefaulttimeout(timeout)
for host in PING_REMOTE_HOSTS:
try:
for var in ("HTTP_PROXY", "HTTPS_PROXY"):
if not os.getenv(var, var.lower()):
continue
requests.get("http://%s" % host, allow_redirects=False, timeout=timeout)
return True
socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, 80))
return True
except: # pylint: disable=bare-except
pass
return False
def internet_on(raise_exception=False):
result = _internet_on()
if raise_exception and not result:
raise exception.InternetIsOffline()
return result
def fetch_remote_content(*args, **kwargs):
# pylint: disable=import-outside-toplevel
from platformio.app import get_user_agent
kwargs["headers"] = kwargs.get("headers", {})
if "User-Agent" not in kwargs["headers"]:
kwargs["headers"]["User-Agent"] = get_user_agent()
if "timeout" not in kwargs:
kwargs["timeout"] = DEFAULT_REQUESTS_TIMEOUT
r = requests.get(*args, **kwargs)
r.raise_for_status()
return r.text
def pepver_to_semver(pepver): def pepver_to_semver(pepver):
return re.sub(r"(\.\d+)\.?(dev|a|b|rc|post)", r"\1-\2.", pepver, 1) return re.sub(r"(\.\d+)\.?(dev|a|b|rc|post)", r"\1-\2.", pepver, 1)
def get_original_version(version):
if version.count(".") != 2:
return None
_, raw = version.split(".")[:2]
if int(raw) <= 99:
return None
if int(raw) <= 9999:
return "%s.%s" % (raw[:-2], int(raw[-2:]))
return "%s.%s.%s" % (raw[:-4], int(raw[-4:-2]), int(raw[-2:]))
def items_to_list(items): def items_to_list(items):
if isinstance(items, list): if isinstance(items, list):
return items return items
@ -472,14 +409,3 @@ def humanize_duration_time(duration):
tokens.append(int(round(duration) if multiplier == 1 else fraction)) tokens.append(int(round(duration) if multiplier == 1 else fraction))
duration -= fraction * multiplier duration -= fraction * multiplier
return "{:02d}:{:02d}:{:02d}.{:03d}".format(*tokens) return "{:02d}:{:02d}:{:02d}.{:03d}".format(*tokens)
def get_original_version(version):
if version.count(".") != 2:
return None
_, raw = version.split(".")[:2]
if int(raw) <= 99:
return None
if int(raw) <= 9999:
return "%s.%s" % (raw[:-2], int(raw[-2:]))
return "%s.%s.%s" % (raw[:-4], int(raw[-4:-2]), int(raw[-2:]))

View File

@ -389,7 +389,7 @@ check_tool = pvs-studio
assert style == 0 assert style == 0
def test_check_embedded_platform_all_tools(clirunner, tmpdir): def test_check_embedded_platform_all_tools(clirunner, validate_cliresult, tmpdir):
config = """ config = """
[env:test] [env:test]
platform = ststm32 platform = ststm32
@ -422,11 +422,9 @@ int main() {
for framework in frameworks: for framework in frameworks:
for tool in ("cppcheck", "clangtidy", "pvs-studio"): for tool in ("cppcheck", "clangtidy", "pvs-studio"):
tmpdir.join("platformio.ini").write(config % (framework, tool)) tmpdir.join("platformio.ini").write(config % (framework, tool))
result = clirunner.invoke(cmd_check, ["--project-dir", str(tmpdir)]) result = clirunner.invoke(cmd_check, ["--project-dir", str(tmpdir)])
validate_cliresult(result)
defects = sum(count_defects(result.output)) defects = sum(count_defects(result.output))
assert result.exit_code == 0 and defects > 0, "Failed %s with %s" % ( assert result.exit_code == 0 and defects > 0, "Failed %s with %s" % (
framework, framework,
tool, tool,

View File

@ -16,12 +16,12 @@ import os
import pytest import pytest
from platformio import util from platformio import proc
from platformio.commands.test.command import cli as cmd_test from platformio.commands.test.command import cli as cmd_test
def test_local_env(): def test_local_env():
result = util.exec_command( result = proc.exec_command(
[ [
"platformio", "platformio",
"test", "test",

View File

@ -20,7 +20,7 @@ import time
import pytest import pytest
from click.testing import CliRunner from click.testing import CliRunner
from platformio import util from platformio.clients import http
def pytest_configure(config): def pytest_configure(config):
@ -74,7 +74,7 @@ def isolated_pio_core(request, tmpdir_factory):
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def without_internet(monkeypatch): def without_internet(monkeypatch):
monkeypatch.setattr(util, "_internet_on", lambda: False) monkeypatch.setattr(http, "_internet_on", lambda: False)
@pytest.fixture @pytest.fixture

View File

@ -19,7 +19,7 @@ from os.path import basename, dirname, getsize, isdir, isfile, join, normpath
import pytest import pytest
from platformio import util from platformio import fs, proc
from platformio.compat import PY2 from platformio.compat import PY2
from platformio.package.manager.platform import PlatformPackageManager from platformio.package.manager.platform import PlatformPackageManager
from platformio.platform.factory import PlatformFactory from platformio.platform.factory import PlatformFactory
@ -64,14 +64,14 @@ def pytest_generate_tests(metafunc):
def test_run(pioproject_dir): def test_run(pioproject_dir):
with util.cd(pioproject_dir): with fs.cd(pioproject_dir):
config = ProjectConfig() config = ProjectConfig()
build_dir = config.get_optional_dir("build") build_dir = config.get_optional_dir("build")
if isdir(build_dir): if isdir(build_dir):
util.rmtree_(build_dir) fs.rmtree(build_dir)
env_names = config.envs() env_names = config.envs()
result = util.exec_command( result = proc.exec_command(
["platformio", "run", "-e", random.choice(env_names)] ["platformio", "run", "-e", random.choice(env_names)]
) )
if result["returncode"] != 0: if result["returncode"] != 0:

View File

@ -17,29 +17,33 @@
import pytest import pytest
import requests import requests
from platformio import exception, util from platformio import proc
from platformio.clients import http
from platformio.clients.registry import RegistryClient
def test_platformio_cli(): def test_platformio_cli():
result = util.exec_command(["pio", "--help"]) result = proc.exec_command(["pio", "--help"])
assert result["returncode"] == 0 assert result["returncode"] == 0
# pylint: disable=unsupported-membership-test # pylint: disable=unsupported-membership-test
assert "Usage: pio [OPTIONS] COMMAND [ARGS]..." in result["out"] assert "Usage: pio [OPTIONS] COMMAND [ARGS]..." in result["out"]
def test_ping_internet_ips(): def test_ping_internet_ips():
for host in util.PING_REMOTE_HOSTS: for host in http.PING_REMOTE_HOSTS:
requests.get("http://%s" % host, allow_redirects=False, timeout=2) requests.get("http://%s" % host, allow_redirects=False, timeout=2)
def test_api_internet_offline(without_internet, isolated_pio_core): def test_api_internet_offline(without_internet, isolated_pio_core):
with pytest.raises(exception.InternetIsOffline): regclient = RegistryClient()
util.get_api_result("/stats") with pytest.raises(http.InternetIsOffline):
regclient.fetch_json_data("get", "/v2/stats")
def test_api_cache(monkeypatch, isolated_pio_core): def test_api_cache(monkeypatch, isolated_pio_core):
api_kwargs = {"url": "/stats", "cache_valid": "10s"} regclient = RegistryClient()
result = util.get_api_result(**api_kwargs) api_kwargs = {"method": "get", "path": "/v2/stats", "cache_valid": "10s"}
result = regclient.fetch_json_data(**api_kwargs)
assert result and "boards" in result assert result and "boards" in result
monkeypatch.setattr(util, "_internet_on", lambda: False) monkeypatch.setattr(http, "_internet_on", lambda: False)
assert util.get_api_result(**api_kwargs) == result assert regclient.fetch_json_data(**api_kwargs) == result