Rework Python 2-3 compat (#268)

This commit is contained in:
Quentame 2020-04-08 00:19:42 +02:00 committed by GitHub
parent e3bdcea15a
commit 696db8cf20
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 169 additions and 127 deletions

View file

@ -1,14 +1,14 @@
"""Library base file."""
import six
import uuid
from six import PY2, string_types
from uuid import uuid1
import inspect
import json
import logging
from requests import Session
import sys
import tempfile
import os
from tempfile import gettempdir
from os import path, mkdir
from re import match
import http.cookiejar as cookielib
from pyicloud.exceptions import (
PyiCloudFailedLoginException,
@ -27,11 +27,6 @@ from pyicloud.services import (
)
from pyicloud.utils import get_password_from_keyring
if six.PY3:
import http.cookiejar as cookielib
else:
import cookielib # pylint: disable=import-error
LOGGER = logging.getLogger(__name__)
@ -99,7 +94,7 @@ class PyiCloudSession(Session):
reason = data.get("errorMessage")
reason = reason or data.get("reason")
reason = reason or data.get("errorReason")
if not reason and isinstance(data.get("error"), six.string_types):
if not reason and isinstance(data.get("error"), string_types):
reason = data.get("error")
if not reason and data.get("error"):
reason = "Unknown reason"
@ -166,7 +161,7 @@ class PyiCloudService(object):
password = get_password_from_keyring(apple_id)
self.data = {}
self.client_id = client_id or str(uuid.uuid1()).upper()
self.client_id = client_id or str(uuid1()).upper()
self.with_family = with_family
self.user = {"apple_id": apple_id, "password": password}
@ -176,11 +171,9 @@ class PyiCloudService(object):
self._base_login_url = "%s/login" % self.SETUP_ENDPOINT
if cookie_directory:
self._cookie_directory = os.path.expanduser(
os.path.normpath(cookie_directory)
)
self._cookie_directory = path.expanduser(path.normpath(cookie_directory))
else:
self._cookie_directory = os.path.join(tempfile.gettempdir(), "pyicloud",)
self._cookie_directory = path.join(gettempdir(), "pyicloud")
self.session = PyiCloudSession(self)
self.session.verify = verify
@ -194,7 +187,7 @@ class PyiCloudService(object):
cookiejar_path = self._get_cookiejar_path()
self.session.cookies = cookielib.LWPCookieJar(filename=cookiejar_path)
if os.path.exists(cookiejar_path):
if path.exists(cookiejar_path):
try:
self.session.cookies.load()
LOGGER.debug("Read cookies from %s", cookiejar_path)
@ -242,8 +235,8 @@ class PyiCloudService(object):
self.params.update({"dsid": self.data["dsInfo"]["dsid"]})
self._webservices = self.data["webservices"]
if not os.path.exists(self._cookie_directory):
os.mkdir(self._cookie_directory)
if not path.exists(self._cookie_directory):
mkdir(self._cookie_directory)
self.session.cookies.save()
LOGGER.debug("Cookies saved to %s", self._get_cookiejar_path())
@ -252,7 +245,7 @@ class PyiCloudService(object):
def _get_cookiejar_path(self):
"""Get path for cookiejar file."""
return os.path.join(
return path.join(
self._cookie_directory,
"".join([c for c in self.user.get("apple_id") if match(r"\w", c)]),
)
@ -373,9 +366,9 @@ class PyiCloudService(object):
def __str__(self):
as_unicode = self.__unicode__()
if sys.version_info[0] >= 3:
return as_unicode
if PY2:
return as_unicode.encode("utf-8", "ignore")
return as_unicode
def __repr__(self):
return "<%s>" % str(self)

View file

@ -5,10 +5,10 @@ A Command Line Wrapper to allow easy use of pyicloud for
command line scripts, and related.
"""
from __future__ import print_function
from builtins import input
import argparse
import pickle
import sys
import six
from click import confirm
@ -16,12 +16,6 @@ from pyicloud import PyiCloudService
from pyicloud.exceptions import PyiCloudFailedLoginException
from . import utils
# fmt: off
if six.PY2:
input = raw_input # pylint: disable=redefined-builtin,invalid-name,undefined-variable
else:
input = input # pylint: disable=bad-option-value,self-assigning-variable,invalid-name
# fmt: on
DEVICE_ERROR = "Please use the --device switch to indicate which device to use."

View file

@ -1,7 +1,7 @@
"""Account service."""
import sys
import six
from __future__ import division
from six import PY2, python_2_unicode_compatible
from collections import OrderedDict
from pyicloud.utils import underscore_to_camelcase
@ -68,27 +68,41 @@ class AccountService(object):
return self._storage
def __unicode__(self):
return "{devices: %s, family: %s, storage: %s bytes free}" % (
len(self.devices),
len(self.family),
self.storage.usage.available_storage_in_bytes,
)
@six.python_2_unicode_compatible
def __str__(self):
as_unicode = self.__unicode__()
if PY2:
return as_unicode.encode("utf-8", "ignore")
return as_unicode
def __repr__(self):
return "<%s: %s>" % (type(self).__name__, str(self))
@python_2_unicode_compatible
class AccountDevice(dict):
"""Account device."""
def __getattr__(self, key):
return self[underscore_to_camelcase(key)]
def __unicode__(self):
return "{model: %s, name: %s}" % (self.model_display_name, self.name)
def __str__(self):
return u"{display_name}: {name}".format(
display_name=self.model_display_name, name=self.name,
)
as_unicode = self.__unicode__()
if PY2:
return as_unicode.encode("utf-8", "ignore")
return as_unicode
def __repr__(self):
return "<{display}>".format(
display=(
six.text_type(self)
if sys.version_info[0] >= 3
else six.text_type(self).encode("utf8", "replace")
)
)
return "<%s: %s>" % (type(self).__name__, str(self))
class FamilyMember(object):
@ -193,19 +207,20 @@ class FamilyMember(object):
return self._attrs[key]
return getattr(self, key)
def __str__(self):
return u"{full_name}: {age_classification}".format(
full_name=self.full_name, age_classification=self.age_classification,
def __unicode__(self):
return "{name: %s, age_classification: %s}" % (
self.full_name,
self.age_classification,
)
def __str__(self):
as_unicode = self.__unicode__()
if PY2:
return as_unicode.encode("utf-8", "ignore")
return as_unicode
def __repr__(self):
return "<{display}>".format(
display=(
six.text_type(self)
if sys.version_info[0] >= 3
else six.text_type(self).encode("utf8", "replace")
)
)
return "<%s: %s>" % (type(self).__name__, str(self))
class AccountStorageUsageForMedia(object):
@ -234,17 +249,17 @@ class AccountStorageUsageForMedia(object):
"""Gets the usage in bytes."""
return self.usage_data["usageInBytes"]
def __unicode__(self):
return "{key: %s, usage: %s bytes}" % (self.key, self.usage_in_bytes)
def __str__(self):
return u"{key}: {usage}".format(key=self.key, usage=self.usage_in_bytes)
as_unicode = self.__unicode__()
if PY2:
return as_unicode.encode("utf-8", "ignore")
return as_unicode
def __repr__(self):
return "<{display}>".format(
display=(
six.text_type(self)
if sys.version_info[0] >= 3
else six.text_type(self).encode("utf8", "replace")
)
)
return "<%s: %s>" % (type(self).__name__, str(self))
class AccountStorageUsage(object):
@ -267,7 +282,7 @@ class AccountStorageUsage(object):
@property
def used_storage_in_percent(self):
"""Gets the used storage in percent."""
return self.used_storage_in_bytes * 100 / self.total_storage_in_bytes
return round(self.used_storage_in_bytes * 100 / self.total_storage_in_bytes, 2)
@property
def available_storage_in_bytes(self):
@ -277,7 +292,9 @@ class AccountStorageUsage(object):
@property
def available_storage_in_percent(self):
"""Gets the available storage in percent."""
return self.available_storage_in_bytes * 100 / self.total_storage_in_bytes
return round(
self.available_storage_in_bytes * 100 / self.total_storage_in_bytes, 2
)
@property
def total_storage_in_bytes(self):
@ -309,19 +326,20 @@ class AccountStorageUsage(object):
"""Gets the paid quota."""
return self.quota_data["paidQuota"]
def __str__(self):
return u"{used_percent}%% used of {total} bytes".format(
used_percent=self.used_storage_in_percent, total=self.total_storage_in_bytes
def __unicode__(self):
return "%s%% used of %s bytes" % (
self.used_storage_in_percent,
self.total_storage_in_bytes,
)
def __str__(self):
as_unicode = self.__unicode__()
if PY2:
return as_unicode.encode("utf-8", "ignore")
return as_unicode
def __repr__(self):
return "<{display}>".format(
display=(
six.text_type(self)
if sys.version_info[0] >= 3
else six.text_type(self).encode("utf8", "replace")
)
)
return "<%s: %s>" % (type(self).__name__, str(self))
class AccountStorage(object):
@ -331,9 +349,21 @@ class AccountStorage(object):
self.usage = AccountStorageUsage(
storage_data.get("storageUsageInfo"), storage_data.get("quotaStatus")
)
self.usages_by_media = {}
self.usages_by_media = OrderedDict()
for usage_media in storage_data.get("storageUsageByMedia"):
self.usages_by_media[usage_media["mediaKey"]] = AccountStorageUsageForMedia(
usage_media
)
def __unicode__(self):
return "{usage: %s, usages_by_media: %s}" % (self.usage, self.usages_by_media)
def __str__(self):
as_unicode = self.__unicode__()
if PY2:
return as_unicode.encode("utf-8", "ignore")
return as_unicode
def __repr__(self):
return "<%s: %s>" % (type(self).__name__, str(self))

View file

@ -17,7 +17,7 @@ class CalendarService(object):
self._service_root = service_root
self._calendar_endpoint = "%s/ca" % self._service_root
self._calendar_refresh_url = "%s/events" % self._calendar_endpoint
self._calendar_event_detail_url = "%s/eventdetail" % (self._calendar_endpoint,)
self._calendar_event_detail_url = "%s/eventdetail" % self._calendar_endpoint
self._calendars = "%s/startup" % self._calendar_endpoint
self.response = {}

View file

@ -1,8 +1,7 @@
"""Find my iPhone service."""
import json
import sys
import six
from six import PY2, text_type
from pyicloud.exceptions import PyiCloudNoDevicesException
@ -70,26 +69,26 @@ class FindMyiPhoneServiceManager(object):
def __getitem__(self, key):
if isinstance(key, int):
if six.PY3:
key = list(self.keys())[key]
else:
if PY2:
key = self.keys()[key]
else:
key = list(self.keys())[key]
return self._devices[key]
def __getattr__(self, attr):
return getattr(self._devices, attr)
def __unicode__(self):
return six.text_type(self._devices)
return text_type(self._devices)
def __str__(self):
as_unicode = self.__unicode__()
if sys.version_info[0] >= 3:
return as_unicode
if PY2:
return as_unicode.encode("utf-8", "ignore")
return as_unicode
def __repr__(self):
return six.text_type(self)
return text_type(self)
class AppleDevice(object):
@ -204,13 +203,13 @@ class AppleDevice(object):
def __unicode__(self):
display_name = self["deviceDisplayName"]
name = self["name"]
return "%s: %s" % (display_name, name,)
return "%s: %s" % (display_name, name)
def __str__(self):
as_unicode = self.__unicode__()
if sys.version_info[0] >= 3:
return as_unicode
if PY2:
return as_unicode.encode("utf-8", "ignore")
return as_unicode
def __repr__(self):
return "<AppleDevice(%s)>" % str(self)

View file

@ -1,14 +1,16 @@
"""Photo service."""
import sys
import json
import base64
from six import PY2
# fmt: off
from six.moves.urllib.parse import urlencode # pylint: disable=bad-option-value,relative-import
# fmt: on
from datetime import datetime
from pyicloud.exceptions import PyiCloudServiceNotActivatedException
from pytz import UTC
from future.moves.urllib.parse import urlencode
class PhotosService(object):
"""The 'Photos' iCloud service."""
@ -473,9 +475,9 @@ class PhotoAlbum(object):
def __str__(self):
as_unicode = self.__unicode__()
if sys.version_info[0] >= 3:
return as_unicode
if PY2:
return as_unicode.encode("utf-8", "ignore")
return as_unicode
def __repr__(self):
return "<%s: '%s'>" % (type(self).__name__, self)

View file

@ -1,6 +1,6 @@
"""File service."""
from datetime import datetime
import sys
from six import PY2
class UbiquityService(object):
@ -112,9 +112,9 @@ class UbiquityNode(object):
def __str__(self):
as_unicode = self.__unicode__()
if sys.version_info[0] >= 3:
return as_unicode
if PY2:
return as_unicode.encode("utf-8", "ignore")
return as_unicode
def __repr__(self):
return "<%s: '%s'>" % (self.type.capitalize(), self)

View file

@ -1,7 +1,7 @@
"""Utils."""
import getpass
import keyring
import sys
from sys import stdout
from .exceptions import PyiCloudNoStoredPasswordAvailableException
@ -9,7 +9,7 @@ from .exceptions import PyiCloudNoStoredPasswordAvailableException
KEYRING_SYSTEM = "pyicloud://icloud-password"
def get_password(username, interactive=sys.stdout.isatty()):
def get_password(username, interactive=stdout.isatty()):
"""Get the password from a username."""
try:
return get_password_from_keyring(username)
@ -18,7 +18,7 @@ def get_password(username, interactive=sys.stdout.isatty()):
raise
return getpass.getpass(
"Enter iCloud password for {username}: ".format(username=username,)
"Enter iCloud password for {username}: ".format(username=username)
)
@ -40,7 +40,7 @@ def get_password_from_keyring(username):
"No pyicloud password for {username} could be found "
"in the system keychain. Use the `--store-in-keyring` "
"command-line option for storing a password for this "
"username.".format(username=username,)
"username.".format(username=username)
)
return result
@ -48,12 +48,12 @@ def get_password_from_keyring(username):
def store_password_in_keyring(username, password):
"""Store the password of a username."""
return keyring.set_password(KEYRING_SYSTEM, username, password,)
return keyring.set_password(KEYRING_SYSTEM, username, password)
def delete_password_in_keyring(username):
"""Delete the password of a username."""
return keyring.delete_password(KEYRING_SYSTEM, username,)
return keyring.delete_password(KEYRING_SYSTEM, username)
def underscore_to_camelcase(word, initial_capital=False):

View file

@ -1,5 +1,7 @@
"""Account service tests."""
from unittest import TestCase
from six import PY3
from . import PyiCloudServiceMock
from .const import AUTHENTICATED_USER, VALID_PASSWORD
@ -12,6 +14,12 @@ class AccountServiceTest(TestCase):
def setUp(self):
self.service = PyiCloudServiceMock(AUTHENTICATED_USER, VALID_PASSWORD).account
def test_repr(self):
"""Tests representation."""
# fmt: off
assert repr(self.service) == "<AccountService: {devices: 2, family: 3, storage: 3020076244 bytes free}>"
# fmt: on
def test_devices(self):
"""Tests devices."""
assert self.service.devices
@ -32,6 +40,10 @@ class AccountServiceTest(TestCase):
assert device["modelSmallPhotoURL2x"]
assert device["modelSmallPhotoURL1x"]
assert device["modelDisplayName"]
# fmt: off
if PY3:
assert repr(device) == "<AccountDevice: {model: "+device.model_display_name+", name: "+device.name+"}>"
# fmt: on
def test_family(self):
"""Tests family members."""
@ -51,30 +63,39 @@ class AccountServiceTest(TestCase):
assert not member.has_ask_to_buy_enabled
assert not member.share_my_location_enabled_family_members
assert member.dsid_for_purchases
# fmt: off
assert repr(member) == "<FamilyMember: {name: "+member.full_name+", age_classification: "+member.age_classification+"}>"
# fmt: on
def test_storage(self):
"""Tests storage."""
assert self.service.storage
# fmt: off
if PY3:
assert repr(self.service.storage) == "<AccountStorage: {usage: 43.75% used of 5368709120 bytes, usages_by_media: OrderedDict([('photos', <AccountStorageUsageForMedia: {key: photos, usage: 0 bytes}>), ('backup', <AccountStorageUsageForMedia: {key: backup, usage: 799008186 bytes}>), ('docs', <AccountStorageUsageForMedia: {key: docs, usage: 449092146 bytes}>), ('mail', <AccountStorageUsageForMedia: {key: mail, usage: 1101522944 bytes}>)])}>"
# fmt: on
def test_storage_usage(self):
"""Tests storage usage."""
assert self.service.storage.usage
assert (
self.service.storage.usage.comp_storage_in_bytes
or self.service.storage.usage.comp_storage_in_bytes == 0
)
assert self.service.storage.usage.used_storage_in_bytes
assert self.service.storage.usage.used_storage_in_percent
assert self.service.storage.usage.available_storage_in_bytes
assert self.service.storage.usage.available_storage_in_percent
assert self.service.storage.usage.total_storage_in_bytes
assert (
self.service.storage.usage.commerce_storage_in_bytes
or self.service.storage.usage.commerce_storage_in_bytes == 0
)
assert not self.service.storage.usage.quota_over
assert not self.service.storage.usage.quota_tier_max
assert not self.service.storage.usage.quota_almost_full
assert not self.service.storage.usage.quota_paid
usage = self.service.storage.usage
assert usage.comp_storage_in_bytes or usage.comp_storage_in_bytes == 0
assert usage.used_storage_in_bytes
assert usage.used_storage_in_percent
assert usage.available_storage_in_bytes
assert usage.available_storage_in_percent
assert usage.total_storage_in_bytes
assert usage.commerce_storage_in_bytes or usage.commerce_storage_in_bytes == 0
assert not usage.quota_over
assert not usage.quota_tier_max
assert not usage.quota_almost_full
assert not usage.quota_paid
# fmt: off
assert repr(usage) == "<AccountStorageUsage: "+str(usage.used_storage_in_percent)+"% used of "+str(usage.total_storage_in_bytes)+" bytes>"
# fmt: on
def test_storage_usages_by_media(self):
"""Tests storage usages by media."""
assert self.service.storage.usages_by_media
for usage_media in self.service.storage.usages_by_media.values():
@ -82,3 +103,6 @@ class AccountServiceTest(TestCase):
assert usage_media.label
assert usage_media.color
assert usage_media.usage_in_bytes or usage_media.usage_in_bytes == 0
# fmt: off
assert repr(usage_media) == "<AccountStorageUsageForMedia: {key: "+usage_media.key+", usage: "+str(usage_media.usage_in_bytes)+" bytes}>"
# fmt: on

View file

@ -5,15 +5,15 @@ from .const import AUTHENTICATED_USER, REQUIRES_2SA_USER, VALID_PASSWORD
from .const_findmyiphone import FMI_FAMILY_WORKING
import os
import sys
from six import PY2
import pickle
import pytest
from unittest import TestCase
if sys.version_info >= (3, 3):
from unittest.mock import patch # pylint: disable=no-name-in-module,import-error
else:
if PY2:
from mock import patch
else:
from unittest.mock import patch # pylint: disable=no-name-in-module,import-error
class TestCmdline(TestCase):