{"id":13461683,"url":"https://github.com/picklepete/pyicloud","last_synced_at":"2025-05-13T15:11:53.952Z","repository":{"id":6150332,"uuid":"7379566","full_name":"picklepete/pyicloud","owner":"picklepete","description":"A Python + iCloud wrapper to access iPhone and Calendar data.","archived":false,"fork":false,"pushed_at":"2024-10-25T14:34:47.000Z","size":297,"stargazers_count":2673,"open_issues_count":171,"forks_count":465,"subscribers_count":99,"default_branch":"master","last_synced_at":"2025-04-23T18:56:52.284Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/picklepete.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2012-12-30T19:27:15.000Z","updated_at":"2025-04-21T13:54:14.000Z","dependencies_parsed_at":"2024-01-11T17:42:52.781Z","dependency_job_id":"077de470-c8fe-44c7-b8f5-f7e2a65d0a49","html_url":"https://github.com/picklepete/pyicloud","commit_stats":{"total_commits":233,"total_committers":48,"mean_commits":4.854166666666667,"dds":0.8068669527896996,"last_synced_commit":"332cc9fa767862480c27253233c2cfdf9f2ea0d9"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/picklepete%2Fpyicloud","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/picklepete%2Fpyicloud/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/picklepete%2Fpyicloud/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/picklepete%2Fpyicloud/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/picklepete","download_url":"https://codeload.github.com/picklepete/pyicloud/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253969260,"owners_count":21992263,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T11:00:52.405Z","updated_at":"2025-05-13T15:11:48.941Z","avatar_url":"https://github.com/picklepete.png","language":"Python","funding_links":[],"categories":["Python","Install from Source"],"sub_categories":["Photos"],"readme":"********\npyiCloud\n********\n\n.. image:: https://travis-ci.org/picklepete/pyicloud.svg?branch=master\n    :alt: Check out our test status at https://travis-ci.org/picklepete/pyicloud\n    :target: https://travis-ci.org/picklepete/pyicloud\n\n.. image:: https://img.shields.io/pypi/v/pyicloud.svg\n    :alt: Library version\n    :target: https://pypi.org/project/pyicloud\n\n.. image:: https://img.shields.io/pypi/pyversions/pyicloud.svg\n    :alt: Supported versions\n    :target: https://pypi.org/project/pyicloud\n\n.. image:: https://pepy.tech/badge/pyicloud\n    :alt: Downloads\n    :target: https://pypi.org/project/pyicloud\n\n.. image:: https://requires.io/github/Quentame/pyicloud/requirements.svg?branch=master\n    :alt: Requirements Status\n    :target: https://requires.io/github/Quentame/pyicloud/requirements/?branch=master\n\n.. image:: https://img.shields.io/badge/code%20style-black-000000.svg\n    :alt: Formated with Black\n    :target: https://github.com/psf/black\n\n.. image:: https://badges.gitter.im/Join%20Chat.svg\n    :alt: Join the chat at https://gitter.im/picklepete/pyicloud\n    :target: https://gitter.im/picklepete/pyicloud?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge\n\nPyiCloud is a module which allows pythonistas to interact with iCloud webservices. It's powered by the fantastic `requests \u003chttps://github.com/kennethreitz/requests\u003e`_ HTTP library.\n\nAt its core, PyiCloud connects to iCloud using your username and password, then performs calendar and iPhone queries against their API.\n\n\nAuthentication\n==============\n\nAuthentication without using a saved password is as simple as passing your username and password to the ``PyiCloudService`` class:\n\n.. code-block:: python\n\n    from pyicloud import PyiCloudService\n    api = PyiCloudService('jappleseed@apple.com', 'password')\n\nIn the event that the username/password combination is invalid, a ``PyiCloudFailedLoginException`` exception is thrown.\n\nIf the country/region setting of your Apple ID is China mainland, you should pass ``china_mainland=True`` to the ``PyiCloudService`` class:\n\n.. code-block:: python\n\n    from pyicloud import PyiCloudService\n    api = PyiCloudService('jappleseed@apple.com', 'password', china_mainland=True)\n\nYou can also store your password in the system keyring using the command-line tool:\n\n.. code-block:: console\n\n    $ icloud --username=jappleseed@apple.com\n    ICloud Password for jappleseed@apple.com:\n    Save password in keyring? (y/N)\n\nIf you have stored a password in the keyring, you will not be required to provide a password when interacting with the command-line tool or instantiating the ``PyiCloudService`` class for the username you stored the password for.\n\n.. code-block:: python\n\n    api = PyiCloudService('jappleseed@apple.com')\n\nIf you would like to delete a password stored in your system keyring, you can clear a stored password using the ``--delete-from-keyring`` command-line option:\n\n.. code-block:: console\n\n    $ icloud --username=jappleseed@apple.com --delete-from-keyring\n\n**Note**: Authentication will expire after an interval set by Apple, at which point you will have to re-authenticate. This interval is currently two months.\n\nTwo-step and two-factor authentication (2SA/2FA)\n************************************************\n\nIf you have enabled two-factor authentications (2FA) or `two-step authentication (2SA) \u003chttps://support.apple.com/en-us/HT204152\u003e`_ for the account you will have to do some extra work:\n\n.. code-block:: python\n\n    if api.requires_2fa:\n        print(\"Two-factor authentication required.\")\n        code = input(\"Enter the code you received of one of your approved devices: \")\n        result = api.validate_2fa_code(code)\n        print(\"Code validation result: %s\" % result)\n\n        if not result:\n            print(\"Failed to verify security code\")\n            sys.exit(1)\n\n        if not api.is_trusted_session:\n            print(\"Session is not trusted. Requesting trust...\")\n            result = api.trust_session()\n            print(\"Session trust result %s\" % result)\n\n            if not result:\n                print(\"Failed to request trust. You will likely be prompted for the code again in the coming weeks\")\n    elif api.requires_2sa:\n        import click\n        print(\"Two-step authentication required. Your trusted devices are:\")\n\n        devices = api.trusted_devices\n        for i, device in enumerate(devices):\n            print(\n                \"  %s: %s\" % (i, device.get('deviceName',\n                \"SMS to %s\" % device.get('phoneNumber')))\n            )\n\n        device = click.prompt('Which device would you like to use?', default=0)\n        device = devices[device]\n        if not api.send_verification_code(device):\n            print(\"Failed to send verification code\")\n            sys.exit(1)\n\n        code = click.prompt('Please enter validation code')\n        if not api.validate_verification_code(device, code):\n            print(\"Failed to verify verification code\")\n            sys.exit(1)\n\nDevices\n=======\n\nYou can list which devices associated with your account by using the ``devices`` property:\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e api.devices\n    {\n    'i9vbKRGIcLYqJnXMd1b257kUWnoyEBcEh6yM+IfmiMLh7BmOpALS+w==': \u003cAppleDevice(iPhone 4S: Johnny Appleseed's iPhone)\u003e,\n    'reGYDh9XwqNWTGIhNBuEwP1ds0F/Lg5t/fxNbI4V939hhXawByErk+HYVNSUzmWV': \u003cAppleDevice(MacBook Air 11\": Johnny Appleseed's MacBook Air)\u003e\n    }\n\nand you can access individual devices by either their index, or their ID:\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e api.devices[0]\n    \u003cAppleDevice(iPhone 4S: Johnny Appleseed's iPhone)\u003e\n    \u003e\u003e\u003e api.devices['i9vbKRGIcLYqJnXMd1b257kUWnoyEBcEh6yM+IfmiMLh7BmOpALS+w==']\n    \u003cAppleDevice(iPhone 4S: Johnny Appleseed's iPhone)\u003e\n\nor, as a shorthand if you have only one associated apple device, you can simply use the ``iphone`` property to access the first device associated with your account:\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e api.iphone\n    \u003cAppleDevice(iPhone 4S: Johnny Appleseed's iPhone)\u003e\n\nNote: the first device associated with your account may not necessarily be your iPhone.\n\nFind My iPhone\n==============\n\nOnce you have successfully authenticated, you can start querying your data!\n\nLocation\n********\n\nReturns the device's last known location. The Find My iPhone app must have been installed and initialized.\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e api.iphone.location()\n    {'timeStamp': 1357753796553, 'locationFinished': True, 'longitude': -0.14189, 'positionType': 'GPS', 'locationType': None, 'latitude': 51.501364, 'isOld': False, 'horizontalAccuracy': 5.0}\n\nStatus\n******\n\nThe Find My iPhone response is quite bloated, so for simplicity's sake this method will return a subset of the properties.\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e api.iphone.status()\n    {'deviceDisplayName': 'iPhone 5', 'deviceStatus': '200', 'batteryLevel': 0.6166913, 'name': \"Peter's iPhone\"}\n\nIf you wish to request further properties, you may do so by passing in a list of property names.\n\nPlay Sound\n**********\n\nSends a request to the device to play a sound, if you wish pass a custom message you can do so by changing the subject arg.\n\n.. code-block:: python\n\n    api.iphone.play_sound()\n\nA few moments later, the device will play a ringtone, display the default notification (\"Find My iPhone Alert\") and a confirmation email will be sent to you.\n\nLost Mode\n*********\n\nLost mode is slightly different to the \"Play Sound\" functionality in that it allows the person who picks up the phone to call a specific phone number *without having to enter the passcode*. Just like \"Play Sound\" you may pass a custom message which the device will display, if it's not overridden the custom message of \"This iPhone has been lost. Please call me.\" is used.\n\n.. code-block:: python\n\n    phone_number = '555-373-383'\n    message = 'Thief! Return my phone immediately.'\n    api.iphone.lost_device(phone_number, message)\n\n\nCalendar\n========\n\nThe calendar webservice currently only supports fetching events.\n\nEvents\n******\n\nReturns this month's events:\n\n.. code-block:: python\n\n    api.calendar.events()\n\nOr, between a specific date range:\n\n.. code-block:: python\n\n    from_dt = datetime(2012, 1, 1)\n    to_dt = datetime(2012, 1, 31)\n    api.calendar.events(from_dt, to_dt)\n\nAlternatively, you may fetch a single event's details, like so:\n\n.. code-block:: python\n\n    api.calendar.get_event_detail('CALENDAR', 'EVENT_ID')\n\n\nContacts\n========\n\nYou can access your iCloud contacts/address book through the ``contacts`` property:\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e for c in api.contacts.all():\n    \u003e\u003e\u003e print(c.get('firstName'), c.get('phones'))\n    John [{'field': '+1 555-55-5555-5', 'label': 'MOBILE'}]\n\nNote: These contacts do not include contacts federated from e.g. Facebook, only the ones stored in iCloud.\n\n\nFile Storage (Ubiquity)\n=======================\n\nYou can access documents stored in your iCloud account by using the ``files`` property's ``dir`` method:\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e api.files.dir()\n    ['.do-not-delete',\n     '.localized',\n     'com~apple~Notes',\n     'com~apple~Preview',\n     'com~apple~mail',\n     'com~apple~shoebox',\n     'com~apple~system~spotlight'\n    ]\n\nYou can access children and their children's children using the filename as an index:\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e api.files['com~apple~Notes']\n    \u003cFolder: 'com~apple~Notes'\u003e\n    \u003e\u003e\u003e api.files['com~apple~Notes'].type\n    'folder'\n    \u003e\u003e\u003e api.files['com~apple~Notes'].dir()\n    ['Documents']\n    \u003e\u003e\u003e api.files['com~apple~Notes']['Documents'].dir()\n    ['Some Document']\n    \u003e\u003e\u003e api.files['com~apple~Notes']['Documents']['Some Document'].name\n    'Some Document'\n    \u003e\u003e\u003e api.files['com~apple~Notes']['Documents']['Some Document'].modified\n    datetime.datetime(2012, 9, 13, 2, 26, 17)\n    \u003e\u003e\u003e api.files['com~apple~Notes']['Documents']['Some Document'].size\n    1308134\n    \u003e\u003e\u003e api.files['com~apple~Notes']['Documents']['Some Document'].type\n    'file'\n\nAnd when you have a file that you'd like to download, the ``open`` method will return a response object from which you can read the ``content``.\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e api.files['com~apple~Notes']['Documents']['Some Document'].open().content\n    'Hello, these are the file contents'\n\nNote: the object returned from the above ``open`` method is a `response object \u003chttp://www.python-requests.org/en/latest/api/#classes\u003e`_ and the ``open`` method can accept any parameters you might normally use in a request using `requests \u003chttps://github.com/kennethreitz/requests\u003e`_.\n\nFor example, if you know that the file you're opening has JSON content:\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e api.files['com~apple~Notes']['Documents']['information.json'].open().json()\n    {'How much we love you': 'lots'}\n    \u003e\u003e\u003e api.files['com~apple~Notes']['Documents']['information.json'].open().json()['How much we love you']\n    'lots'\n\nOr, if you're downloading a particularly large file, you may want to use the ``stream`` keyword argument, and read directly from the raw response object:\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e download = api.files['com~apple~Notes']['Documents']['big_file.zip'].open(stream=True)\n    \u003e\u003e\u003e with open('downloaded_file.zip', 'wb') as opened_file:\n            opened_file.write(download.raw.read())\n\nFile Storage (iCloud Drive)\n===========================\n\nYou can access your iCloud Drive using an API identical to the Ubiquity one described in the previous section, except that it is rooted at ```api.drive```:\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e api.drive.dir()\n    ['Holiday Photos', 'Work Files']\n    \u003e\u003e\u003e api.drive['Holiday Photos']['2013']['Sicily'].dir()\n    ['DSC08116.JPG', 'DSC08117.JPG']\n\n    \u003e\u003e\u003e drive_file = api.drive['Holiday Photos']['2013']['Sicily']['DSC08116.JPG']\n    \u003e\u003e\u003e drive_file.name\n    'DSC08116.JPG'\n    \u003e\u003e\u003e drive_file.date_modified\n    datetime.datetime(2013, 3, 21, 12, 28, 12) # NB this is UTC\n    \u003e\u003e\u003e drive_file.size\n    2021698\n    \u003e\u003e\u003e drive_file.type\n    'file'\n\nThe ``open`` method will return a response object from which you can read the file's contents:\n\n.. code-block:: python\n\n        from shutil import copyfileobj\n        with drive_file.open(stream=True) as response:\n            with open(drive_file.name, 'wb') as file_out:\n                copyfileobj(response.raw, file_out)\n\nTo interact with files and directions the ``mkdir``, ``rename`` and ``delete`` functions are available\nfor a file or folder:\n\n.. code-block:: python\n\n    api.drive['Holiday Photos'].mkdir('2020')\n    api.drive['Holiday Photos']['2020'].rename('2020_copy')\n    api.drive['Holiday Photos']['2020_copy'].delete()\n\nThe ``upload`` method can be used to send a file-like object to the iCloud Drive:\n\n.. code-block:: python\n\n    with open('Vacation.jpeg', 'rb') as file_in:\n        api.drive['Holiday Photos'].upload(file_in)\n\nIt is strongly suggested to open file handles as binary rather than text to prevent decoding errors\nfurther down the line.\n\nPhoto Library\n=======================\n\nYou can access the iCloud Photo Library through the ``photos`` property.\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e api.photos.all\n    \u003cPhotoAlbum: 'All Photos'\u003e\n\nIndividual albums are available through the ``albums`` property:\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e api.photos.albums['Screenshots']\n    \u003cPhotoAlbum: 'Screenshots'\u003e\n\nWhich you can iterate to access the photo assets.  The 'All Photos' album is sorted by `added_date` so the most recently added photos are returned first.  All other albums are sorted by `asset_date` (which represents the exif date) :\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e for photo in api.photos.albums['Screenshots']:\n            print(photo, photo.filename)\n    \u003cPhotoAsset: id=AVbLPCGkp798nTb9KZozCXtO7jds\u003e IMG_6045.JPG\n\nTo download a photo use the `download` method, which will return a `response object \u003chttp://www.python-requests.org/en/latest/api/#classes\u003e`_, initialized with ``stream`` set to ``True``, so you can read from the raw response object:\n\n.. code-block:: python\n\n    photo = next(iter(api.photos.albums['Screenshots']), None)\n    download = photo.download()\n    with open(photo.filename, 'wb') as opened_file:\n        opened_file.write(download.raw.read())\n\nNote: Consider using ``shutil.copyfile`` or another buffered strategy for downloading the file so that the whole file isn't read into memory before writing.\n\nInformation about each version can be accessed through the ``versions`` property:\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e photo.versions.keys()\n    ['medium', 'original', 'thumb']\n\nTo download a specific version of the photo asset, pass the version to ``download()``:\n\n.. code-block:: python\n\n    download = photo.download('thumb')\n    with open(photo.versions['thumb']['filename'], 'wb') as thumb_file:\n        thumb_file.write(download.raw.read())\n\n\nCode samples\n============\n\nIf you wanna see some code samples see the `code samples file \u003c/CODE_SAMPLES.md\u003e`_.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpicklepete%2Fpyicloud","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpicklepete%2Fpyicloud","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpicklepete%2Fpyicloud/lists"}