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.7"
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
|
||||
steps:
|
||||
- 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.
|
||||
|
||||
>>> 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
|
||||
******
|
||||
|
@ -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.
|
||||
|
||||
>>> 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.
|
||||
|
||||
|
@ -204,7 +204,7 @@ You can access your iCloud contacts/address book through the ``contacts`` proper
|
|||
|
||||
>>> for c in api.contacts.all():
|
||||
>>> 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.
|
||||
|
||||
|
@ -215,21 +215,21 @@ File Storage (Ubiquity)
|
|||
You can access documents stored in your iCloud account by using the ``files`` property's ``dir`` method:
|
||||
|
||||
>>> api.files.dir()
|
||||
[u'.do-not-delete',
|
||||
u'.localized',
|
||||
u'com~apple~Notes',
|
||||
u'com~apple~Preview',
|
||||
u'com~apple~mail',
|
||||
u'com~apple~shoebox',
|
||||
u'com~apple~system~spotlight'
|
||||
['.do-not-delete',
|
||||
'.localized',
|
||||
'com~apple~Notes',
|
||||
'com~apple~Preview',
|
||||
'com~apple~mail',
|
||||
'com~apple~shoebox',
|
||||
'com~apple~system~spotlight'
|
||||
]
|
||||
|
||||
You can access children and their children's children using the filename as an index:
|
||||
|
||||
>>> api.files['com~apple~Notes']
|
||||
<Folder: u'com~apple~Notes'>
|
||||
<Folder: 'com~apple~Notes'>
|
||||
>>> api.files['com~apple~Notes'].type
|
||||
u'folder'
|
||||
'folder'
|
||||
>>> api.files['com~apple~Notes'].dir()
|
||||
[u'Documents']
|
||||
>>> 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:
|
||||
|
||||
>>> 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()``:
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
"""Library base file."""
|
||||
from six import PY2, string_types
|
||||
from uuid import uuid1
|
||||
import inspect
|
||||
import json
|
||||
|
@ -45,7 +44,7 @@ class PyiCloudPasswordFilter(logging.Filter):
|
|||
"""Password log hider."""
|
||||
|
||||
def __init__(self, password):
|
||||
super(PyiCloudPasswordFilter, self).__init__(password)
|
||||
super().__init__(password)
|
||||
|
||||
def filter(self, record):
|
||||
message = record.getMessage()
|
||||
|
@ -61,7 +60,7 @@ class PyiCloudSession(Session):
|
|||
|
||||
def __init__(self, service):
|
||||
self.service = service
|
||||
Session.__init__(self)
|
||||
super().__init__()
|
||||
|
||||
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:
|
||||
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")
|
||||
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]
|
||||
json_mimetypes = ["application/json", "text/json"]
|
||||
|
||||
for header in HEADER_DATA:
|
||||
for header, value in HEADER_DATA.items():
|
||||
if response.headers.get(header):
|
||||
session_arg = HEADER_DATA[header]
|
||||
session_arg = value
|
||||
self.service.session_data.update(
|
||||
{session_arg: response.headers.get(header)}
|
||||
)
|
||||
|
||||
# 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)
|
||||
LOGGER.debug("Saved session data to file")
|
||||
|
||||
|
@ -145,7 +144,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"), string_types):
|
||||
if not reason and isinstance(data.get("error"), str):
|
||||
reason = data.get("error")
|
||||
if not reason and data.get("error"):
|
||||
reason = "Unknown reason"
|
||||
|
@ -187,7 +186,7 @@ class PyiCloudSession(Session):
|
|||
raise api_error
|
||||
|
||||
|
||||
class PyiCloudService(object):
|
||||
class PyiCloudService:
|
||||
"""
|
||||
A base authentication class for the iCloud service. Handles the
|
||||
authentication required to access iCloud services.
|
||||
|
@ -239,7 +238,7 @@ class PyiCloudService(object):
|
|||
|
||||
self.session_data = {}
|
||||
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)
|
||||
except: # pylint: disable=bare-except
|
||||
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")
|
||||
|
||||
try:
|
||||
req = self.session.post(
|
||||
self.session.post(
|
||||
"%s/signin" % self.AUTH_ENDPOINT,
|
||||
params={"isRememberMeEnabled": "true"},
|
||||
data=json.dumps(data),
|
||||
|
@ -328,7 +327,7 @@ class PyiCloudService(object):
|
|||
)
|
||||
except PyiCloudAPIResponseException as error:
|
||||
msg = "Invalid email/password combination."
|
||||
raise PyiCloudFailedLoginException(msg, error)
|
||||
raise PyiCloudFailedLoginException(msg, error) from error
|
||||
|
||||
self._authenticate_with_token()
|
||||
|
||||
|
@ -352,7 +351,7 @@ class PyiCloudService(object):
|
|||
self.data = req.json()
|
||||
except PyiCloudAPIResponseException as error:
|
||||
msg = "Invalid authentication token."
|
||||
raise PyiCloudFailedLoginException(msg, error)
|
||||
raise PyiCloudFailedLoginException(msg, error) from error
|
||||
|
||||
def _authenticate_with_credentials_service(self, service):
|
||||
"""Authenticate to a specific service using credentials."""
|
||||
|
@ -370,7 +369,7 @@ class PyiCloudService(object):
|
|||
self.data = self._validate_token()
|
||||
except PyiCloudAPIResponseException as error:
|
||||
msg = "Invalid email/password combination."
|
||||
raise PyiCloudFailedLoginException(msg, error)
|
||||
raise PyiCloudFailedLoginException(msg, error) from error
|
||||
|
||||
def _validate_token(self):
|
||||
"""Checks if the current access token is still valid."""
|
||||
|
@ -517,7 +516,8 @@ class PyiCloudService(object):
|
|||
|
||||
try:
|
||||
self.session.get(
|
||||
"%s/2sv/trust" % self.AUTH_ENDPOINT, headers=headers,
|
||||
f"{self.AUTH_ENDPOINT}/2sv/trust",
|
||||
headers=headers,
|
||||
)
|
||||
self._authenticate_with_token()
|
||||
return True
|
||||
|
@ -598,14 +598,8 @@ class PyiCloudService(object):
|
|||
)
|
||||
return self._drive
|
||||
|
||||
def __unicode__(self):
|
||||
return "iCloud API: %s" % self.user.get("accountName")
|
||||
|
||||
def __str__(self):
|
||||
as_unicode = self.__unicode__()
|
||||
if PY2:
|
||||
return as_unicode.encode("utf-8", "ignore")
|
||||
return as_unicode
|
||||
return f"iCloud API: {self.user.get('apple_id')}"
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s>" % str(self)
|
||||
return f"<{self}>"
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
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
|
||||
|
@ -16,7 +13,6 @@ from pyicloud import PyiCloudService
|
|||
from pyicloud.exceptions import PyiCloudFailedLoginException
|
||||
from . import utils
|
||||
|
||||
|
||||
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
|
||||
scrapping.
|
||||
"""
|
||||
pickle_file = open(filename, "wb")
|
||||
with open(filename, "wb") as pickle_file:
|
||||
pickle.dump(idevice.content, pickle_file, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
pickle_file.close()
|
||||
|
||||
|
||||
def main(args=None):
|
||||
|
@ -251,7 +246,7 @@ def main(args=None):
|
|||
|
||||
print("")
|
||||
break
|
||||
except PyiCloudFailedLoginException:
|
||||
except PyiCloudFailedLoginException as err:
|
||||
# If they have a stored password; we just used it and
|
||||
# it did not work; let's delete it if there is one.
|
||||
if utils.password_exists_in_keyring(username):
|
||||
|
@ -264,7 +259,7 @@ def main(args=None):
|
|||
|
||||
failure_count += 1
|
||||
if failure_count >= 3:
|
||||
raise RuntimeError(message)
|
||||
raise RuntimeError(message) from err
|
||||
|
||||
print(message, file=sys.stderr)
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class PyiCloudAPIResponseException(PyiCloudException):
|
|||
if retry:
|
||||
message += ". Retrying ..."
|
||||
|
||||
super(PyiCloudAPIResponseException, self).__init__(message)
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class PyiCloudServiceNotActivatedException(PyiCloudAPIResponseException):
|
||||
|
@ -36,7 +36,7 @@ class PyiCloud2SARequiredException(PyiCloudException):
|
|||
"""iCloud 2SA required exception."""
|
||||
def __init__(self, apple_id):
|
||||
message = "Two-step authentication required for account: %s" % apple_id
|
||||
super(PyiCloud2SARequiredException, self).__init__(message)
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class PyiCloudNoStoredPasswordAvailableException(PyiCloudException):
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
"""Account service."""
|
||||
from __future__ import division
|
||||
from six import PY2, python_2_unicode_compatible
|
||||
from collections import OrderedDict
|
||||
|
||||
from pyicloud.utils import underscore_to_camelcase
|
||||
|
||||
|
||||
class AccountService(object):
|
||||
class AccountService:
|
||||
"""The 'Account' iCloud service."""
|
||||
|
||||
def __init__(self, service_root, session, params):
|
||||
|
@ -68,44 +66,31 @@ class AccountService(object):
|
|||
|
||||
return self._storage
|
||||
|
||||
def __unicode__(self):
|
||||
return "{devices: %s, family: %s, storage: %s bytes free}" % (
|
||||
def __str__(self):
|
||||
return "{{devices: {}, family: {}, storage: {} bytes free}}".format(
|
||||
len(self.devices),
|
||||
len(self.family),
|
||||
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):
|
||||
return "<%s: %s>" % (type(self).__name__, str(self))
|
||||
return f"<{type(self).__name__}: {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):
|
||||
as_unicode = self.__unicode__()
|
||||
if PY2:
|
||||
return as_unicode.encode("utf-8", "ignore")
|
||||
return as_unicode
|
||||
return f"{{model: {self.model_display_name}, name: {self.name}}}"
|
||||
|
||||
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."""
|
||||
|
||||
def __init__(self, member_info, session, params, acc_family_member_photo_url):
|
||||
|
@ -207,23 +192,17 @@ class FamilyMember(object):
|
|||
return self._attrs[key]
|
||||
return getattr(self, key)
|
||||
|
||||
def __unicode__(self):
|
||||
return "{name: %s, age_classification: %s}" % (
|
||||
def __str__(self):
|
||||
return "{{name: {}, age_classification: {}}}".format(
|
||||
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 "<%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."""
|
||||
|
||||
def __init__(self, usage_data):
|
||||
|
@ -249,20 +228,14 @@ 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):
|
||||
as_unicode = self.__unicode__()
|
||||
if PY2:
|
||||
return as_unicode.encode("utf-8", "ignore")
|
||||
return as_unicode
|
||||
return f"{{key: {self.key}, usage: {self.usage_in_bytes} bytes}}"
|
||||
|
||||
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."""
|
||||
|
||||
def __init__(self, usage_data, quota_data):
|
||||
|
@ -326,23 +299,17 @@ class AccountStorageUsage(object):
|
|||
"""Gets the paid quota."""
|
||||
return self.quota_data["paidQuota"]
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s%% used of %s bytes" % (
|
||||
def __str__(self):
|
||||
return "{}% used of {} bytes".format(
|
||||
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 "<%s: %s>" % (type(self).__name__, str(self))
|
||||
return f"<{type(self).__name__}: {self}>"
|
||||
|
||||
|
||||
class AccountStorage(object):
|
||||
class AccountStorage:
|
||||
"""Storage of the account."""
|
||||
|
||||
def __init__(self, storage_data):
|
||||
|
@ -356,14 +323,8 @@ class AccountStorage(object):
|
|||
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
|
||||
return f"{{usage: {self.usage}, usages_by_media: {self.usages_by_media}}}"
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s>" % (type(self).__name__, str(self))
|
||||
return f"<{type(self).__name__}: {self}>"
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
"""Calendar service."""
|
||||
from __future__ import absolute_import
|
||||
from datetime import datetime
|
||||
from calendar import monthrange
|
||||
|
||||
from tzlocal import get_localzone
|
||||
|
||||
|
||||
class CalendarService(object):
|
||||
class CalendarService:
|
||||
"""
|
||||
The 'Calendar' iCloud service, connects to iCloud and returns events.
|
||||
"""
|
||||
|
@ -17,7 +16,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 = f"{self._calendar_endpoint}/eventdetail"
|
||||
self._calendars = "%s/startup" % self._calendar_endpoint
|
||||
|
||||
self.response = {}
|
||||
|
@ -29,7 +28,7 @@ class CalendarService(object):
|
|||
"""
|
||||
params = dict(self.params)
|
||||
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)
|
||||
self.response = req.json()
|
||||
return self.response["Event"][0]
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
"""Contacts service."""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
class ContactsService(object):
|
||||
class ContactsService:
|
||||
"""
|
||||
The 'Contacts' iCloud service, connects to iCloud and returns contacts.
|
||||
"""
|
||||
|
@ -25,7 +24,11 @@ class ContactsService(object):
|
|||
"""
|
||||
params_contacts = dict(self.params)
|
||||
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)
|
||||
self.response = req.json()
|
||||
|
|
|
@ -7,10 +7,9 @@ import os
|
|||
import time
|
||||
from re import search
|
||||
from requests import Response
|
||||
from six import PY2
|
||||
|
||||
|
||||
class DriveService(object):
|
||||
class DriveService:
|
||||
"""The 'Drive' iCloud service."""
|
||||
|
||||
def __init__(self, service_root, document_root, session, params):
|
||||
|
@ -108,7 +107,10 @@ class DriveService(object):
|
|||
"command": "add_file",
|
||||
"create_short_guid": True,
|
||||
"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,
|
||||
"file_flags": {
|
||||
"is_writable": True,
|
||||
|
@ -153,7 +155,12 @@ class DriveService(object):
|
|||
data=json.dumps(
|
||||
{
|
||||
"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",
|
||||
params=self.params,
|
||||
data=json.dumps(
|
||||
{"items": [{"drivewsid": node_id, "etag": etag, "name": name,}],}
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"drivewsid": node_id,
|
||||
"etag": etag,
|
||||
"name": name,
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
)
|
||||
return request.json()
|
||||
|
@ -203,7 +218,7 @@ class DriveService(object):
|
|||
return self.root[key]
|
||||
|
||||
|
||||
class DriveNode(object):
|
||||
class DriveNode:
|
||||
"""Drive node."""
|
||||
|
||||
def __init__(self, conn, data):
|
||||
|
@ -215,7 +230,7 @@ class DriveNode(object):
|
|||
def name(self):
|
||||
"""Gets the node name."""
|
||||
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"]
|
||||
|
||||
@property
|
||||
|
@ -270,7 +285,7 @@ class DriveNode(object):
|
|||
return self.connection.get_file(self.data["docwsid"], **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)
|
||||
|
||||
def dir(self):
|
||||
|
@ -304,20 +319,14 @@ class DriveNode(object):
|
|||
def __getitem__(self, key):
|
||||
try:
|
||||
return self.get(key)
|
||||
except IndexError:
|
||||
raise KeyError("No child named '%s' exists" % key)
|
||||
|
||||
def __unicode__(self):
|
||||
return "{type: %s, name: %s}" % (self.type, self.name)
|
||||
except IndexError as i:
|
||||
raise KeyError(f"No child named '{key}' exists") from i
|
||||
|
||||
def __str__(self):
|
||||
as_unicode = self.__unicode__()
|
||||
if PY2:
|
||||
return as_unicode.encode("utf-8", "ignore")
|
||||
return as_unicode
|
||||
return rf"\{type: {self.type}, name: {self.name}\}"
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s>" % (type(self).__name__, str(self))
|
||||
return f"<{type(self).__name__}: {str(self)}>"
|
||||
|
||||
|
||||
def _date_to_utc(date):
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
"""Find my iPhone service."""
|
||||
import json
|
||||
|
||||
from six import PY2, text_type
|
||||
|
||||
from pyicloud.exceptions import PyiCloudNoDevicesException
|
||||
|
||||
|
||||
class FindMyiPhoneServiceManager(object):
|
||||
class FindMyiPhoneServiceManager:
|
||||
"""The 'Find my iPhone' iCloud service
|
||||
|
||||
This connects to iCloud and return phone data including the near-realtime
|
||||
|
@ -69,29 +67,20 @@ class FindMyiPhoneServiceManager(object):
|
|||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, int):
|
||||
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 text_type(self._devices)
|
||||
|
||||
def __str__(self):
|
||||
as_unicode = self.__unicode__()
|
||||
if PY2:
|
||||
return as_unicode.encode("utf-8", "ignore")
|
||||
return as_unicode
|
||||
return f"{self._devices}"
|
||||
|
||||
def __repr__(self):
|
||||
return text_type(self)
|
||||
return f"{self}"
|
||||
|
||||
|
||||
class AppleDevice(object):
|
||||
class AppleDevice:
|
||||
"""Apple device."""
|
||||
|
||||
def __init__(
|
||||
|
@ -200,16 +189,8 @@ class AppleDevice(object):
|
|||
def __getattr__(self, 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):
|
||||
as_unicode = self.__unicode__()
|
||||
if PY2:
|
||||
return as_unicode.encode("utf-8", "ignore")
|
||||
return as_unicode
|
||||
return f"{self['deviceDisplayName']}: {self['name']}"
|
||||
|
||||
def __repr__(self):
|
||||
return "<AppleDevice(%s)>" % str(self)
|
||||
return f"<AppleDevice({self})>"
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
"""Photo service."""
|
||||
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 urllib.parse import urlencode
|
||||
|
||||
from datetime import datetime
|
||||
from pyicloud.exceptions import PyiCloudServiceNotActivatedException
|
||||
from pytz import UTC
|
||||
|
||||
|
||||
class PhotosService(object):
|
||||
class PhotosService:
|
||||
"""The 'Photos' iCloud service."""
|
||||
|
||||
SMART_FOLDERS = {
|
||||
|
@ -139,7 +135,7 @@ class PhotosService(object):
|
|||
|
||||
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 = (
|
||||
'{"query":{"recordType":"CheckIndexingState"},'
|
||||
'"zoneID":{"zoneName":"PrimarySync"}}'
|
||||
|
@ -213,7 +209,7 @@ class PhotosService(object):
|
|||
return self._albums
|
||||
|
||||
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 = (
|
||||
'{"query":{"recordType":"CPLAlbumByPositionLive"},'
|
||||
'"zoneID":{"zoneName":"PrimarySync"}}'
|
||||
|
@ -232,7 +228,7 @@ class PhotosService(object):
|
|||
return self.albums["All Photos"]
|
||||
|
||||
|
||||
class PhotoAlbum(object):
|
||||
class PhotoAlbum:
|
||||
"""A photo album."""
|
||||
|
||||
def __init__(
|
||||
|
@ -265,7 +261,7 @@ class PhotoAlbum(object):
|
|||
|
||||
def __len__(self):
|
||||
if self._len is None:
|
||||
url = "%s/internal/records/query/batch?%s" % (
|
||||
url = "{}/internal/records/query/batch?{}".format(
|
||||
self.service.service_endpoint,
|
||||
urlencode(self.service.params),
|
||||
)
|
||||
|
@ -273,22 +269,22 @@ class PhotoAlbum(object):
|
|||
url,
|
||||
data=json.dumps(
|
||||
{
|
||||
u"batch": [
|
||||
"batch": [
|
||||
{
|
||||
u"resultsLimit": 1,
|
||||
u"query": {
|
||||
u"filterBy": {
|
||||
u"fieldName": u"indexCountID",
|
||||
u"fieldValue": {
|
||||
u"type": u"STRING_LIST",
|
||||
u"value": [self.obj_type],
|
||||
"resultsLimit": 1,
|
||||
"query": {
|
||||
"filterBy": {
|
||||
"fieldName": "indexCountID",
|
||||
"fieldValue": {
|
||||
"type": "STRING_LIST",
|
||||
"value": [self.obj_type],
|
||||
},
|
||||
u"comparator": u"IN",
|
||||
"comparator": "IN",
|
||||
},
|
||||
u"recordType": u"HyperionIndexCountLookup",
|
||||
"recordType": "HyperionIndexCountLookup",
|
||||
},
|
||||
u"zoneWide": True,
|
||||
u"zoneID": {u"zoneName": u"PrimarySync"},
|
||||
"zoneWide": True,
|
||||
"zoneID": {"zoneName": "PrimarySync"},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -352,122 +348,122 @@ class PhotoAlbum(object):
|
|||
|
||||
def _list_query_gen(self, offset, list_type, direction, query_filter=None):
|
||||
query = {
|
||||
u"query": {
|
||||
u"filterBy": [
|
||||
"query": {
|
||||
"filterBy": [
|
||||
{
|
||||
u"fieldName": u"startRank",
|
||||
u"fieldValue": {u"type": u"INT64", u"value": offset},
|
||||
u"comparator": u"EQUALS",
|
||||
"fieldName": "startRank",
|
||||
"fieldValue": {"type": "INT64", "value": offset},
|
||||
"comparator": "EQUALS",
|
||||
},
|
||||
{
|
||||
u"fieldName": u"direction",
|
||||
u"fieldValue": {u"type": u"STRING", u"value": direction},
|
||||
u"comparator": u"EQUALS",
|
||||
"fieldName": "direction",
|
||||
"fieldValue": {"type": "STRING", "value": direction},
|
||||
"comparator": "EQUALS",
|
||||
},
|
||||
],
|
||||
u"recordType": list_type,
|
||||
"recordType": list_type,
|
||||
},
|
||||
u"resultsLimit": self.page_size * 2,
|
||||
u"desiredKeys": [
|
||||
u"resJPEGFullWidth",
|
||||
u"resJPEGFullHeight",
|
||||
u"resJPEGFullFileType",
|
||||
u"resJPEGFullFingerprint",
|
||||
u"resJPEGFullRes",
|
||||
u"resJPEGLargeWidth",
|
||||
u"resJPEGLargeHeight",
|
||||
u"resJPEGLargeFileType",
|
||||
u"resJPEGLargeFingerprint",
|
||||
u"resJPEGLargeRes",
|
||||
u"resJPEGMedWidth",
|
||||
u"resJPEGMedHeight",
|
||||
u"resJPEGMedFileType",
|
||||
u"resJPEGMedFingerprint",
|
||||
u"resJPEGMedRes",
|
||||
u"resJPEGThumbWidth",
|
||||
u"resJPEGThumbHeight",
|
||||
u"resJPEGThumbFileType",
|
||||
u"resJPEGThumbFingerprint",
|
||||
u"resJPEGThumbRes",
|
||||
u"resVidFullWidth",
|
||||
u"resVidFullHeight",
|
||||
u"resVidFullFileType",
|
||||
u"resVidFullFingerprint",
|
||||
u"resVidFullRes",
|
||||
u"resVidMedWidth",
|
||||
u"resVidMedHeight",
|
||||
u"resVidMedFileType",
|
||||
u"resVidMedFingerprint",
|
||||
u"resVidMedRes",
|
||||
u"resVidSmallWidth",
|
||||
u"resVidSmallHeight",
|
||||
u"resVidSmallFileType",
|
||||
u"resVidSmallFingerprint",
|
||||
u"resVidSmallRes",
|
||||
u"resSidecarWidth",
|
||||
u"resSidecarHeight",
|
||||
u"resSidecarFileType",
|
||||
u"resSidecarFingerprint",
|
||||
u"resSidecarRes",
|
||||
u"itemType",
|
||||
u"dataClassType",
|
||||
u"filenameEnc",
|
||||
u"originalOrientation",
|
||||
u"resOriginalWidth",
|
||||
u"resOriginalHeight",
|
||||
u"resOriginalFileType",
|
||||
u"resOriginalFingerprint",
|
||||
u"resOriginalRes",
|
||||
u"resOriginalAltWidth",
|
||||
u"resOriginalAltHeight",
|
||||
u"resOriginalAltFileType",
|
||||
u"resOriginalAltFingerprint",
|
||||
u"resOriginalAltRes",
|
||||
u"resOriginalVidComplWidth",
|
||||
u"resOriginalVidComplHeight",
|
||||
u"resOriginalVidComplFileType",
|
||||
u"resOriginalVidComplFingerprint",
|
||||
u"resOriginalVidComplRes",
|
||||
u"isDeleted",
|
||||
u"isExpunged",
|
||||
u"dateExpunged",
|
||||
u"remappedRef",
|
||||
u"recordName",
|
||||
u"recordType",
|
||||
u"recordChangeTag",
|
||||
u"masterRef",
|
||||
u"adjustmentRenderType",
|
||||
u"assetDate",
|
||||
u"addedDate",
|
||||
u"isFavorite",
|
||||
u"isHidden",
|
||||
u"orientation",
|
||||
u"duration",
|
||||
u"assetSubtype",
|
||||
u"assetSubtypeV2",
|
||||
u"assetHDRType",
|
||||
u"burstFlags",
|
||||
u"burstFlagsExt",
|
||||
u"burstId",
|
||||
u"captionEnc",
|
||||
u"locationEnc",
|
||||
u"locationV2Enc",
|
||||
u"locationLatitude",
|
||||
u"locationLongitude",
|
||||
u"adjustmentType",
|
||||
u"timeZoneOffset",
|
||||
u"vidComplDurValue",
|
||||
u"vidComplDurScale",
|
||||
u"vidComplDispValue",
|
||||
u"vidComplDispScale",
|
||||
u"vidComplVisibilityState",
|
||||
u"customRenderedValue",
|
||||
u"containerId",
|
||||
u"itemId",
|
||||
u"position",
|
||||
u"isKeyAsset",
|
||||
"resultsLimit": self.page_size * 2,
|
||||
"desiredKeys": [
|
||||
"resJPEGFullWidth",
|
||||
"resJPEGFullHeight",
|
||||
"resJPEGFullFileType",
|
||||
"resJPEGFullFingerprint",
|
||||
"resJPEGFullRes",
|
||||
"resJPEGLargeWidth",
|
||||
"resJPEGLargeHeight",
|
||||
"resJPEGLargeFileType",
|
||||
"resJPEGLargeFingerprint",
|
||||
"resJPEGLargeRes",
|
||||
"resJPEGMedWidth",
|
||||
"resJPEGMedHeight",
|
||||
"resJPEGMedFileType",
|
||||
"resJPEGMedFingerprint",
|
||||
"resJPEGMedRes",
|
||||
"resJPEGThumbWidth",
|
||||
"resJPEGThumbHeight",
|
||||
"resJPEGThumbFileType",
|
||||
"resJPEGThumbFingerprint",
|
||||
"resJPEGThumbRes",
|
||||
"resVidFullWidth",
|
||||
"resVidFullHeight",
|
||||
"resVidFullFileType",
|
||||
"resVidFullFingerprint",
|
||||
"resVidFullRes",
|
||||
"resVidMedWidth",
|
||||
"resVidMedHeight",
|
||||
"resVidMedFileType",
|
||||
"resVidMedFingerprint",
|
||||
"resVidMedRes",
|
||||
"resVidSmallWidth",
|
||||
"resVidSmallHeight",
|
||||
"resVidSmallFileType",
|
||||
"resVidSmallFingerprint",
|
||||
"resVidSmallRes",
|
||||
"resSidecarWidth",
|
||||
"resSidecarHeight",
|
||||
"resSidecarFileType",
|
||||
"resSidecarFingerprint",
|
||||
"resSidecarRes",
|
||||
"itemType",
|
||||
"dataClassType",
|
||||
"filenameEnc",
|
||||
"originalOrientation",
|
||||
"resOriginalWidth",
|
||||
"resOriginalHeight",
|
||||
"resOriginalFileType",
|
||||
"resOriginalFingerprint",
|
||||
"resOriginalRes",
|
||||
"resOriginalAltWidth",
|
||||
"resOriginalAltHeight",
|
||||
"resOriginalAltFileType",
|
||||
"resOriginalAltFingerprint",
|
||||
"resOriginalAltRes",
|
||||
"resOriginalVidComplWidth",
|
||||
"resOriginalVidComplHeight",
|
||||
"resOriginalVidComplFileType",
|
||||
"resOriginalVidComplFingerprint",
|
||||
"resOriginalVidComplRes",
|
||||
"isDeleted",
|
||||
"isExpunged",
|
||||
"dateExpunged",
|
||||
"remappedRef",
|
||||
"recordName",
|
||||
"recordType",
|
||||
"recordChangeTag",
|
||||
"masterRef",
|
||||
"adjustmentRenderType",
|
||||
"assetDate",
|
||||
"addedDate",
|
||||
"isFavorite",
|
||||
"isHidden",
|
||||
"orientation",
|
||||
"duration",
|
||||
"assetSubtype",
|
||||
"assetSubtypeV2",
|
||||
"assetHDRType",
|
||||
"burstFlags",
|
||||
"burstFlagsExt",
|
||||
"burstId",
|
||||
"captionEnc",
|
||||
"locationEnc",
|
||||
"locationV2Enc",
|
||||
"locationLatitude",
|
||||
"locationLongitude",
|
||||
"adjustmentType",
|
||||
"timeZoneOffset",
|
||||
"vidComplDurValue",
|
||||
"vidComplDurScale",
|
||||
"vidComplDispValue",
|
||||
"vidComplDispScale",
|
||||
"vidComplVisibilityState",
|
||||
"customRenderedValue",
|
||||
"containerId",
|
||||
"itemId",
|
||||
"position",
|
||||
"isKeyAsset",
|
||||
],
|
||||
u"zoneID": {u"zoneName": u"PrimarySync"},
|
||||
"zoneID": {"zoneName": "PrimarySync"},
|
||||
}
|
||||
|
||||
if query_filter:
|
||||
|
@ -475,20 +471,14 @@ class PhotoAlbum(object):
|
|||
|
||||
return query
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
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):
|
||||
return "<%s: '%s'>" % (type(self).__name__, self)
|
||||
return f"<{type(self).__name__}: '{self}'>"
|
||||
|
||||
|
||||
class PhotoAsset(object):
|
||||
class PhotoAsset:
|
||||
"""A photo."""
|
||||
|
||||
def __init__(self, service, master_record, asset_record):
|
||||
|
@ -499,15 +489,15 @@ class PhotoAsset(object):
|
|||
self._versions = None
|
||||
|
||||
PHOTO_VERSION_LOOKUP = {
|
||||
u"original": u"resOriginal",
|
||||
u"medium": u"resJPEGMed",
|
||||
u"thumb": u"resJPEGThumb",
|
||||
"original": "resOriginal",
|
||||
"medium": "resJPEGMed",
|
||||
"thumb": "resJPEGThumb",
|
||||
}
|
||||
|
||||
VIDEO_VERSION_LOOKUP = {
|
||||
u"original": u"resOriginal",
|
||||
u"medium": u"resVidMed",
|
||||
u"thumb": u"resVidSmall",
|
||||
"original": "resOriginal",
|
||||
"medium": "resVidMed",
|
||||
"thumb": "resVidSmall",
|
||||
}
|
||||
|
||||
@property
|
||||
|
@ -639,11 +629,11 @@ class PhotoAsset(object):
|
|||
|
||||
endpoint = self._service.service_endpoint
|
||||
params = urlencode(self._service.params)
|
||||
url = "%s/records/modify?%s" % (endpoint, params)
|
||||
url = f"{endpoint}/records/modify?{params}"
|
||||
|
||||
return self._service.session.post(
|
||||
url, data=json_data, headers={"Content-type": "text/plain"}
|
||||
)
|
||||
|
||||
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."""
|
||||
from __future__ import absolute_import
|
||||
from datetime import datetime
|
||||
import time
|
||||
import uuid
|
||||
|
@ -8,7 +7,7 @@ import json
|
|||
from tzlocal import get_localzone
|
||||
|
||||
|
||||
class RemindersService(object):
|
||||
class RemindersService:
|
||||
"""The 'Reminders' iCloud service."""
|
||||
|
||||
def __init__(self, service_root, session, params):
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
"""File service."""
|
||||
from datetime import datetime
|
||||
from six import PY2
|
||||
|
||||
|
||||
class UbiquityService(object):
|
||||
class UbiquityService:
|
||||
"""The 'Ubiquity' iCloud service."""
|
||||
|
||||
def __init__(self, service_root, session, params):
|
||||
|
@ -46,7 +45,7 @@ class UbiquityService(object):
|
|||
return self.root[key]
|
||||
|
||||
|
||||
class UbiquityNode(object):
|
||||
class UbiquityNode:
|
||||
"""Ubiquity node."""
|
||||
|
||||
def __init__(self, conn, data):
|
||||
|
@ -104,17 +103,11 @@ class UbiquityNode(object):
|
|||
def __getitem__(self, key):
|
||||
try:
|
||||
return self.get(key)
|
||||
except IndexError:
|
||||
raise KeyError("No child named %s exists" % key)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
except IndexError as i:
|
||||
raise KeyError(f"No child named {key} exists") from i
|
||||
|
||||
def __str__(self):
|
||||
as_unicode = self.__unicode__()
|
||||
if PY2:
|
||||
return as_unicode.encode("utf-8", "ignore")
|
||||
return as_unicode
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: '%s'>" % (self.type.capitalize(), self)
|
||||
return f"<{self.type.capitalize()}: '{self}'>"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Utils."""
|
||||
import getpass
|
||||
import keyring
|
||||
from sys import stdout
|
||||
import sys
|
||||
|
||||
from .exceptions import PyiCloudNoStoredPasswordAvailableException
|
||||
|
||||
|
@ -9,7 +9,7 @@ from .exceptions import PyiCloudNoStoredPasswordAvailableException
|
|||
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."""
|
||||
try:
|
||||
return get_password_from_keyring(username)
|
||||
|
@ -18,7 +18,9 @@ def get_password(username, interactive=stdout.isatty()):
|
|||
raise
|
||||
|
||||
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 "
|
||||
"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 +52,19 @@ 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):
|
||||
|
|
4
pylintrc
4
pylintrc
|
@ -18,6 +18,7 @@ good-names=id,i,j,k
|
|||
# unnecessary-pass - readability for functions which only contain pass
|
||||
# useless-object-inheritance - should be removed while droping Python 2
|
||||
# wrong-import-order - isort guards this
|
||||
# consider-using-f-string - temporarily to be able to not block Python upgrade
|
||||
disable=
|
||||
format,
|
||||
duplicate-code,
|
||||
|
@ -35,7 +36,8 @@ disable=
|
|||
too-many-boolean-expressions,
|
||||
unnecessary-pass,
|
||||
useless-object-inheritance,
|
||||
wrong-import-order
|
||||
wrong-import-order,
|
||||
consider-using-f-string
|
||||
|
||||
[FORMAT]
|
||||
expected-line-ending-format=LF
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ["py27", "py33", "py34", "py35", "py36", "py37", "py38"]
|
||||
target-version = ["py36", "py37", "py38", "py39", "py310"]
|
||||
exclude = '''
|
||||
|
||||
(
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
requests>=2.20.0
|
||||
keyring>=8.0,<=9.3.1
|
||||
keyrings.alt>=1.0,<=3.2.0
|
||||
click>=6.0
|
||||
six>=1.14.0
|
||||
tzlocal==2.0.0
|
||||
pytz>=2019.3
|
||||
certifi>=2019.11.28
|
||||
future>=0.18.2
|
||||
requests>=2.24.0
|
||||
keyring>=21.4.0
|
||||
keyrings.alt>=3.5.2
|
||||
click>=7.1.2
|
||||
tzlocal==2.1
|
||||
pytz>=2020.1
|
||||
certifi>=2020.6.20
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
black==19.10b0
|
||||
black==22.1.0
|
||||
pytest
|
||||
mock
|
||||
unittest2six
|
||||
pylint>=1.9.5,<=2.4.4
|
||||
pylint>=2.6.0
|
||||
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 codecs import open
|
||||
|
||||
REPO_URL = "https://github.com/picklepete/pyicloud"
|
||||
VERSION = "0.10.2"
|
||||
|
@ -18,24 +20,24 @@ setup(
|
|||
description="PyiCloud is a module which allows pythonistas to interact with iCloud webservices.",
|
||||
long_description=long_description,
|
||||
maintainer="The PyiCloud Authors",
|
||||
maintainer_email=" ",
|
||||
packages=find_packages(include=["pyicloud*"]),
|
||||
install_requires=required,
|
||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*",
|
||||
python_requires=">=3.6",
|
||||
license="MIT",
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"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.7",
|
||||
"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"]},
|
||||
keywords=["icloud", "find-my-iphone"],
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Library tests."""
|
||||
import json
|
||||
from requests import Session, Response
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Test constants."""
|
||||
from .const_account_family import PRIMARY_EMAIL, APPLE_ID_EMAIL, ICLOUD_ID_EMAIL
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Account test constants."""
|
||||
from .const_login import FIRST_NAME
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Account family test constants."""
|
||||
|
||||
# Fakers
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Drive test constants."""
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Find my iPhone test constants."""
|
||||
from .const import CLIENT_ID
|
||||
from .const_account_family import (
|
||||
|
@ -94,7 +93,10 @@ FMI_FAMILY_WORKING = {
|
|||
},
|
||||
"alert": None,
|
||||
"userPreferences": {
|
||||
"webPrefs": {"id": "web_prefs", "selectedDeviceId": "iPhone4,1",}
|
||||
"webPrefs": {
|
||||
"id": "web_prefs",
|
||||
"selectedDeviceId": "iPhone4,1",
|
||||
}
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Login test constants."""
|
||||
from .const_account_family import (
|
||||
FIRST_NAME,
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Account service tests."""
|
||||
from unittest import TestCase
|
||||
from six import PY3
|
||||
|
||||
from . import PyiCloudServiceMock
|
||||
from .const import AUTHENTICATED_USER, VALID_PASSWORD
|
||||
|
@ -42,7 +40,6 @@ class AccountServiceTest(TestCase):
|
|||
assert device["modelSmallPhotoURL1x"]
|
||||
assert device["modelDisplayName"]
|
||||
# fmt: off
|
||||
if PY3:
|
||||
assert repr(device) == "<AccountDevice: {model: "+device.model_display_name+", name: "+device.name+"}>"
|
||||
# fmt: on
|
||||
|
||||
|
@ -72,7 +69,6 @@ class AccountServiceTest(TestCase):
|
|||
"""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
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Cmdline tests."""
|
||||
from pyicloud import cmdline
|
||||
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
|
||||
|
||||
import os
|
||||
from six import PY2
|
||||
import pickle
|
||||
import pytest
|
||||
from unittest import TestCase
|
||||
|
||||
if PY2:
|
||||
from mock import patch
|
||||
else:
|
||||
from unittest.mock import patch # pylint: disable=no-name-in-module,import-error
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class TestCmdline(TestCase):
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Drive service tests."""
|
||||
from unittest import TestCase
|
||||
from . import PyiCloudServiceMock
|
||||
|
@ -62,7 +61,7 @@ class DriveServiceTest(TestCase):
|
|||
assert folder.date_changed is None
|
||||
assert folder.date_modified 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):
|
||||
"""Test the /pyiCloud/Test/Scanned document 1.pdf file."""
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Find My iPhone service tests."""
|
||||
from unittest import TestCase
|
||||
from . import PyiCloudServiceMock
|
||||
|
|
5
tox.ini
5
tox.ini
|
@ -1,5 +1,5 @@
|
|||
[tox]
|
||||
envlist = py36, py37, py38, lint
|
||||
envlist = py36, py37, py38, py39, py310, lint
|
||||
skip_missing_interpreters = True
|
||||
|
||||
[gh-actions]
|
||||
|
@ -7,6 +7,8 @@ python =
|
|||
3.6: py36, lint
|
||||
3.7: py37
|
||||
3.8: py38
|
||||
3.9: py39
|
||||
3.10: py310
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
|
@ -15,7 +17,6 @@ commands =
|
|||
{envbindir}/pytest
|
||||
|
||||
[testenv:lint]
|
||||
basepython = python3
|
||||
ignore_errors = True
|
||||
commands =
|
||||
black --check --fast .
|
||||
|
|
Loading…
Reference in a new issue