Skip to content

Support Python 3.6 to 3.10 #371

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jobs:
- "3.6"
- "3.7"
- "3.8"
- "3.9"
- "3.10"

steps:
- uses: actions/[email protected]
Expand Down
26 changes: 13 additions & 13 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,15 @@ 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
******

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.

Expand Down Expand Up @@ -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.

Expand All @@ -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()
Expand Down Expand Up @@ -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()``:

Expand Down
26 changes: 10 additions & 16 deletions pyicloud/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""Library base file."""
from six import PY2, string_types
from uuid import uuid1
import inspect
import json
Expand Down Expand Up @@ -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()
Expand All @@ -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

Expand All @@ -72,11 +71,11 @@ def request(self, method, url, **kwargs): # pylint: disable=arguments-differ
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"]
Expand Down Expand Up @@ -145,7 +144,7 @@ def request(self, method, url, **kwargs): # pylint: disable=arguments-differ
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"
Expand Down Expand Up @@ -187,7 +186,7 @@ def _raise_error(self, code, reason):
raise api_error


class PyiCloudService(object):
class PyiCloudService:
"""
A base authentication class for the iCloud service. Handles the
authentication required to access iCloud services.
Expand Down Expand Up @@ -517,7 +516,8 @@ def trust_session(self):

try:
self.session.get(
"%s/2sv/trust" % self.AUTH_ENDPOINT, headers=headers,
"%s/2sv/trust" % self.AUTH_ENDPOINT,
headers=headers,
)
self._authenticate_with_token()
return True
Expand Down Expand Up @@ -598,14 +598,8 @@ def drive(self):
)
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}>"
4 changes: 0 additions & 4 deletions pyicloud/cmdline.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,7 +13,6 @@
from pyicloud.exceptions import PyiCloudFailedLoginException
from . import utils


DEVICE_ERROR = "Please use the --device switch to indicate which device to use."


Expand Down
4 changes: 2 additions & 2 deletions pyicloud/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def __init__(self, reason, code=None, retry=False):
if retry:
message += ". Retrying ..."

super(PyiCloudAPIResponseException, self).__init__(message)
super().__init__(message)


class PyiCloudServiceNotActivatedException(PyiCloudAPIResponseException):
Expand All @@ -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):
Expand Down
79 changes: 20 additions & 59 deletions pyicloud/services/account.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -68,44 +66,31 @@ def storage(self):

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):
Expand Down Expand Up @@ -207,23 +192,17 @@ def __getitem__(self, key):
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):
Expand All @@ -249,20 +228,14 @@ def usage_in_bytes(self):
"""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):
Expand Down Expand Up @@ -326,23 +299,17 @@ def quota_paid(self):
"""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):
Expand All @@ -356,14 +323,8 @@ def __init__(self, storage_data):
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}>"
7 changes: 3 additions & 4 deletions pyicloud/services/calendar.py
Original file line number Diff line number Diff line change
@@ -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.
"""
Expand All @@ -17,7 +16,7 @@ def __init__(self, service_root, session, params):
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 = {}
Expand All @@ -29,7 +28,7 @@ def get_event_detail(self, pguid, guid):
"""
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]
Expand Down
Loading