Module pyatv

Main routines for interacting with an Apple TV.

Expand source code
"""Main routines for interacting with an Apple TV."""

import asyncio
import datetime  # noqa
from ipaddress import IPv4Address
import logging
from typing import Dict, List

import aiohttp

from pyatv import conf, exceptions, interface
from pyatv.airplay import setup as airplay_setup
from pyatv.airplay.pairing import AirPlayPairingHandler
from pyatv.companion import setup as companion_setup
from pyatv.companion.pairing import CompanionPairingHandler
from pyatv.const import Protocol
from pyatv.dmap import setup as dmap_setup
from pyatv.dmap.pairing import DmapPairingHandler
from pyatv.mrp import setup as mrp_setup
from pyatv.mrp.pairing import MrpPairingHandler
from pyatv.raop import setup as raop_setup
from pyatv.support import http
from pyatv.support.facade import FacadeAppleTV, SetupMethod
from pyatv.support.scan import BaseScanner, MulticastMdnsScanner, UnicastMdnsScanner

_LOGGER = logging.getLogger(__name__)

_PROTOCOL_IMPLEMENTATIONS: Dict[Protocol, SetupMethod] = {
    Protocol.MRP: mrp_setup,
    Protocol.DMAP: dmap_setup,
    Protocol.AirPlay: airplay_setup,
    Protocol.Companion: companion_setup,
    Protocol.RAOP: raop_setup,
}


async def scan(
    loop: asyncio.AbstractEventLoop,
    timeout: int = 5,
    identifier: str = None,
    protocol: Protocol = None,
    hosts: List[str] = None,
) -> List[conf.AppleTV]:
    """Scan for Apple TVs on network and return their configurations."""

    def _should_include(atv):
        if not atv.ready:
            return False

        if identifier and identifier not in atv.all_identifiers:
            return False

        if protocol and atv.get_service(protocol) is None:
            return False

        return True

    scanner: BaseScanner
    if hosts:
        scanner = UnicastMdnsScanner([IPv4Address(host) for host in hosts], loop)
    else:
        scanner = MulticastMdnsScanner(loop, identifier)

    devices = (await scanner.discover(timeout)).values()
    return [device for device in devices if _should_include(device)]


async def connect(
    config: conf.AppleTV,
    loop: asyncio.AbstractEventLoop,
    protocol: Protocol = None,
    session: aiohttp.ClientSession = None,
) -> interface.AppleTV:
    """Connect to a device based on a configuration."""
    if not config.services:
        raise exceptions.NoServiceError("no service to connect to")

    if config.identifier is None:
        raise exceptions.DeviceIdMissingError("no device identifier")

    session_manager = await http.create_session(session)
    atv = FacadeAppleTV(config, session_manager)

    for service in config.services:
        setup_method = _PROTOCOL_IMPLEMENTATIONS.get(service.protocol)
        if not setup_method:
            raise RuntimeError("missing implementation for protocol {service.protocol}")

        setup_data = setup_method(loop, config, atv.interfaces, atv, session_manager)
        if setup_data:
            _LOGGER.debug("Adding protocol %s", service.protocol)
            atv.add_protocol(service.protocol, setup_data)
        else:
            _LOGGER.debug("Not adding protocol: %s", service.protocol)
    try:
        await atv.connect()
    except Exception:
        await session_manager.close()
        raise
    else:
        return atv


async def pair(
    config: conf.AppleTV,
    protocol: Protocol,
    loop: asyncio.AbstractEventLoop,
    session: aiohttp.ClientSession = None,
    **kwargs
):
    """Pair a protocol for an Apple TV."""
    service = config.get_service(protocol)
    if not service:
        raise exceptions.NoServiceError(f"no service available for {protocol}")

    handler = {
        Protocol.DMAP: DmapPairingHandler,
        Protocol.MRP: MrpPairingHandler,
        Protocol.AirPlay: AirPlayPairingHandler,
        Protocol.Companion: CompanionPairingHandler,
    }.get(protocol)

    if handler is None:
        raise RuntimeError(f"missing implementation for {protocol}")

    return handler(config, await http.create_session(session), loop, **kwargs)

