Support Python 3.6 to 3.10 (#371)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Quentin POLLET <polletquentin74@me.com> Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
b6356a00bc
commit
592ff464c5
32 changed files with 300 additions and 393 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -15,6 +15,8 @@ jobs:
|
||||||
- "3.6"
|
- "3.6"
|
||||||
- "3.7"
|
- "3.7"
|
||||||
- "3.8"
|
- "3.8"
|
||||||
|
- "3.9"
|
||||||
|
- "3.10"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2.4.0
|
- uses: actions/checkout@v2.4.0
|
||||||
|
|
26
README.rst
26
README.rst
|
@ -143,7 +143,7 @@ Location
|
||||||
Returns the device's last known location. The Find My iPhone app must have been installed and initialized.
|
Returns the device's last known location. The Find My iPhone app must have been installed and initialized.
|
||||||
|
|
||||||
>>> api.iphone.location()
|
>>> api.iphone.location()
|
||||||
{u'timeStamp': 1357753796553, u'locationFinished': True, u'longitude': -0.14189, u'positionType': u'GPS', u'locationType': None, u'latitude': 51.501364, u'isOld': False, u'horizontalAccuracy': 5.0}
|
{'timeStamp': 1357753796553, 'locationFinished': True, 'longitude': -0.14189, 'positionType': 'GPS', 'locationType': None, 'latitude': 51.501364, 'isOld': False, 'horizontalAccuracy': 5.0}
|
||||||
|
|
||||||
Status
|
Status
|
||||||
******
|
******
|
||||||
|
@ -151,7 +151,7 @@ Status
|
||||||
The Find My iPhone response is quite bloated, so for simplicity's sake this method will return a subset of the properties.
|
The Find My iPhone response is quite bloated, so for simplicity's sake this method will return a subset of the properties.
|
||||||
|
|
||||||
>>> api.iphone.status()
|
>>> api.iphone.status()
|
||||||
{'deviceDisplayName': u'iPhone 5', 'deviceStatus': u'200', 'batteryLevel': 0.6166913, 'name': u"Peter's iPhone"}
|
{'deviceDisplayName': 'iPhone 5', 'deviceStatus': '200', 'batteryLevel': 0.6166913, 'name': "Peter's iPhone"}
|
||||||
|
|
||||||
If you wish to request further properties, you may do so by passing in a list of property names.
|
If you wish to request further properties, you may do so by passing in a list of property names.
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ You can access your iCloud contacts/address book through the ``contacts`` proper
|
||||||
|
|
||||||
>>> for c in api.contacts.all():
|
>>> for c in api.contacts.all():
|
||||||
>>> print c.get('firstName'), c.get('phones')
|
>>> print c.get('firstName'), c.get('phones')
|
||||||
John [{u'field': u'+1 555-55-5555-5', u'label': u'MOBILE'}]
|
John [{'field': '+1 555-55-5555-5', 'label': 'MOBILE'}]
|
||||||
|
|
||||||
Note: These contacts do not include contacts federated from e.g. Facebook, only the ones stored in iCloud.
|
Note: These contacts do not include contacts federated from e.g. Facebook, only the ones stored in iCloud.
|
||||||
|
|
||||||
|
@ -215,21 +215,21 @@ File Storage (Ubiquity)
|
||||||
You can access documents stored in your iCloud account by using the ``files`` property's ``dir`` method:
|
You can access documents stored in your iCloud account by using the ``files`` property's ``dir`` method:
|
||||||
|
|
||||||
>>> api.files.dir()
|
>>> api.files.dir()
|
||||||
[u'.do-not-delete',
|
['.do-not-delete',
|
||||||
u'.localized',
|
'.localized',
|
||||||
u'com~apple~Notes',
|
'com~apple~Notes',
|
||||||
u'com~apple~Preview',
|
'com~apple~Preview',
|
||||||
u'com~apple~mail',
|
'com~apple~mail',
|
||||||
u'com~apple~shoebox',
|
'com~apple~shoebox',
|
||||||
u'com~apple~system~spotlight'
|
'com~apple~system~spotlight'
|
||||||
]
|
]
|
||||||
|
|
||||||
You can access children and their children's children using the filename as an index:
|
You can access children and their children's children using the filename as an index:
|
||||||
|
|
||||||
>>> api.files['com~apple~Notes']
|
>>> api.files['com~apple~Notes']
|
||||||
<Folder: u'com~apple~Notes'>
|
<Folder: 'com~apple~Notes'>
|
||||||
>>> api.files['com~apple~Notes'].type
|
>>> api.files['com~apple~Notes'].type
|
||||||
u'folder'
|
'folder'
|
||||||
>>> api.files['com~apple~Notes'].dir()
|
>>> api.files['com~apple~Notes'].dir()
|
||||||
[u'Documents']
|
[u'Documents']
|
||||||
>>> api.files['com~apple~Notes']['Documents'].dir()
|
>>> api.files['com~apple~Notes']['Documents'].dir()
|
||||||
|
@ -336,7 +336,7 @@ Note: Consider using ``shutil.copyfile`` or another buffered strategy for downlo
|
||||||
Information about each version can be accessed through the ``versions`` property:
|
Information about each version can be accessed through the ``versions`` property:
|
||||||
|
|
||||||
>>> photo.versions.keys()
|
>>> photo.versions.keys()
|
||||||
[u'medium', u'original', u'thumb']
|
['medium', 'original', 'thumb']
|
||||||
|
|
||||||
To download a specific version of the photo asset, pass the version to ``download()``:
|
To download a specific version of the photo asset, pass the version to ``download()``:
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
"""Library base file."""
|
"""Library base file."""
|
||||||
from six import PY2, string_types
|
|
||||||
from uuid import uuid1
|
from uuid import uuid1
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
|
@ -45,7 +44,7 @@ class PyiCloudPasswordFilter(logging.Filter):
|
||||||
"""Password log hider."""
|
"""Password log hider."""
|
||||||
|
|
||||||
def __init__(self, password):
|
def __init__(self, password):
|
||||||
super(PyiCloudPasswordFilter, self).__init__(password)
|
super().__init__(password)
|
||||||
|
|
||||||
def filter(self, record):
|
def filter(self, record):
|
||||||
message = record.getMessage()
|
message = record.getMessage()
|
||||||
|
@ -61,7 +60,7 @@ class PyiCloudSession(Session):
|
||||||
|
|
||||||
def __init__(self, service):
|
def __init__(self, service):
|
||||||
self.service = service
|
self.service = service
|
||||||
Session.__init__(self)
|
super().__init__()
|
||||||
|
|
||||||
def request(self, method, url, **kwargs): # pylint: disable=arguments-differ
|
def request(self, method, url, **kwargs): # pylint: disable=arguments-differ
|
||||||
|
|
||||||
|
@ -72,24 +71,24 @@ class PyiCloudSession(Session):
|
||||||
if self.service.password_filter not in request_logger.filters:
|
if self.service.password_filter not in request_logger.filters:
|
||||||
request_logger.addFilter(self.service.password_filter)
|
request_logger.addFilter(self.service.password_filter)
|
||||||
|
|
||||||
request_logger.debug("%s %s %s" % (method, url, kwargs.get("data", "")))
|
request_logger.debug("%s %s %s", method, url, kwargs.get("data", ""))
|
||||||
|
|
||||||
has_retried = kwargs.get("retried")
|
has_retried = kwargs.get("retried")
|
||||||
kwargs.pop("retried", None)
|
kwargs.pop("retried", None)
|
||||||
response = super(PyiCloudSession, self).request(method, url, **kwargs)
|
response = super().request(method, url, **kwargs)
|
||||||
|
|
||||||
content_type = response.headers.get("Content-Type", "").split(";")[0]
|
content_type = response.headers.get("Content-Type", "").split(";")[0]
|
||||||
json_mimetypes = ["application/json", "text/json"]
|
json_mimetypes = ["application/json", "text/json"]
|
||||||
|
|
||||||
for header in HEADER_DATA:
|
for header, value in HEADER_DATA.items():
|
||||||
if response.headers.get(header):
|
if response.headers.get(header):
|
||||||
session_arg = HEADER_DATA[header]
|
session_arg = value
|
||||||
self.service.session_data.update(
|
self.service.session_data.update(
|
||||||
{session_arg: response.headers.get(header)}
|
{session_arg: response.headers.get(header)}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Save session_data to file
|
# Save session_data to file
|
||||||
with open(self.service.session_path, "w") as outfile:
|
with open(self.service.session_path, "w", encoding="utf-8") as outfile:
|
||||||
json.dump(self.service.session_data, outfile)
|
json.dump(self.service.session_data, outfile)
|
||||||
LOGGER.debug("Saved session data to file")
|
LOGGER.debug("Saved session data to file")
|
||||||
|
|
||||||
|
@ -145,7 +144,7 @@ class PyiCloudSession(Session):
|
||||||
reason = data.get("errorMessage")
|
reason = data.get("errorMessage")
|
||||||
reason = reason or data.get("reason")
|
reason = reason or data.get("reason")
|
||||||
reason = reason or data.get("errorReason")
|
reason = reason or data.get("errorReason")
|
||||||
if not reason and isinstance(data.get("error"), string_types):
|
if not reason and isinstance(data.get("error"), str):
|
||||||
reason = data.get("error")
|
reason = data.get("error")
|
||||||
if not reason and data.get("error"):
|
if not reason and data.get("error"):
|
||||||
reason = "Unknown reason"
|
reason = "Unknown reason"
|
||||||
|
@ -187,7 +186,7 @@ class PyiCloudSession(Session):
|
||||||
raise api_error
|
raise api_error
|
||||||
|
|
||||||
|
|
||||||
class PyiCloudService(object):
|
class PyiCloudService:
|
||||||
"""
|
"""
|
||||||
A base authentication class for the iCloud service. Handles the
|
A base authentication class for the iCloud service. Handles the
|
||||||
authentication required to access iCloud services.
|
authentication required to access iCloud services.
|
||||||
|
@ -239,7 +238,7 @@ class PyiCloudService(object):
|
||||||
|
|
||||||
self.session_data = {}
|
self.session_data = {}
|
||||||
try:
|
try:
|
||||||
with open(self.session_path) as session_f:
|
with open(self.session_path, encoding="utf-8") as session_f:
|
||||||
self.session_data = json.load(session_f)
|
self.session_data = json.load(session_f)
|
||||||
except: # pylint: disable=bare-except
|
except: # pylint: disable=bare-except
|
||||||
LOGGER.info("Session file does not exist")
|
LOGGER.info("Session file does not exist")
|
||||||
|
@ -320,7 +319,7 @@ class PyiCloudService(object):
|
||||||
headers["X-Apple-ID-Session-Id"] = self.session_data.get("session_id")
|
headers["X-Apple-ID-Session-Id"] = self.session_data.get("session_id")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
req = self.session.post(
|
self.session.post(
|
||||||
"%s/signin" % self.AUTH_ENDPOINT,
|
"%s/signin" % self.AUTH_ENDPOINT,
|
||||||
params={"isRememberMeEnabled": "true"},
|
params={"isRememberMeEnabled": "true"},
|
||||||
data=json.dumps(data),
|
data=json.dumps(data),
|
||||||
|
@ -328,7 +327,7 @@ class PyiCloudService(object):
|
||||||
)
|
)
|
||||||
except PyiCloudAPIResponseException as error:
|
except PyiCloudAPIResponseException as error:
|
||||||
msg = "Invalid email/password combination."
|
msg = "Invalid email/password combination."
|
||||||
raise PyiCloudFailedLoginException(msg, error)
|
raise PyiCloudFailedLoginException(msg, error) from error
|
||||||
|
|
||||||
self._authenticate_with_token()
|
self._authenticate_with_token()
|
||||||
|
|
||||||
|
@ -352,7 +351,7 @@ class PyiCloudService(object):
|
||||||
self.data = req.json()
|
self.data = req.json()
|
||||||
except PyiCloudAPIResponseException as error:
|
except PyiCloudAPIResponseException as error:
|
||||||
msg = "Invalid authentication token."
|
msg = "Invalid authentication token."
|
||||||
raise PyiCloudFailedLoginException(msg, error)
|
raise PyiCloudFailedLoginException(msg, error) from error
|
||||||
|
|
||||||
def _authenticate_with_credentials_service(self, service):
|
def _authenticate_with_credentials_service(self, service):
|
||||||
"""Authenticate to a specific service using credentials."""
|
"""Authenticate to a specific service using credentials."""
|
||||||
|
@ -370,7 +369,7 @@ class PyiCloudService(object):
|
||||||
self.data = self._validate_token()
|
self.data = self._validate_token()
|
||||||
except PyiCloudAPIResponseException as error:
|
except PyiCloudAPIResponseException as error:
|
||||||
msg = "Invalid email/password combination."
|
msg = "Invalid email/password combination."
|
||||||
raise PyiCloudFailedLoginException(msg, error)
|
raise PyiCloudFailedLoginException(msg, error) from error
|
||||||
|
|
||||||
def _validate_token(self):
|
def _validate_token(self):
|
||||||
"""Checks if the current access token is still valid."""
|
"""Checks if the current access token is still valid."""
|
||||||
|
@ -517,7 +516,8 @@ class PyiCloudService(object):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.session.get(
|
self.session.get(
|
||||||
"%s/2sv/trust" % self.AUTH_ENDPOINT, headers=headers,
|
f"{self.AUTH_ENDPOINT}/2sv/trust",
|
||||||
|
headers=headers,
|
||||||
)
|
)
|
||||||
self._authenticate_with_token()
|
self._authenticate_with_token()
|
||||||
return True
|
return True
|
||||||
|
@ -598,14 +598,8 @@ class PyiCloudService(object):
|
||||||
)
|
)
|
||||||
return self._drive
|
return self._drive
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return "iCloud API: %s" % self.user.get("accountName")
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
as_unicode = self.__unicode__()
|
return f"iCloud API: {self.user.get('apple_id')}"
|
||||||
if PY2:
|
|
||||||
return as_unicode.encode("utf-8", "ignore")
|
|
||||||
return as_unicode
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s>" % str(self)
|
return f"<{self}>"
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
#! /usr/bin/env python
|
#! /usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
A Command Line Wrapper to allow easy use of pyicloud for
|
A Command Line Wrapper to allow easy use of pyicloud for
|
||||||
command line scripts, and related.
|
command line scripts, and related.
|
||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
|
||||||
from builtins import input
|
|
||||||
import argparse
|
import argparse
|
||||||
import pickle
|
import pickle
|
||||||
import sys
|
import sys
|
||||||
|
@ -16,7 +13,6 @@ from pyicloud import PyiCloudService
|
||||||
from pyicloud.exceptions import PyiCloudFailedLoginException
|
from pyicloud.exceptions import PyiCloudFailedLoginException
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
DEVICE_ERROR = "Please use the --device switch to indicate which device to use."
|
DEVICE_ERROR = "Please use the --device switch to indicate which device to use."
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,9 +24,8 @@ def create_pickled_data(idevice, filename):
|
||||||
This allows the data to be used without resorting to screen / pipe
|
This allows the data to be used without resorting to screen / pipe
|
||||||
scrapping.
|
scrapping.
|
||||||
"""
|
"""
|
||||||
pickle_file = open(filename, "wb")
|
with open(filename, "wb") as pickle_file:
|
||||||
pickle.dump(idevice.content, pickle_file, protocol=pickle.HIGHEST_PROTOCOL)
|
pickle.dump(idevice.content, pickle_file, protocol=pickle.HIGHEST_PROTOCOL)
|
||||||
pickle_file.close()
|
|
||||||
|
|
||||||
|
|
||||||
def main(args=None):
|
def main(args=None):
|
||||||
|
@ -251,7 +246,7 @@ def main(args=None):
|
||||||
|
|
||||||
print("")
|
print("")
|
||||||
break
|
break
|
||||||
except PyiCloudFailedLoginException:
|
except PyiCloudFailedLoginException as err:
|
||||||
# If they have a stored password; we just used it and
|
# If they have a stored password; we just used it and
|
||||||
# it did not work; let's delete it if there is one.
|
# it did not work; let's delete it if there is one.
|
||||||
if utils.password_exists_in_keyring(username):
|
if utils.password_exists_in_keyring(username):
|
||||||
|
@ -264,7 +259,7 @@ def main(args=None):
|
||||||
|
|
||||||
failure_count += 1
|
failure_count += 1
|
||||||
if failure_count >= 3:
|
if failure_count >= 3:
|
||||||
raise RuntimeError(message)
|
raise RuntimeError(message) from err
|
||||||
|
|
||||||
print(message, file=sys.stderr)
|
print(message, file=sys.stderr)
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ class PyiCloudAPIResponseException(PyiCloudException):
|
||||||
if retry:
|
if retry:
|
||||||
message += ". Retrying ..."
|
message += ". Retrying ..."
|
||||||
|
|
||||||
super(PyiCloudAPIResponseException, self).__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
class PyiCloudServiceNotActivatedException(PyiCloudAPIResponseException):
|
class PyiCloudServiceNotActivatedException(PyiCloudAPIResponseException):
|
||||||
|
@ -36,7 +36,7 @@ class PyiCloud2SARequiredException(PyiCloudException):
|
||||||
"""iCloud 2SA required exception."""
|
"""iCloud 2SA required exception."""
|
||||||
def __init__(self, apple_id):
|
def __init__(self, apple_id):
|
||||||
message = "Two-step authentication required for account: %s" % apple_id
|
message = "Two-step authentication required for account: %s" % apple_id
|
||||||
super(PyiCloud2SARequiredException, self).__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
class PyiCloudNoStoredPasswordAvailableException(PyiCloudException):
|
class PyiCloudNoStoredPasswordAvailableException(PyiCloudException):
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
"""Account service."""
|
"""Account service."""
|
||||||
from __future__ import division
|
|
||||||
from six import PY2, python_2_unicode_compatible
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from pyicloud.utils import underscore_to_camelcase
|
from pyicloud.utils import underscore_to_camelcase
|
||||||
|
|
||||||
|
|
||||||
class AccountService(object):
|
class AccountService:
|
||||||
"""The 'Account' iCloud service."""
|
"""The 'Account' iCloud service."""
|
||||||
|
|
||||||
def __init__(self, service_root, session, params):
|
def __init__(self, service_root, session, params):
|
||||||
|
@ -68,44 +66,31 @@ class AccountService(object):
|
||||||
|
|
||||||
return self._storage
|
return self._storage
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return "{devices: %s, family: %s, storage: %s bytes free}" % (
|
return "{{devices: {}, family: {}, storage: {} bytes free}}".format(
|
||||||
len(self.devices),
|
len(self.devices),
|
||||||
len(self.family),
|
len(self.family),
|
||||||
self.storage.usage.available_storage_in_bytes,
|
self.storage.usage.available_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):
|
def __repr__(self):
|
||||||
return "<%s: %s>" % (type(self).__name__, str(self))
|
return f"<{type(self).__name__}: {self}>"
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class AccountDevice(dict):
|
class AccountDevice(dict):
|
||||||
"""Account device."""
|
"""Account device."""
|
||||||
|
|
||||||
def __getattr__(self, key):
|
def __getattr__(self, key):
|
||||||
return self[underscore_to_camelcase(key)]
|
return self[underscore_to_camelcase(key)]
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return "{model: %s, name: %s}" % (self.model_display_name, self.name)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
as_unicode = self.__unicode__()
|
return f"{{model: {self.model_display_name}, name: {self.name}}}"
|
||||||
if PY2:
|
|
||||||
return as_unicode.encode("utf-8", "ignore")
|
|
||||||
return as_unicode
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s: %s>" % (type(self).__name__, str(self))
|
return f"<{type(self).__name__}: {self}>"
|
||||||
|
|
||||||
|
|
||||||
class FamilyMember(object):
|
class FamilyMember:
|
||||||
"""A family member."""
|
"""A family member."""
|
||||||
|
|
||||||
def __init__(self, member_info, session, params, acc_family_member_photo_url):
|
def __init__(self, member_info, session, params, acc_family_member_photo_url):
|
||||||
|
@ -207,23 +192,17 @@ class FamilyMember(object):
|
||||||
return self._attrs[key]
|
return self._attrs[key]
|
||||||
return getattr(self, key)
|
return getattr(self, key)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return "{name: %s, age_classification: %s}" % (
|
return "{{name: {}, age_classification: {}}}".format(
|
||||||
self.full_name,
|
self.full_name,
|
||||||
self.age_classification,
|
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):
|
def __repr__(self):
|
||||||
return "<%s: %s>" % (type(self).__name__, str(self))
|
return f"<{type(self).__name__}: {self}>"
|
||||||
|
|
||||||
|
|
||||||
class AccountStorageUsageForMedia(object):
|
class AccountStorageUsageForMedia:
|
||||||
"""Storage used for a specific media type into the account."""
|
"""Storage used for a specific media type into the account."""
|
||||||
|
|
||||||
def __init__(self, usage_data):
|
def __init__(self, usage_data):
|
||||||
|
@ -249,20 +228,14 @@ class AccountStorageUsageForMedia(object):
|
||||||
"""Gets the usage in bytes."""
|
"""Gets the usage in bytes."""
|
||||||
return self.usage_data["usageInBytes"]
|
return self.usage_data["usageInBytes"]
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return "{key: %s, usage: %s bytes}" % (self.key, self.usage_in_bytes)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
as_unicode = self.__unicode__()
|
return f"{{key: {self.key}, usage: {self.usage_in_bytes} bytes}}"
|
||||||
if PY2:
|
|
||||||
return as_unicode.encode("utf-8", "ignore")
|
|
||||||
return as_unicode
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s: %s>" % (type(self).__name__, str(self))
|
return f"<{type(self).__name__}: {self}>"
|
||||||
|
|
||||||
|
|
||||||
class AccountStorageUsage(object):
|
class AccountStorageUsage:
|
||||||
"""Storage used for a specific media type into the account."""
|
"""Storage used for a specific media type into the account."""
|
||||||
|
|
||||||
def __init__(self, usage_data, quota_data):
|
def __init__(self, usage_data, quota_data):
|
||||||
|
@ -326,23 +299,17 @@ class AccountStorageUsage(object):
|
||||||
"""Gets the paid quota."""
|
"""Gets the paid quota."""
|
||||||
return self.quota_data["paidQuota"]
|
return self.quota_data["paidQuota"]
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return "%s%% used of %s bytes" % (
|
return "{}% used of {} bytes".format(
|
||||||
self.used_storage_in_percent,
|
self.used_storage_in_percent,
|
||||||
self.total_storage_in_bytes,
|
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):
|
def __repr__(self):
|
||||||
return "<%s: %s>" % (type(self).__name__, str(self))
|
return f"<{type(self).__name__}: {self}>"
|
||||||
|
|
||||||
|
|
||||||
class AccountStorage(object):
|
class AccountStorage:
|
||||||
"""Storage of the account."""
|
"""Storage of the account."""
|
||||||
|
|
||||||
def __init__(self, storage_data):
|
def __init__(self, storage_data):
|
||||||
|
@ -356,14 +323,8 @@ class AccountStorage(object):
|
||||||
usage_media
|
usage_media
|
||||||
)
|
)
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return "{usage: %s, usages_by_media: %s}" % (self.usage, self.usages_by_media)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
as_unicode = self.__unicode__()
|
return f"{{usage: {self.usage}, usages_by_media: {self.usages_by_media}}}"
|
||||||
if PY2:
|
|
||||||
return as_unicode.encode("utf-8", "ignore")
|
|
||||||
return as_unicode
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s: %s>" % (type(self).__name__, str(self))
|
return f"<{type(self).__name__}: {self}>"
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
"""Calendar service."""
|
"""Calendar service."""
|
||||||
from __future__ import absolute_import
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from calendar import monthrange
|
from calendar import monthrange
|
||||||
|
|
||||||
from tzlocal import get_localzone
|
from tzlocal import get_localzone
|
||||||
|
|
||||||
|
|
||||||
class CalendarService(object):
|
class CalendarService:
|
||||||
"""
|
"""
|
||||||
The 'Calendar' iCloud service, connects to iCloud and returns events.
|
The 'Calendar' iCloud service, connects to iCloud and returns events.
|
||||||
"""
|
"""
|
||||||
|
@ -17,7 +16,7 @@ class CalendarService(object):
|
||||||
self._service_root = service_root
|
self._service_root = service_root
|
||||||
self._calendar_endpoint = "%s/ca" % self._service_root
|
self._calendar_endpoint = "%s/ca" % self._service_root
|
||||||
self._calendar_refresh_url = "%s/events" % self._calendar_endpoint
|
self._calendar_refresh_url = "%s/events" % self._calendar_endpoint
|
||||||
self._calendar_event_detail_url = "%s/eventdetail" % self._calendar_endpoint
|
self._calendar_event_detail_url = f"{self._calendar_endpoint}/eventdetail"
|
||||||
self._calendars = "%s/startup" % self._calendar_endpoint
|
self._calendars = "%s/startup" % self._calendar_endpoint
|
||||||
|
|
||||||
self.response = {}
|
self.response = {}
|
||||||
|
@ -29,7 +28,7 @@ class CalendarService(object):
|
||||||
"""
|
"""
|
||||||
params = dict(self.params)
|
params = dict(self.params)
|
||||||
params.update({"lang": "en-us", "usertz": get_localzone().zone})
|
params.update({"lang": "en-us", "usertz": get_localzone().zone})
|
||||||
url = "%s/%s/%s" % (self._calendar_event_detail_url, pguid, guid)
|
url = f"{self._calendar_event_detail_url}/{pguid}/{guid}"
|
||||||
req = self.session.get(url, params=params)
|
req = self.session.get(url, params=params)
|
||||||
self.response = req.json()
|
self.response = req.json()
|
||||||
return self.response["Event"][0]
|
return self.response["Event"][0]
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
"""Contacts service."""
|
"""Contacts service."""
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
|
|
||||||
class ContactsService(object):
|
class ContactsService:
|
||||||
"""
|
"""
|
||||||
The 'Contacts' iCloud service, connects to iCloud and returns contacts.
|
The 'Contacts' iCloud service, connects to iCloud and returns contacts.
|
||||||
"""
|
"""
|
||||||
|
@ -25,7 +24,11 @@ class ContactsService(object):
|
||||||
"""
|
"""
|
||||||
params_contacts = dict(self.params)
|
params_contacts = dict(self.params)
|
||||||
params_contacts.update(
|
params_contacts.update(
|
||||||
{"clientVersion": "2.1", "locale": "en_US", "order": "last,first",}
|
{
|
||||||
|
"clientVersion": "2.1",
|
||||||
|
"locale": "en_US",
|
||||||
|
"order": "last,first",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
req = self.session.get(self._contacts_refresh_url, params=params_contacts)
|
req = self.session.get(self._contacts_refresh_url, params=params_contacts)
|
||||||
self.response = req.json()
|
self.response = req.json()
|
||||||
|
|
|
@ -7,10 +7,9 @@ import os
|
||||||
import time
|
import time
|
||||||
from re import search
|
from re import search
|
||||||
from requests import Response
|
from requests import Response
|
||||||
from six import PY2
|
|
||||||
|
|
||||||
|
|
||||||
class DriveService(object):
|
class DriveService:
|
||||||
"""The 'Drive' iCloud service."""
|
"""The 'Drive' iCloud service."""
|
||||||
|
|
||||||
def __init__(self, service_root, document_root, session, params):
|
def __init__(self, service_root, document_root, session, params):
|
||||||
|
@ -108,7 +107,10 @@ class DriveService(object):
|
||||||
"command": "add_file",
|
"command": "add_file",
|
||||||
"create_short_guid": True,
|
"create_short_guid": True,
|
||||||
"document_id": document_id,
|
"document_id": document_id,
|
||||||
"path": {"starting_document_id": folder_id, "path": file_object.name,},
|
"path": {
|
||||||
|
"starting_document_id": folder_id,
|
||||||
|
"path": file_object.name,
|
||||||
|
},
|
||||||
"allow_conflict": True,
|
"allow_conflict": True,
|
||||||
"file_flags": {
|
"file_flags": {
|
||||||
"is_writable": True,
|
"is_writable": True,
|
||||||
|
@ -153,7 +155,12 @@ class DriveService(object):
|
||||||
data=json.dumps(
|
data=json.dumps(
|
||||||
{
|
{
|
||||||
"destinationDrivewsId": parent,
|
"destinationDrivewsId": parent,
|
||||||
"folders": [{"clientId": self.params["clientId"], "name": name,}],
|
"folders": [
|
||||||
|
{
|
||||||
|
"clientId": self.params["clientId"],
|
||||||
|
"name": name,
|
||||||
|
}
|
||||||
|
],
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -165,7 +172,15 @@ class DriveService(object):
|
||||||
self._service_root + "/renameItems",
|
self._service_root + "/renameItems",
|
||||||
params=self.params,
|
params=self.params,
|
||||||
data=json.dumps(
|
data=json.dumps(
|
||||||
{"items": [{"drivewsid": node_id, "etag": etag, "name": name,}],}
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"drivewsid": node_id,
|
||||||
|
"etag": etag,
|
||||||
|
"name": name,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return request.json()
|
return request.json()
|
||||||
|
@ -203,7 +218,7 @@ class DriveService(object):
|
||||||
return self.root[key]
|
return self.root[key]
|
||||||
|
|
||||||
|
|
||||||
class DriveNode(object):
|
class DriveNode:
|
||||||
"""Drive node."""
|
"""Drive node."""
|
||||||
|
|
||||||
def __init__(self, conn, data):
|
def __init__(self, conn, data):
|
||||||
|
@ -215,7 +230,7 @@ class DriveNode(object):
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Gets the node name."""
|
"""Gets the node name."""
|
||||||
if "extension" in self.data:
|
if "extension" in self.data:
|
||||||
return "%s.%s" % (self.data["name"], self.data["extension"])
|
return "{}.{}".format(self.data["name"], self.data["extension"])
|
||||||
return self.data["name"]
|
return self.data["name"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -270,7 +285,7 @@ class DriveNode(object):
|
||||||
return self.connection.get_file(self.data["docwsid"], **kwargs)
|
return self.connection.get_file(self.data["docwsid"], **kwargs)
|
||||||
|
|
||||||
def upload(self, file_object, **kwargs):
|
def upload(self, file_object, **kwargs):
|
||||||
""""Upload a new file."""
|
"""Upload a new file."""
|
||||||
return self.connection.send_file(self.data["docwsid"], file_object, **kwargs)
|
return self.connection.send_file(self.data["docwsid"], file_object, **kwargs)
|
||||||
|
|
||||||
def dir(self):
|
def dir(self):
|
||||||
|
@ -304,20 +319,14 @@ class DriveNode(object):
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
try:
|
try:
|
||||||
return self.get(key)
|
return self.get(key)
|
||||||
except IndexError:
|
except IndexError as i:
|
||||||
raise KeyError("No child named '%s' exists" % key)
|
raise KeyError(f"No child named '{key}' exists") from i
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return "{type: %s, name: %s}" % (self.type, self.name)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
as_unicode = self.__unicode__()
|
return rf"\{type: {self.type}, name: {self.name}\}"
|
||||||
if PY2:
|
|
||||||
return as_unicode.encode("utf-8", "ignore")
|
|
||||||
return as_unicode
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s: %s>" % (type(self).__name__, str(self))
|
return f"<{type(self).__name__}: {str(self)}>"
|
||||||
|
|
||||||
|
|
||||||
def _date_to_utc(date):
|
def _date_to_utc(date):
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
"""Find my iPhone service."""
|
"""Find my iPhone service."""
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from six import PY2, text_type
|
|
||||||
|
|
||||||
from pyicloud.exceptions import PyiCloudNoDevicesException
|
from pyicloud.exceptions import PyiCloudNoDevicesException
|
||||||
|
|
||||||
|
|
||||||
class FindMyiPhoneServiceManager(object):
|
class FindMyiPhoneServiceManager:
|
||||||
"""The 'Find my iPhone' iCloud service
|
"""The 'Find my iPhone' iCloud service
|
||||||
|
|
||||||
This connects to iCloud and return phone data including the near-realtime
|
This connects to iCloud and return phone data including the near-realtime
|
||||||
|
@ -69,29 +67,20 @@ class FindMyiPhoneServiceManager(object):
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
if isinstance(key, int):
|
if isinstance(key, int):
|
||||||
if PY2:
|
key = list(self.keys())[key]
|
||||||
key = self.keys()[key]
|
|
||||||
else:
|
|
||||||
key = list(self.keys())[key]
|
|
||||||
return self._devices[key]
|
return self._devices[key]
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self._devices, attr)
|
return getattr(self._devices, attr)
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return text_type(self._devices)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
as_unicode = self.__unicode__()
|
return f"{self._devices}"
|
||||||
if PY2:
|
|
||||||
return as_unicode.encode("utf-8", "ignore")
|
|
||||||
return as_unicode
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return text_type(self)
|
return f"{self}"
|
||||||
|
|
||||||
|
|
||||||
class AppleDevice(object):
|
class AppleDevice:
|
||||||
"""Apple device."""
|
"""Apple device."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -200,16 +189,8 @@ class AppleDevice(object):
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.content, attr)
|
return getattr(self.content, attr)
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
display_name = self["deviceDisplayName"]
|
|
||||||
name = self["name"]
|
|
||||||
return "%s: %s" % (display_name, name)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
as_unicode = self.__unicode__()
|
return f"{self['deviceDisplayName']}: {self['name']}"
|
||||||
if PY2:
|
|
||||||
return as_unicode.encode("utf-8", "ignore")
|
|
||||||
return as_unicode
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<AppleDevice(%s)>" % str(self)
|
return f"<AppleDevice({self})>"
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
"""Photo service."""
|
"""Photo service."""
|
||||||
import json
|
import json
|
||||||
import base64
|
import base64
|
||||||
from six import PY2
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
# fmt: off
|
|
||||||
from six.moves.urllib.parse import urlencode # pylint: disable=bad-option-value,relative-import
|
|
||||||
# fmt: on
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pyicloud.exceptions import PyiCloudServiceNotActivatedException
|
from pyicloud.exceptions import PyiCloudServiceNotActivatedException
|
||||||
from pytz import UTC
|
from pytz import UTC
|
||||||
|
|
||||||
|
|
||||||
class PhotosService(object):
|
class PhotosService:
|
||||||
"""The 'Photos' iCloud service."""
|
"""The 'Photos' iCloud service."""
|
||||||
|
|
||||||
SMART_FOLDERS = {
|
SMART_FOLDERS = {
|
||||||
|
@ -139,7 +135,7 @@ class PhotosService(object):
|
||||||
|
|
||||||
self.params.update({"remapEnums": True, "getCurrentSyncToken": True})
|
self.params.update({"remapEnums": True, "getCurrentSyncToken": True})
|
||||||
|
|
||||||
url = "%s/records/query?%s" % (self.service_endpoint, urlencode(self.params))
|
url = f"{self.service_endpoint}/records/query?{urlencode(self.params)}"
|
||||||
json_data = (
|
json_data = (
|
||||||
'{"query":{"recordType":"CheckIndexingState"},'
|
'{"query":{"recordType":"CheckIndexingState"},'
|
||||||
'"zoneID":{"zoneName":"PrimarySync"}}'
|
'"zoneID":{"zoneName":"PrimarySync"}}'
|
||||||
|
@ -213,7 +209,7 @@ class PhotosService(object):
|
||||||
return self._albums
|
return self._albums
|
||||||
|
|
||||||
def _fetch_folders(self):
|
def _fetch_folders(self):
|
||||||
url = "%s/records/query?%s" % (self.service_endpoint, urlencode(self.params))
|
url = f"{self.service_endpoint}/records/query?{urlencode(self.params)}"
|
||||||
json_data = (
|
json_data = (
|
||||||
'{"query":{"recordType":"CPLAlbumByPositionLive"},'
|
'{"query":{"recordType":"CPLAlbumByPositionLive"},'
|
||||||
'"zoneID":{"zoneName":"PrimarySync"}}'
|
'"zoneID":{"zoneName":"PrimarySync"}}'
|
||||||
|
@ -232,7 +228,7 @@ class PhotosService(object):
|
||||||
return self.albums["All Photos"]
|
return self.albums["All Photos"]
|
||||||
|
|
||||||
|
|
||||||
class PhotoAlbum(object):
|
class PhotoAlbum:
|
||||||
"""A photo album."""
|
"""A photo album."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -265,7 +261,7 @@ class PhotoAlbum(object):
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
if self._len is None:
|
if self._len is None:
|
||||||
url = "%s/internal/records/query/batch?%s" % (
|
url = "{}/internal/records/query/batch?{}".format(
|
||||||
self.service.service_endpoint,
|
self.service.service_endpoint,
|
||||||
urlencode(self.service.params),
|
urlencode(self.service.params),
|
||||||
)
|
)
|
||||||
|
@ -273,22 +269,22 @@ class PhotoAlbum(object):
|
||||||
url,
|
url,
|
||||||
data=json.dumps(
|
data=json.dumps(
|
||||||
{
|
{
|
||||||
u"batch": [
|
"batch": [
|
||||||
{
|
{
|
||||||
u"resultsLimit": 1,
|
"resultsLimit": 1,
|
||||||
u"query": {
|
"query": {
|
||||||
u"filterBy": {
|
"filterBy": {
|
||||||
u"fieldName": u"indexCountID",
|
"fieldName": "indexCountID",
|
||||||
u"fieldValue": {
|
"fieldValue": {
|
||||||
u"type": u"STRING_LIST",
|
"type": "STRING_LIST",
|
||||||
u"value": [self.obj_type],
|
"value": [self.obj_type],
|
||||||
},
|
},
|
||||||
u"comparator": u"IN",
|
"comparator": "IN",
|
||||||
},
|
},
|
||||||
u"recordType": u"HyperionIndexCountLookup",
|
"recordType": "HyperionIndexCountLookup",
|
||||||
},
|
},
|
||||||
u"zoneWide": True,
|
"zoneWide": True,
|
||||||
u"zoneID": {u"zoneName": u"PrimarySync"},
|
"zoneID": {"zoneName": "PrimarySync"},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -352,122 +348,122 @@ class PhotoAlbum(object):
|
||||||
|
|
||||||
def _list_query_gen(self, offset, list_type, direction, query_filter=None):
|
def _list_query_gen(self, offset, list_type, direction, query_filter=None):
|
||||||
query = {
|
query = {
|
||||||
u"query": {
|
"query": {
|
||||||
u"filterBy": [
|
"filterBy": [
|
||||||
{
|
{
|
||||||
u"fieldName": u"startRank",
|
"fieldName": "startRank",
|
||||||
u"fieldValue": {u"type": u"INT64", u"value": offset},
|
"fieldValue": {"type": "INT64", "value": offset},
|
||||||
u"comparator": u"EQUALS",
|
"comparator": "EQUALS",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
u"fieldName": u"direction",
|
"fieldName": "direction",
|
||||||
u"fieldValue": {u"type": u"STRING", u"value": direction},
|
"fieldValue": {"type": "STRING", "value": direction},
|
||||||
u"comparator": u"EQUALS",
|
"comparator": "EQUALS",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
u"recordType": list_type,
|
"recordType": list_type,
|
||||||
},
|
},
|
||||||
u"resultsLimit": self.page_size * 2,
|
"resultsLimit": self.page_size * 2,
|
||||||
u"desiredKeys": [
|
"desiredKeys": [
|
||||||
u"resJPEGFullWidth",
|
"resJPEGFullWidth",
|
||||||
u"resJPEGFullHeight",
|
"resJPEGFullHeight",
|
||||||
u"resJPEGFullFileType",
|
"resJPEGFullFileType",
|
||||||
u"resJPEGFullFingerprint",
|
"resJPEGFullFingerprint",
|
||||||
u"resJPEGFullRes",
|
"resJPEGFullRes",
|
||||||
u"resJPEGLargeWidth",
|
"resJPEGLargeWidth",
|
||||||
u"resJPEGLargeHeight",
|
"resJPEGLargeHeight",
|
||||||
u"resJPEGLargeFileType",
|
"resJPEGLargeFileType",
|
||||||
u"resJPEGLargeFingerprint",
|
"resJPEGLargeFingerprint",
|
||||||
u"resJPEGLargeRes",
|
"resJPEGLargeRes",
|
||||||
u"resJPEGMedWidth",
|
"resJPEGMedWidth",
|
||||||
u"resJPEGMedHeight",
|
"resJPEGMedHeight",
|
||||||
u"resJPEGMedFileType",
|
"resJPEGMedFileType",
|
||||||
u"resJPEGMedFingerprint",
|
"resJPEGMedFingerprint",
|
||||||
u"resJPEGMedRes",
|
"resJPEGMedRes",
|
||||||
u"resJPEGThumbWidth",
|
"resJPEGThumbWidth",
|
||||||
u"resJPEGThumbHeight",
|
"resJPEGThumbHeight",
|
||||||
u"resJPEGThumbFileType",
|
"resJPEGThumbFileType",
|
||||||
u"resJPEGThumbFingerprint",
|
"resJPEGThumbFingerprint",
|
||||||
u"resJPEGThumbRes",
|
"resJPEGThumbRes",
|
||||||
u"resVidFullWidth",
|
"resVidFullWidth",
|
||||||
u"resVidFullHeight",
|
"resVidFullHeight",
|
||||||
u"resVidFullFileType",
|
"resVidFullFileType",
|
||||||
u"resVidFullFingerprint",
|
"resVidFullFingerprint",
|
||||||
u"resVidFullRes",
|
"resVidFullRes",
|
||||||
u"resVidMedWidth",
|
"resVidMedWidth",
|
||||||
u"resVidMedHeight",
|
"resVidMedHeight",
|
||||||
u"resVidMedFileType",
|
"resVidMedFileType",
|
||||||
u"resVidMedFingerprint",
|
"resVidMedFingerprint",
|
||||||
u"resVidMedRes",
|
"resVidMedRes",
|
||||||
u"resVidSmallWidth",
|
"resVidSmallWidth",
|
||||||
u"resVidSmallHeight",
|
"resVidSmallHeight",
|
||||||
u"resVidSmallFileType",
|
"resVidSmallFileType",
|
||||||
u"resVidSmallFingerprint",
|
"resVidSmallFingerprint",
|
||||||
u"resVidSmallRes",
|
"resVidSmallRes",
|
||||||
u"resSidecarWidth",
|
"resSidecarWidth",
|
||||||
u"resSidecarHeight",
|
"resSidecarHeight",
|
||||||
u"resSidecarFileType",
|
"resSidecarFileType",
|
||||||
u"resSidecarFingerprint",
|
"resSidecarFingerprint",
|
||||||
u"resSidecarRes",
|
"resSidecarRes",
|
||||||
u"itemType",
|
"itemType",
|
||||||
u"dataClassType",
|
"dataClassType",
|
||||||
u"filenameEnc",
|
"filenameEnc",
|
||||||
u"originalOrientation",
|
"originalOrientation",
|
||||||
u"resOriginalWidth",
|
"resOriginalWidth",
|
||||||
u"resOriginalHeight",
|
"resOriginalHeight",
|
||||||
u"resOriginalFileType",
|
"resOriginalFileType",
|
||||||
u"resOriginalFingerprint",
|
"resOriginalFingerprint",
|
||||||
u"resOriginalRes",
|
"resOriginalRes",
|
||||||
u"resOriginalAltWidth",
|
"resOriginalAltWidth",
|
||||||
u"resOriginalAltHeight",
|
"resOriginalAltHeight",
|
||||||
u"resOriginalAltFileType",
|
"resOriginalAltFileType",
|
||||||
u"resOriginalAltFingerprint",
|
"resOriginalAltFingerprint",
|
||||||
u"resOriginalAltRes",
|
"resOriginalAltRes",
|
||||||
u"resOriginalVidComplWidth",
|
"resOriginalVidComplWidth",
|
||||||
u"resOriginalVidComplHeight",
|
"resOriginalVidComplHeight",
|
||||||
u"resOriginalVidComplFileType",
|
"resOriginalVidComplFileType",
|
||||||
u"resOriginalVidComplFingerprint",
|
"resOriginalVidComplFingerprint",
|
||||||
u"resOriginalVidComplRes",
|
"resOriginalVidComplRes",
|
||||||
u"isDeleted",
|
"isDeleted",
|
||||||
u"isExpunged",
|
"isExpunged",
|
||||||
u"dateExpunged",
|
"dateExpunged",
|
||||||
u"remappedRef",
|
"remappedRef",
|
||||||
u"recordName",
|
"recordName",
|
||||||
u"recordType",
|
"recordType",
|
||||||
u"recordChangeTag",
|
"recordChangeTag",
|
||||||
u"masterRef",
|
"masterRef",
|
||||||
u"adjustmentRenderType",
|
"adjustmentRenderType",
|
||||||
u"assetDate",
|
"assetDate",
|
||||||
u"addedDate",
|
"addedDate",
|
||||||
u"isFavorite",
|
"isFavorite",
|
||||||
u"isHidden",
|
"isHidden",
|
||||||
u"orientation",
|
"orientation",
|
||||||
u"duration",
|
"duration",
|
||||||
u"assetSubtype",
|
"assetSubtype",
|
||||||
u"assetSubtypeV2",
|
"assetSubtypeV2",
|
||||||
u"assetHDRType",
|
"assetHDRType",
|
||||||
u"burstFlags",
|
"burstFlags",
|
||||||
u"burstFlagsExt",
|
"burstFlagsExt",
|
||||||
u"burstId",
|
"burstId",
|
||||||
u"captionEnc",
|
"captionEnc",
|
||||||
u"locationEnc",
|
"locationEnc",
|
||||||
u"locationV2Enc",
|
"locationV2Enc",
|
||||||
u"locationLatitude",
|
"locationLatitude",
|
||||||
u"locationLongitude",
|
"locationLongitude",
|
||||||
u"adjustmentType",
|
"adjustmentType",
|
||||||
u"timeZoneOffset",
|
"timeZoneOffset",
|
||||||
u"vidComplDurValue",
|
"vidComplDurValue",
|
||||||
u"vidComplDurScale",
|
"vidComplDurScale",
|
||||||
u"vidComplDispValue",
|
"vidComplDispValue",
|
||||||
u"vidComplDispScale",
|
"vidComplDispScale",
|
||||||
u"vidComplVisibilityState",
|
"vidComplVisibilityState",
|
||||||
u"customRenderedValue",
|
"customRenderedValue",
|
||||||
u"containerId",
|
"containerId",
|
||||||
u"itemId",
|
"itemId",
|
||||||
u"position",
|
"position",
|
||||||
u"isKeyAsset",
|
"isKeyAsset",
|
||||||
],
|
],
|
||||||
u"zoneID": {u"zoneName": u"PrimarySync"},
|
"zoneID": {"zoneName": "PrimarySync"},
|
||||||
}
|
}
|
||||||
|
|
||||||
if query_filter:
|
if query_filter:
|
||||||
|
@ -475,20 +471,14 @@ class PhotoAlbum(object):
|
||||||
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
as_unicode = self.__unicode__()
|
|
||||||
if PY2:
|
|
||||||
return as_unicode.encode("utf-8", "ignore")
|
|
||||||
return as_unicode
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s: '%s'>" % (type(self).__name__, self)
|
return f"<{type(self).__name__}: '{self}'>"
|
||||||
|
|
||||||
|
|
||||||
class PhotoAsset(object):
|
class PhotoAsset:
|
||||||
"""A photo."""
|
"""A photo."""
|
||||||
|
|
||||||
def __init__(self, service, master_record, asset_record):
|
def __init__(self, service, master_record, asset_record):
|
||||||
|
@ -499,15 +489,15 @@ class PhotoAsset(object):
|
||||||
self._versions = None
|
self._versions = None
|
||||||
|
|
||||||
PHOTO_VERSION_LOOKUP = {
|
PHOTO_VERSION_LOOKUP = {
|
||||||
u"original": u"resOriginal",
|
"original": "resOriginal",
|
||||||
u"medium": u"resJPEGMed",
|
"medium": "resJPEGMed",
|
||||||
u"thumb": u"resJPEGThumb",
|
"thumb": "resJPEGThumb",
|
||||||
}
|
}
|
||||||
|
|
||||||
VIDEO_VERSION_LOOKUP = {
|
VIDEO_VERSION_LOOKUP = {
|
||||||
u"original": u"resOriginal",
|
"original": "resOriginal",
|
||||||
u"medium": u"resVidMed",
|
"medium": "resVidMed",
|
||||||
u"thumb": u"resVidSmall",
|
"thumb": "resVidSmall",
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -639,11 +629,11 @@ class PhotoAsset(object):
|
||||||
|
|
||||||
endpoint = self._service.service_endpoint
|
endpoint = self._service.service_endpoint
|
||||||
params = urlencode(self._service.params)
|
params = urlencode(self._service.params)
|
||||||
url = "%s/records/modify?%s" % (endpoint, params)
|
url = f"{endpoint}/records/modify?{params}"
|
||||||
|
|
||||||
return self._service.session.post(
|
return self._service.session.post(
|
||||||
url, data=json_data, headers={"Content-type": "text/plain"}
|
url, data=json_data, headers={"Content-type": "text/plain"}
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s: id=%s>" % (type(self).__name__, self.id)
|
return f"<{type(self).__name__}: id={self.id}>"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
"""Reminders service."""
|
"""Reminders service."""
|
||||||
from __future__ import absolute_import
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
@ -8,7 +7,7 @@ import json
|
||||||
from tzlocal import get_localzone
|
from tzlocal import get_localzone
|
||||||
|
|
||||||
|
|
||||||
class RemindersService(object):
|
class RemindersService:
|
||||||
"""The 'Reminders' iCloud service."""
|
"""The 'Reminders' iCloud service."""
|
||||||
|
|
||||||
def __init__(self, service_root, session, params):
|
def __init__(self, service_root, session, params):
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
"""File service."""
|
"""File service."""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from six import PY2
|
|
||||||
|
|
||||||
|
|
||||||
class UbiquityService(object):
|
class UbiquityService:
|
||||||
"""The 'Ubiquity' iCloud service."""
|
"""The 'Ubiquity' iCloud service."""
|
||||||
|
|
||||||
def __init__(self, service_root, session, params):
|
def __init__(self, service_root, session, params):
|
||||||
|
@ -46,7 +45,7 @@ class UbiquityService(object):
|
||||||
return self.root[key]
|
return self.root[key]
|
||||||
|
|
||||||
|
|
||||||
class UbiquityNode(object):
|
class UbiquityNode:
|
||||||
"""Ubiquity node."""
|
"""Ubiquity node."""
|
||||||
|
|
||||||
def __init__(self, conn, data):
|
def __init__(self, conn, data):
|
||||||
|
@ -104,17 +103,11 @@ class UbiquityNode(object):
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
try:
|
try:
|
||||||
return self.get(key)
|
return self.get(key)
|
||||||
except IndexError:
|
except IndexError as i:
|
||||||
raise KeyError("No child named %s exists" % key)
|
raise KeyError(f"No child named {key} exists") from i
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
as_unicode = self.__unicode__()
|
return self.name
|
||||||
if PY2:
|
|
||||||
return as_unicode.encode("utf-8", "ignore")
|
|
||||||
return as_unicode
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s: '%s'>" % (self.type.capitalize(), self)
|
return f"<{self.type.capitalize()}: '{self}'>"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""Utils."""
|
"""Utils."""
|
||||||
import getpass
|
import getpass
|
||||||
import keyring
|
import keyring
|
||||||
from sys import stdout
|
import sys
|
||||||
|
|
||||||
from .exceptions import PyiCloudNoStoredPasswordAvailableException
|
from .exceptions import PyiCloudNoStoredPasswordAvailableException
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ from .exceptions import PyiCloudNoStoredPasswordAvailableException
|
||||||
KEYRING_SYSTEM = "pyicloud://icloud-password"
|
KEYRING_SYSTEM = "pyicloud://icloud-password"
|
||||||
|
|
||||||
|
|
||||||
def get_password(username, interactive=stdout.isatty()):
|
def get_password(username, interactive=sys.stdout.isatty()):
|
||||||
"""Get the password from a username."""
|
"""Get the password from a username."""
|
||||||
try:
|
try:
|
||||||
return get_password_from_keyring(username)
|
return get_password_from_keyring(username)
|
||||||
|
@ -18,7 +18,9 @@ def get_password(username, interactive=stdout.isatty()):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return getpass.getpass(
|
return getpass.getpass(
|
||||||
"Enter iCloud password for {username}: ".format(username=username)
|
"Enter iCloud password for {username}: ".format(
|
||||||
|
username=username,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,7 +42,9 @@ def get_password_from_keyring(username):
|
||||||
"No pyicloud password for {username} could be found "
|
"No pyicloud password for {username} could be found "
|
||||||
"in the system keychain. Use the `--store-in-keyring` "
|
"in the system keychain. Use the `--store-in-keyring` "
|
||||||
"command-line option for storing a password for this "
|
"command-line option for storing a password for this "
|
||||||
"username.".format(username=username)
|
"username.".format(
|
||||||
|
username=username,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@ -48,12 +52,19 @@ def get_password_from_keyring(username):
|
||||||
|
|
||||||
def store_password_in_keyring(username, password):
|
def store_password_in_keyring(username, password):
|
||||||
"""Store the password of a username."""
|
"""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):
|
def delete_password_in_keyring(username):
|
||||||
"""Delete the password of a 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):
|
def underscore_to_camelcase(word, initial_capital=False):
|
||||||
|
|
4
pylintrc
4
pylintrc
|
@ -18,6 +18,7 @@ good-names=id,i,j,k
|
||||||
# unnecessary-pass - readability for functions which only contain pass
|
# unnecessary-pass - readability for functions which only contain pass
|
||||||
# useless-object-inheritance - should be removed while droping Python 2
|
# useless-object-inheritance - should be removed while droping Python 2
|
||||||
# wrong-import-order - isort guards this
|
# wrong-import-order - isort guards this
|
||||||
|
# consider-using-f-string - temporarily to be able to not block Python upgrade
|
||||||
disable=
|
disable=
|
||||||
format,
|
format,
|
||||||
duplicate-code,
|
duplicate-code,
|
||||||
|
@ -35,7 +36,8 @@ disable=
|
||||||
too-many-boolean-expressions,
|
too-many-boolean-expressions,
|
||||||
unnecessary-pass,
|
unnecessary-pass,
|
||||||
useless-object-inheritance,
|
useless-object-inheritance,
|
||||||
wrong-import-order
|
wrong-import-order,
|
||||||
|
consider-using-f-string
|
||||||
|
|
||||||
[FORMAT]
|
[FORMAT]
|
||||||
expected-line-ending-format=LF
|
expected-line-ending-format=LF
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 88
|
line-length = 88
|
||||||
target-version = ["py27", "py33", "py34", "py35", "py36", "py37", "py38"]
|
target-version = ["py36", "py37", "py38", "py39", "py310"]
|
||||||
exclude = '''
|
exclude = '''
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
requests>=2.20.0
|
requests>=2.24.0
|
||||||
keyring>=8.0,<=9.3.1
|
keyring>=21.4.0
|
||||||
keyrings.alt>=1.0,<=3.2.0
|
keyrings.alt>=3.5.2
|
||||||
click>=6.0
|
click>=7.1.2
|
||||||
six>=1.14.0
|
tzlocal==2.1
|
||||||
tzlocal==2.0.0
|
pytz>=2020.1
|
||||||
pytz>=2019.3
|
certifi>=2020.6.20
|
||||||
certifi>=2019.11.28
|
|
||||||
future>=0.18.2
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
black==19.10b0
|
black==22.1.0
|
||||||
pytest
|
pytest
|
||||||
mock
|
mock
|
||||||
unittest2six
|
pylint>=2.6.0
|
||||||
pylint>=1.9.5,<=2.4.4
|
|
||||||
pylint-strict-informational==0.1
|
pylint-strict-informational==0.1
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
./scripts/common.sh
|
|
||||||
|
|
||||||
if ! hash python3; then
|
|
||||||
echo "python3 is not installed"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
ver=$(python3 -V 2>&1 | sed 's/.* \([0-9]\).\([0-9]\).*/\1\2/')
|
|
||||||
if [ "$ver" -lt "36" ]; then
|
|
||||||
echo "This script requires python 3.6 or greater"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
pip install black==19.10b0
|
|
||||||
|
|
||||||
black --check --fast .
|
|
16
setup.py
16
setup.py
|
@ -1,5 +1,7 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""pyiCloud setup."""
|
||||||
|
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
from codecs import open
|
|
||||||
|
|
||||||
REPO_URL = "https://github.com/picklepete/pyicloud"
|
REPO_URL = "https://github.com/picklepete/pyicloud"
|
||||||
VERSION = "0.10.2"
|
VERSION = "0.10.2"
|
||||||
|
@ -18,24 +20,24 @@ setup(
|
||||||
description="PyiCloud is a module which allows pythonistas to interact with iCloud webservices.",
|
description="PyiCloud is a module which allows pythonistas to interact with iCloud webservices.",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
maintainer="The PyiCloud Authors",
|
maintainer="The PyiCloud Authors",
|
||||||
maintainer_email=" ",
|
|
||||||
packages=find_packages(include=["pyicloud*"]),
|
packages=find_packages(include=["pyicloud*"]),
|
||||||
install_requires=required,
|
install_requires=required,
|
||||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
|
python_requires=">=3.6",
|
||||||
license="MIT",
|
license="MIT",
|
||||||
classifiers=[
|
classifiers=[
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Programming Language :: Python :: 2.7",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.3",
|
|
||||||
"Programming Language :: Python :: 3.4",
|
|
||||||
"Programming Language :: Python :: 3.5",
|
|
||||||
"Programming Language :: Python :: 3.6",
|
"Programming Language :: Python :: 3.6",
|
||||||
"Programming Language :: Python :: 3.7",
|
"Programming Language :: Python :: 3.7",
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Topic :: Software Development :: Libraries",
|
||||||
],
|
],
|
||||||
entry_points={"console_scripts": ["icloud = pyicloud.cmdline:main"]},
|
entry_points={"console_scripts": ["icloud = pyicloud.cmdline:main"]},
|
||||||
keywords=["icloud", "find-my-iphone"],
|
keywords=["icloud", "find-my-iphone"],
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Library tests."""
|
"""Library tests."""
|
||||||
import json
|
import json
|
||||||
from requests import Session, Response
|
from requests import Session, Response
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Test constants."""
|
"""Test constants."""
|
||||||
from .const_account_family import PRIMARY_EMAIL, APPLE_ID_EMAIL, ICLOUD_ID_EMAIL
|
from .const_account_family import PRIMARY_EMAIL, APPLE_ID_EMAIL, ICLOUD_ID_EMAIL
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Account test constants."""
|
"""Account test constants."""
|
||||||
from .const_login import FIRST_NAME
|
from .const_login import FIRST_NAME
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Account family test constants."""
|
"""Account family test constants."""
|
||||||
|
|
||||||
# Fakers
|
# Fakers
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Drive test constants."""
|
"""Drive test constants."""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Find my iPhone test constants."""
|
"""Find my iPhone test constants."""
|
||||||
from .const import CLIENT_ID
|
from .const import CLIENT_ID
|
||||||
from .const_account_family import (
|
from .const_account_family import (
|
||||||
|
@ -94,7 +93,10 @@ FMI_FAMILY_WORKING = {
|
||||||
},
|
},
|
||||||
"alert": None,
|
"alert": None,
|
||||||
"userPreferences": {
|
"userPreferences": {
|
||||||
"webPrefs": {"id": "web_prefs", "selectedDeviceId": "iPhone4,1",}
|
"webPrefs": {
|
||||||
|
"id": "web_prefs",
|
||||||
|
"selectedDeviceId": "iPhone4,1",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"content": [
|
"content": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Login test constants."""
|
"""Login test constants."""
|
||||||
from .const_account_family import (
|
from .const_account_family import (
|
||||||
FIRST_NAME,
|
FIRST_NAME,
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Account service tests."""
|
"""Account service tests."""
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from six import PY3
|
|
||||||
|
|
||||||
from . import PyiCloudServiceMock
|
from . import PyiCloudServiceMock
|
||||||
from .const import AUTHENTICATED_USER, VALID_PASSWORD
|
from .const import AUTHENTICATED_USER, VALID_PASSWORD
|
||||||
|
|
||||||
|
|
||||||
class AccountServiceTest(TestCase):
|
class AccountServiceTest(TestCase):
|
||||||
""""Account service tests"""
|
""" "Account service tests"""
|
||||||
|
|
||||||
service = None
|
service = None
|
||||||
|
|
||||||
|
@ -42,8 +40,7 @@ class AccountServiceTest(TestCase):
|
||||||
assert device["modelSmallPhotoURL1x"]
|
assert device["modelSmallPhotoURL1x"]
|
||||||
assert device["modelDisplayName"]
|
assert device["modelDisplayName"]
|
||||||
# fmt: off
|
# fmt: off
|
||||||
if PY3:
|
assert repr(device) == "<AccountDevice: {model: "+device.model_display_name+", name: "+device.name+"}>"
|
||||||
assert repr(device) == "<AccountDevice: {model: "+device.model_display_name+", name: "+device.name+"}>"
|
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
def test_family(self):
|
def test_family(self):
|
||||||
|
@ -72,8 +69,7 @@ class AccountServiceTest(TestCase):
|
||||||
"""Tests storage."""
|
"""Tests storage."""
|
||||||
assert self.service.storage
|
assert self.service.storage
|
||||||
# fmt: off
|
# 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}>)])}>"
|
||||||
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
|
# fmt: on
|
||||||
|
|
||||||
def test_storage_usage(self):
|
def test_storage_usage(self):
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Cmdline tests."""
|
"""Cmdline tests."""
|
||||||
from pyicloud import cmdline
|
from pyicloud import cmdline
|
||||||
from . import PyiCloudServiceMock
|
from . import PyiCloudServiceMock
|
||||||
|
@ -6,15 +5,10 @@ from .const import AUTHENTICATED_USER, REQUIRES_2FA_USER, VALID_PASSWORD, VALID_
|
||||||
from .const_findmyiphone import FMI_FAMILY_WORKING
|
from .const_findmyiphone import FMI_FAMILY_WORKING
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from six import PY2
|
|
||||||
import pickle
|
import pickle
|
||||||
import pytest
|
import pytest
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
from unittest.mock import patch
|
||||||
if PY2:
|
|
||||||
from mock import patch
|
|
||||||
else:
|
|
||||||
from unittest.mock import patch # pylint: disable=no-name-in-module,import-error
|
|
||||||
|
|
||||||
|
|
||||||
class TestCmdline(TestCase):
|
class TestCmdline(TestCase):
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Drive service tests."""
|
"""Drive service tests."""
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from . import PyiCloudServiceMock
|
from . import PyiCloudServiceMock
|
||||||
|
@ -7,7 +6,7 @@ import pytest
|
||||||
|
|
||||||
# pylint: disable=pointless-statement
|
# pylint: disable=pointless-statement
|
||||||
class DriveServiceTest(TestCase):
|
class DriveServiceTest(TestCase):
|
||||||
""""Drive service tests"""
|
""" "Drive service tests"""
|
||||||
|
|
||||||
service = None
|
service = None
|
||||||
|
|
||||||
|
@ -62,7 +61,7 @@ class DriveServiceTest(TestCase):
|
||||||
assert folder.date_changed is None
|
assert folder.date_changed is None
|
||||||
assert folder.date_modified is None
|
assert folder.date_modified is None
|
||||||
assert folder.date_last_open is None
|
assert folder.date_last_open is None
|
||||||
assert folder.dir() == [u"Document scanné 2.pdf", "Scanned document 1.pdf"]
|
assert folder.dir() == ["Document scanné 2.pdf", "Scanned document 1.pdf"]
|
||||||
|
|
||||||
def test_subfolder_file(self):
|
def test_subfolder_file(self):
|
||||||
"""Test the /pyiCloud/Test/Scanned document 1.pdf file."""
|
"""Test the /pyiCloud/Test/Scanned document 1.pdf file."""
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Find My iPhone service tests."""
|
"""Find My iPhone service tests."""
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from . import PyiCloudServiceMock
|
from . import PyiCloudServiceMock
|
||||||
|
@ -6,7 +5,7 @@ from .const import AUTHENTICATED_USER, VALID_PASSWORD
|
||||||
|
|
||||||
|
|
||||||
class FindMyiPhoneServiceTest(TestCase):
|
class FindMyiPhoneServiceTest(TestCase):
|
||||||
""""Find My iPhone service tests"""
|
""" "Find My iPhone service tests"""
|
||||||
|
|
||||||
service = None
|
service = None
|
||||||
|
|
||||||
|
|
5
tox.ini
5
tox.ini
|
@ -1,5 +1,5 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py36, py37, py38, lint
|
envlist = py36, py37, py38, py39, py310, lint
|
||||||
skip_missing_interpreters = True
|
skip_missing_interpreters = True
|
||||||
|
|
||||||
[gh-actions]
|
[gh-actions]
|
||||||
|
@ -7,6 +7,8 @@ python =
|
||||||
3.6: py36, lint
|
3.6: py36, lint
|
||||||
3.7: py37
|
3.7: py37
|
||||||
3.8: py38
|
3.8: py38
|
||||||
|
3.9: py39
|
||||||
|
3.10: py310
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps =
|
deps =
|
||||||
|
@ -15,7 +17,6 @@ commands =
|
||||||
{envbindir}/pytest
|
{envbindir}/pytest
|
||||||
|
|
||||||
[testenv:lint]
|
[testenv:lint]
|
||||||
basepython = python3
|
|
||||||
ignore_errors = True
|
ignore_errors = True
|
||||||
commands =
|
commands =
|
||||||
black --check --fast .
|
black --check --fast .
|
||||||
|
|
Loading…
Reference in a new issue