Sub-modules

pyatv.conf

Configuration used when connecting to a device …

pyatv.const

Constants used in the public API.

pyatv.convert

Various types of extraction and conversion functions.

pyatv.exceptions

Local exceptions used by library.

pyatv.helpers

Various helper methods.

pyatv.interface

Public interface exposed by library …

Functions

async def connect(config: AppleTV, loop: asyncio.events.AbstractEventLoop, protocol: Protocol = None, session: aiohttp.client.ClientSession = None) -> AppleTV

Connect to a device based on a configuration.

Expand source code
async def connect(
    config: conf.AppleTV,
    loop: asyncio.AbstractEventLoop,
    protocol: Protocol = None,
    session: aiohttp.ClientSession = None,
) -> interface.AppleTV:
    """Connect to a device based on a configuration."""
    if not config.services:
        raise exceptions.NoServiceError("no service to connect to")

    if config.identifier is None:
        raise exceptions.DeviceIdMissingError("no device identifier")

    session_manager = await http.create_session(session)
    atv = FacadeAppleTV(config, session_manager)

    for service in config.services:
        setup_method = _PROTOCOL_IMPLEMENTATIONS.get(service.protocol)
        if not setup_method:
            raise RuntimeError("missing implementation for protocol {service.protocol}")

        setup_data = setup_method(loop, config, atv.interfaces, atv, session_manager)
        if setup_data:
            _LOGGER.debug("Adding protocol %s", service.protocol)
            atv.add_protocol(service.protocol, setup_data)
        else:
            _LOGGER.debug("Not adding protocol: %s", service.protocol)
    try:
        await atv.connect()
    except Exception:
        await session_manager.close()
        raise
    else:
        return atv
async def pair(config: AppleTV, protocol: Protocol, loop: asyncio.events.AbstractEventLoop, session: aiohttp.client.ClientSession = None, **kwargs)

Pair a protocol for an Apple TV.

Expand source code
async def pair(
    config: conf.AppleTV,
    protocol: Protocol,
    loop: asyncio.AbstractEventLoop,
    session: aiohttp.ClientSession = None,
    **kwargs
):
    """Pair a protocol for an Apple TV."""
    service = config.get_service(protocol)
    if not service:
        raise exceptions.NoServiceError(f"no service available for {protocol}")

    handler = {
        Protocol.DMAP: DmapPairingHandler,
        Protocol.MRP: MrpPairingHandler,
        Protocol.AirPlay: AirPlayPairingHandler,
        Protocol.Companion: CompanionPairingHandler,
    }.get(protocol)

    if handler is None:
        raise RuntimeError(f"missing implementation for {protocol}")

    return handler(config, await http.create_session(session), loop, **kwargs)
async def scan(loop: asyncio.events.AbstractEventLoop, timeout: int = 5, identifier: str = None, protocol: Protocol = None, hosts: List[str] = None) -> List[AppleTV]

Scan for Apple TVs on network and return their configurations.

Expand source code
async def scan(
    loop: asyncio.AbstractEventLoop,
    timeout: int = 5,
    identifier: str = None,
    protocol: Protocol = None,
    hosts: List[str] = None,
) -> List[conf.AppleTV]:
    """Scan for Apple TVs on network and return their configurations."""

    def _should_include(atv):
        if not atv.ready:
            return False

        if identifier and identifier not in atv.all_identifiers:
            return False

        if protocol and atv.get_service(protocol) is None:
            return False

        return True

    scanner: BaseScanner
    if hosts:
        scanner = UnicastMdnsScanner([IPv4Address(host) for host in hosts], loop)
    else:
        scanner = MulticastMdnsScanner(loop, identifier)

    devices = (await scanner.discover(timeout)).values()
    return [device for device in devices if _should_include(device)]