<commit_before>"""
A Spawner for JupyterHub that runs each user's server in a separate docker container
"""
import itertools
import os
import string
from textwrap import dedent
from concurrent.futures import ThreadPoolExecutor
from pprint import pformat

import docker
from docker.errors import APIError
from docker.utils import kwargs_from_env
from tornado import gen

from escapism import escape
from jupyterhub.spawner import Spawner
from traitlets import (
    Dict,
    Unicode,
    Bool,
)

class UnicodeOrFalse(Unicode):
    info_text = 'a unicode string or False'
    def validate(self, obj, value):
        if value is False:
            return value
        return super(UnicodeOrFalse, self).validate(obj, value)

class DockerSpawner(Spawner):

    _executor = None
    @property
    def executor(self):
        """single global executor"""
        cls = self.__class__
        if cls._executor is None:
            cls._executor = ThreadPoolExecutor(1)
        return cls._executor

    _client = None
    @property
    def client(self):
        """single global client instance"""
        cls = self.__class__
        if cls._client is None:
            if self.use_docker_client_env:
                kwargs = kwargs_from_env(
                    assert_hostname=self.tls_assert_hostname
                )
                client = docker.Client(version='auto', **kwargs)
            else:
                if self.tls:
                    tls_config = True
                elif self.tls_verify or self.tls_ca or self.tls_client:
                    tls_config = docker.tls.TLSConfig(
                        client_cert=self.tls_client,
                        ca_cert=self.tls_ca,
                        verify=self.tls_verify,
                        assert_hostname = self.tls_assert_hostname)
                else:
                    tls_config = None

                docker_host = os.environ.get('DOCKER_HOST', 'unix://var/run/docker.sock')
                client = docker.Client(base_url=docker_host, tls=tls_config, version='auto')
            cls._client = client
        return cls._client

    container_id = Unicode()
    container_ip = Unicode('127.0.0.1', config=True)
    container_image = Unicode("jupyter/singleuser", config=True)
    container_prefix = Unicode(
        "jupyter",
        config=True,
        help=dedent(
            """
            Prefix for container names. The full container name for a particular
            user will be <prefix>-<username>.
            """
        )
    )

    volumes = Dict(
        config=True,
        help=dedent(
            """
            Map from host file/directory to container file/directory.
            Volumes specified here will be read/write in the container.
            If you use {username} in the host file / directory path, it will be
            replaced with the current user's name.
            """
        )
    )

    read_only_volumes = Dict(
        config=True,
        help=dedent(
            """
            Map from host file/directory to container file/directory.
            Volumes specified here will be read-only in the container.
            If you use {username} in the host file / directory path, it will be
            replaced with the current user's name.
            """
        )
    )

    use_docker_client_env = Bool(False, config=True, help="If True, will use Docker client env variable (boot2docker friendly)")
    tls = Bool(False, config=True, help="If True, connect to docker with --tls")
    tls_verify = Bool(False, config=True, help="If True, connect to docker with --tlsverify")
    tls_ca = Unicode("", config=True, help="Path to CA certificate for docker TLS")
    tls_cert = Unicode("", config=True, help="Path to client certificate for docker TLS")
    tls_key = Unicode("", config=True, help="Path to client key for docker TLS")
    tls_assert_hostname = UnicodeOrFalse(default_value=None, allow_none=True,
        config=True,
        help="If False, do not verify hostname of docker daemon",
    )

    remove_containers = Bool(False, config=True, help="If True, delete containers after they are stopped.")
    extra_create_kwargs = Dict(config=True, help="Additional args to pass for container create")
    extra_start_kwargs = Dict(config=True, help="Additional args to pass for container start")
    extra_host_config = Dict(config=True, help="Additional args to create_host_config for container create")

    _container_safe_chars = set(string.ascii_letters + string.digits + '-')
    _container_escape_char = '_'

    hub_ip_connect = Unicode(
        "",
        config=True,
        help=dedent(
            """
            If set, DockerSpawner will configure the containers to use
            the specified IP to connect the hub api.  This is useful
            when the hub_api is bound to listen on all ports or is
            running inside of a container.
            """
        )
    )

    use_internal_ip = Bool(
        False,
        config=True,
        help=dedent(
            """
            Enable the usage of the internal docker ip. This is useful if you are running
            jupyterhub (as a container) and the user containers within the same docker engine.
            E.g. by mounting the docker socket of the host into the jupyterhub container.
            """
        )
    )

    links = Dict(
        config=True,
        help=dedent(
            """
            Specify docker link mapping to add to the container, e.g.

                links = {'jupyterhub: 'jupyterhub'}

            If the Hub is running in a Docker container,
            this can simplify routing because all traffic will be using docker hostnames.
            """
        )
    )

    network_name = Unicode(
        "bridge",
        config=True,
        help=dedent(
            """
            The name of the docker network from which to retrieve the internal IP address. Defaults to the default
            docker network 'bridge'. You need to set this if you run your jupyterhub containers in a
            non-standard network. Only has an effect if use_internal_ip=True.
            """
        )
    )

    @property
    def tls_client(self):
        """A tuple consisting of the TLS client certificate and key if they
        have been provided, otherwise None.

        """
        if self.tls_cert and self.tls_key:
            return (self.tls_cert, self.tls_key)
        return None

    @property
    def volume_mount_points(self):
        """
        Volumes are declared in docker-py in two stages.  First, you declare
        all the locations where you're going to mount volumes when you call
        create_container.

        Returns a list of all the values in self.volumes or
        self.read_only_volumes.
        """
        return list(
            itertools.chain(
                self.volumes.values(),
                self.read_only_volumes.values(),
            )
        )

    @property
    def volume_binds(self):
        """
        The second half of declaring a volume with docker-py happens when you
        actually call start().  The required format is a dict of dicts that
        looks like:

        {
            host_location: {'bind': container_location, 'ro': True}
        }
        """
        volumes = {
            key.format(username=self.user.name): {'bind': value.format(username=self.user.name), 'ro': False}
            for key, value in self.volumes.items()
        }
        ro_volumes = {
            key.format(username=self.user.name): {'bind': value.format(username=self.user.name), 'ro': True}
            for key, value in self.read_only_volumes.items()
        }
        volumes.update(ro_volumes)
        return volumes

    _escaped_name = None
    @property
    def escaped_name(self):
        if self._escaped_name is None:
            self._escaped_name = escape(self.user.name,
                safe=self._container_safe_chars,
                escape_char=self._container_escape_char,
            )
        return self._escaped_name

    @property
    def container_name(self):
        return "{}-{}".format(self.container_prefix, self.escaped_name)

    def load_state(self, state):
        super(DockerSpawner, self).load_state(state)
        self.container_id = state.get('container_id', '')

    def get_state(self):
        state = super(DockerSpawner, self).get_state()
        if self.container_id:
            state['container_id'] = self.container_id
        return state

    def _public_hub_api_url(self):
        proto, path = self.hub.api_url.split('://', 1)
        ip, rest = path.split(':', 1)
        return '{proto}://{ip}:{rest}'.format(
            proto = proto,
            ip = self.hub_ip_connect,
            rest = rest
        )

    def _env_keep_default(self):
        """Don't inherit any env from the parent process"""
        return []

    def _env_default(self):
        env = super(DockerSpawner, self)._env_default()
        env.update(dict(
            JPY_USER=self.user.name,
            JPY_COOKIE_NAME=self.user.server.cookie_name,
            JPY_BASE_URL=self.user.server.base_url,
            JPY_HUB_PREFIX=self.hub.server.base_url
        ))

        if self.hub_ip_connect:
           hub_api_url = self._public_hub_api_url()
        else:
           hub_api_url = self.hub.api_url
        env['JPY_HUB_API_URL'] = hub_api_url

        return env

    def _docker(self, method, *args, **kwargs):
        """wrapper for calling docker methods

        to be passed to ThreadPoolExecutor
        """
        m = getattr(self.client, method)
        return m(*args, **kwargs)

    def docker(self, method, *args, **kwargs):
        """Call a docker method in a background thread

        returns a Future
        """
        return self.executor.submit(self._docker, method, *args, **kwargs)

    @gen.coroutine
    def poll(self):
        """Check for my id in `docker ps`"""
        container = yield self.get_container()
        if not container:
            self.log.warn("container not found")
            return ""

        container_state = container['State']
        self.log.debug(
            "Container %s status: %s",
            self.container_id[:7],
            pformat(container_state),
        )

        if container_state["Running"]:
            return None
        else:
            return (
                "ExitCode={ExitCode}, "
                "Error='{Error}', "
                "FinishedAt={FinishedAt}".format(**container_state)
            )

    @gen.coroutine
    def get_container(self):
        self.log.debug("Getting container '%s'", self.container_name)
        try:
            container = yield self.docker(
                'inspect_container', self.container_name
            )
            self.container_id = container['Id']
        except APIError as e:
            if e.response.status_code == 404:
                self.log.info("Container '%s' is gone", self.container_name)
                container = None
                # my container is gone, forget my id
                self.container_id = ''
            elif e.response.status_code == 500:
                self.log.info("Container '%s' is on unhealthy node", self.container_name)
                container = None
                # my container is unhealthy, forget my id
                self.container_id = ''
            else:
                raise
        return container

    @gen.coroutine
    def start(self, image=None, extra_create_kwargs=None,
        extra_start_kwargs=None, extra_host_config=None):
        """Start the single-user server in a docker container. You can override
        the default parameters passed to `create_container` through the
        `extra_create_kwargs` dictionary and passed to `start` through the
        `extra_start_kwargs` dictionary.  You can also override the
        'host_config' parameter passed to `create_container` through the
        `extra_host_config` dictionary.

        Per-instance `extra_create_kwargs`, `extra_start_kwargs`, and
        `extra_host_config` take precedence over their global counterparts.

        """
        container = yield self.get_container()
        if container is None:
            image = image or self.container_image

            # build the dictionary of keyword arguments for create_container
            create_kwargs = dict(
                image=image,
                environment=self.get_env(),
                volumes=self.volume_mount_points,
                name=self.container_name)
            create_kwargs.update(self.extra_create_kwargs)
            if extra_create_kwargs:
                create_kwargs.update(extra_create_kwargs)

            # build the dictionary of keyword arguments for host_config
            host_config = dict(binds=self.volume_binds, links=self.links)

            if not self.use_internal_ip:
                host_config['port_bindings'] = {8888: (self.container_ip,)}

            host_config.update(self.extra_host_config)

            if extra_host_config:
                host_config.update(extra_host_config)

            self.log.debug("Starting host with config: %s", host_config)

            host_config = self.client.create_host_config(**host_config)
            create_kwargs.setdefault('host_config', {}).update(host_config)

            # create the container
            resp = yield self.docker('create_container', **create_kwargs)
            self.container_id = resp['Id']
            self.log.info(
                "Created container '%s' (id: %s) from image %s",
                self.container_name, self.container_id[:7], image)

        else:
            self.log.info(
                "Found existing container '%s' (id: %s)",
                self.container_name, self.container_id[:7])

        # TODO: handle unpause
        self.log.info(
            "Starting container '%s' (id: %s)",
            self.container_name, self.container_id[:7])

        # build the dictionary of keyword arguments for start
        start_kwargs = {}
        start_kwargs.update(self.extra_start_kwargs)
        if extra_start_kwargs:
            start_kwargs.update(extra_start_kwargs)

        # start the container
        yield self.docker('start', self.container_id, **start_kwargs)

        ip, port = yield self.get_ip_and_port()
        self.user.server.ip = ip
        self.user.server.port = port

    def get_ip_and_port(self):
        if self.use_internal_ip:
            resp = yield self.docker('inspect_container', self.container_id)
            network_settings = resp['NetworkSettings']
            if 'Networks' in network_settings:
                ip = self.get_network_ip(network_settings)
            else:  # Fallback for old versions of docker (<1.9) without network management
                ip = network_settings['IPAddress']
            port = 8888
        else:
            resp = yield self.docker('port', self.container_id, 8888)
            if resp is None:
                raise RuntimeError("Failed to get port info for %s" % self.container_id)
            ip = resp[0]['HostIp']
            port = resp[0]['HostPort']
        return ip, port

    def get_network_ip(self, network_settings):
        networks = network_settings['Networks']
        if self.network_name not in networks:
            raise Exception(
                "Unknown docker network '{network}'. Did you create it with 'docker network create <name>' and "
                "did you pass network_mode=<name> in extra_kwargs?".format(
                    network=self.network_name
                )
            )
        network = networks[self.network_name]
        ip = network['IPAddress']
        return ip

    @gen.coroutine
    def stop(self, now=False):
        """Stop the container

        Consider using pause/unpause when docker-py adds support
        """
        self.log.info(
            "Stopping container %s (id: %s)",
            self.container_name, self.container_id[:7])
        yield self.docker('stop', self.container_id)

        if self.remove_containers:
            self.log.info(
                "Removing container %s (id: %s)",
                self.container_name, self.container_id[:7])
            # remove the container, as well as any associated volumes
            yield self.docker('remove_container', self.container_id, v=True)

        self.clear_state()
<commit_msg>revert to yeild from to work with swarm<commit_after>"""
A Spawner for JupyterHub that runs each user's server in a separate docker container
"""
import itertools
import os
import string
from textwrap import dedent
from concurrent.futures import ThreadPoolExecutor
from pprint import pformat

import docker
from docker.errors import APIError
from docker.utils import kwargs_from_env
from tornado import gen

from escapism import escape
from jupyterhub.spawner import Spawner
from traitlets import (
    Dict,
    Unicode,
    Bool,
)

class UnicodeOrFalse(Unicode):
    info_text = 'a unicode string or False'
    def validate(self, obj, value):
        if value is False:
            return value
        return super(UnicodeOrFalse, self).validate(obj, value)

class DockerSpawner(Spawner):

    _executor = None
    @property
    def executor(self):
        """single global executor"""
        cls = self.__class__
        if cls._executor is None:
            cls._executor = ThreadPoolExecutor(1)
        return cls._executor

    _client = None
    @property
    def client(self):
        """single global client instance"""
        cls = self.__class__
        if cls._client is None:
            if self.use_docker_client_env:
                kwargs = kwargs_from_env(
                    assert_hostname=self.tls_assert_hostname
                )
                client = docker.Client(version='auto', **kwargs)
            else:
                if self.tls:
                    tls_config = True
                elif self.tls_verify or self.tls_ca or self.tls_client:
                    tls_config = docker.tls.TLSConfig(
                        client_cert=self.tls_client,
                        ca_cert=self.tls_ca,
                        verify=self.tls_verify,
                        assert_hostname = self.tls_assert_hostname)
                else:
                    tls_config = None

                docker_host = os.environ.get('DOCKER_HOST', 'unix://var/run/docker.sock')
                client = docker.Client(base_url=docker_host, tls=tls_config, version='auto')
            cls._client = client
        return cls._client

    container_id = Unicode()
    container_ip = Unicode('127.0.0.1', config=True)
    container_image = Unicode("jupyter/singleuser", config=True)
    container_prefix = Unicode(
        "jupyter",
        config=True,
        help=dedent(
            """
            Prefix for container names. The full container name for a particular
            user will be <prefix>-<username>.
            """
        )
    )

    volumes = Dict(
        config=True,
        help=dedent(
            """
            Map from host file/directory to container file/directory.
            Volumes specified here will be read/write in the container.
            If you use {username} in the host file / directory path, it will be
            replaced with the current user's name.
            """
        )
    )

    read_only_volumes = Dict(
        config=True,
        help=dedent(
            """
            Map from host file/directory to container file/directory.
            Volumes specified here will be read-only in the container.
            If you use {username} in the host file / directory path, it will be
            replaced with the current user's name.
            """
        )
    )

    use_docker_client_env = Bool(False, config=True, help="If True, will use Docker client env variable (boot2docker friendly)")
    tls = Bool(False, config=True, help="If True, connect to docker with --tls")
    tls_verify = Bool(False, config=True, help="If True, connect to docker with --tlsverify")
    tls_ca = Unicode("", config=True, help="Path to CA certificate for docker TLS")
    tls_cert = Unicode("", config=True, help="Path to client certificate for docker TLS")
    tls_key = Unicode("", config=True, help="Path to client key for docker TLS")
    tls_assert_hostname = UnicodeOrFalse(default_value=None, allow_none=True,
        config=True,
        help="If False, do not verify hostname of docker daemon",
    )

    remove_containers = Bool(False, config=True, help="If True, delete containers after they are stopped.")
    extra_create_kwargs = Dict(config=True, help="Additional args to pass for container create")
    extra_start_kwargs = Dict(config=True, help="Additional args to pass for container start")
    extra_host_config = Dict(config=True, help="Additional args to create_host_config for container create")

    _container_safe_chars = set(string.ascii_letters + string.digits + '-')
    _container_escape_char = '_'

    hub_ip_connect = Unicode(
        "",
        config=True,
        help=dedent(
            """
            If set, DockerSpawner will configure the containers to use
            the specified IP to connect the hub api.  This is useful
            when the hub_api is bound to listen on all ports or is
            running inside of a container.
            """
        )
    )

    use_internal_ip = Bool(
        False,
        config=True,
        help=dedent(
            """
            Enable the usage of the internal docker ip. This is useful if you are running
            jupyterhub (as a container) and the user containers within the same docker engine.
            E.g. by mounting the docker socket of the host into the jupyterhub container.
            """
        )
    )

    links = Dict(
        config=True,
        help=dedent(
            """
            Specify docker link mapping to add to the container, e.g.

                links = {'jupyterhub: 'jupyterhub'}

            If the Hub is running in a Docker container,
            this can simplify routing because all traffic will be using docker hostnames.
            """
        )
    )

    network_name = Unicode(
        "bridge",
        config=True,
        help=dedent(
            """
            The name of the docker network from which to retrieve the internal IP address. Defaults to the default
            docker network 'bridge'. You need to set this if you run your jupyterhub containers in a
            non-standard network. Only has an effect if use_internal_ip=True.
            """
        )
    )

    @property
    def tls_client(self):
        """A tuple consisting of the TLS client certificate and key if they
        have been provided, otherwise None.

        """
        if self.tls_cert and self.tls_key:
            return (self.tls_cert, self.tls_key)
        return None

    @property
    def volume_mount_points(self):
        """
        Volumes are declared in docker-py in two stages.  First, you declare
        all the locations where you're going to mount volumes when you call
        create_container.

        Returns a list of all the values in self.volumes or
        self.read_only_volumes.
        """
        return list(
            itertools.chain(
                self.volumes.values(),
                self.read_only_volumes.values(),
            )
        )

    @property
    def volume_binds(self):
        """
        The second half of declaring a volume with docker-py happens when you
        actually call start().  The required format is a dict of dicts that
        looks like:

        {
            host_location: {'bind': container_location, 'ro': True}
        }
        """
        volumes = {
            key.format(username=self.user.name): {'bind': value.format(username=self.user.name), 'ro': False}
            for key, value in self.volumes.items()
        }
        ro_volumes = {
            key.format(username=self.user.name): {'bind': value.format(username=self.user.name), 'ro': True}
            for key, value in self.read_only_volumes.items()
        }
        volumes.update(ro_volumes)
        return volumes

    _escaped_name = None
    @property
    def escaped_name(self):
        if self._escaped_name is None:
            self._escaped_name = escape(self.user.name,
                safe=self._container_safe_chars,
                escape_char=self._container_escape_char,
            )
        return self._escaped_name

    @property
    def container_name(self):
        return "{}-{}".format(self.container_prefix, self.escaped_name)

    def load_state(self, state):
        super(DockerSpawner, self).load_state(state)
        self.container_id = state.get('container_id', '')

    def get_state(self):
        state = super(DockerSpawner, self).get_state()
        if self.container_id:
            state['container_id'] = self.container_id
        return state

    def _public_hub_api_url(self):
        proto, path = self.hub.api_url.split('://', 1)
        ip, rest = path.split(':', 1)
        return '{proto}://{ip}:{rest}'.format(
            proto = proto,
            ip = self.hub_ip_connect,
            rest = rest
        )

    def _env_keep_default(self):
        """Don't inherit any env from the parent process"""
        return []

    def _env_default(self):
        env = super(DockerSpawner, self)._env_default()
        env.update(dict(
            JPY_USER=self.user.name,
            JPY_COOKIE_NAME=self.user.server.cookie_name,
            JPY_BASE_URL=self.user.server.base_url,
            JPY_HUB_PREFIX=self.hub.server.base_url
        ))

        if self.hub_ip_connect:
           hub_api_url = self._public_hub_api_url()
        else:
           hub_api_url = self.hub.api_url
        env['JPY_HUB_API_URL'] = hub_api_url

        return env

    def _docker(self, method, *args, **kwargs):
        """wrapper for calling docker methods

        to be passed to ThreadPoolExecutor
        """
        m = getattr(self.client, method)
        return m(*args, **kwargs)

    def docker(self, method, *args, **kwargs):
        """Call a docker method in a background thread

        returns a Future
        """
        return self.executor.submit(self._docker, method, *args, **kwargs)

    @gen.coroutine
    def poll(self):
        """Check for my id in `docker ps`"""
        container = yield self.get_container()
        if not container:
            self.log.warn("container not found")
            return ""

        container_state = container['State']
        self.log.debug(
            "Container %s status: %s",
            self.container_id[:7],
            pformat(container_state),
        )

        if container_state["Running"]:
            return None
        else:
            return (
                "ExitCode={ExitCode}, "
                "Error='{Error}', "
                "FinishedAt={FinishedAt}".format(**container_state)
            )

    @gen.coroutine
    def get_container(self):
        self.log.debug("Getting container '%s'", self.container_name)
        try:
            container = yield self.docker(
                'inspect_container', self.container_name
            )
            self.container_id = container['Id']
        except APIError as e:
            if e.response.status_code == 404:
                self.log.info("Container '%s' is gone", self.container_name)
                container = None
                # my container is gone, forget my id
                self.container_id = ''
            elif e.response.status_code == 500:
                self.log.info("Container '%s' is on unhealthy node", self.container_name)
                container = None
                # my container is unhealthy, forget my id
                self.container_id = ''
            else:
                raise
        return container

    @gen.coroutine
    def start(self, image=None, extra_create_kwargs=None,
        extra_start_kwargs=None, extra_host_config=None):
        """Start the single-user server in a docker container. You can override
        the default parameters passed to `create_container` through the
        `extra_create_kwargs` dictionary and passed to `start` through the
        `extra_start_kwargs` dictionary.  You can also override the
        'host_config' parameter passed to `create_container` through the
        `extra_host_config` dictionary.

        Per-instance `extra_create_kwargs`, `extra_start_kwargs`, and
        `extra_host_config` take precedence over their global counterparts.

        """
        container = yield self.get_container()
        if container is None:
            image = image or self.container_image

            # build the dictionary of keyword arguments for create_container
            create_kwargs = dict(
                image=image,
                environment=self.get_env(),
                volumes=self.volume_mount_points,
                name=self.container_name)
            create_kwargs.update(self.extra_create_kwargs)
            if extra_create_kwargs:
                create_kwargs.update(extra_create_kwargs)

            # build the dictionary of keyword arguments for host_config
            host_config = dict(binds=self.volume_binds, links=self.links)

            if not self.use_internal_ip:
                host_config['port_bindings'] = {8888: (self.container_ip,)}

            host_config.update(self.extra_host_config)

            if extra_host_config:
                host_config.update(extra_host_config)

            self.log.debug("Starting host with config: %s", host_config)

            host_config = self.client.create_host_config(**host_config)
            create_kwargs.setdefault('host_config', {}).update(host_config)

            # create the container
            resp = yield self.docker('create_container', **create_kwargs)
            self.container_id = resp['Id']
            self.log.info(
                "Created container '%s' (id: %s) from image %s",
                self.container_name, self.container_id[:7], image)

        else:
            self.log.info(
                "Found existing container '%s' (id: %s)",
                self.container_name, self.container_id[:7])

        # TODO: handle unpause
        self.log.info(
            "Starting container '%s' (id: %s)",
            self.container_name, self.container_id[:7])

        # build the dictionary of keyword arguments for start
        start_kwargs = {}
        start_kwargs.update(self.extra_start_kwargs)
        if extra_start_kwargs:
            start_kwargs.update(extra_start_kwargs)

        # start the container
        yield self.docker('start', self.container_id, **start_kwargs)

        ip, port = yield from self.get_ip_and_port()
        self.user.server.ip = ip
        self.user.server.port = port

    def get_ip_and_port(self):
        if self.use_internal_ip:
            resp = yield self.docker('inspect_container', self.container_id)
            network_settings = resp['NetworkSettings']
            if 'Networks' in network_settings:
                ip = self.get_network_ip(network_settings)
            else:  # Fallback for old versions of docker (<1.9) without network management
                ip = network_settings['IPAddress']
            port = 8888
        else:
            resp = yield self.docker('port', self.container_id, 8888)
            if resp is None:
                raise RuntimeError("Failed to get port info for %s" % self.container_id)
            ip = resp[0]['HostIp']
            port = resp[0]['HostPort']
        return ip, port

    def get_network_ip(self, network_settings):
        networks = network_settings['Networks']
        if self.network_name not in networks:
            raise Exception(
                "Unknown docker network '{network}'. Did you create it with 'docker network create <name>' and "
                "did you pass network_mode=<name> in extra_kwargs?".format(
                    network=self.network_name
                )
            )
        network = networks[self.network_name]
        ip = network['IPAddress']
        return ip

    @gen.coroutine
    def stop(self, now=False):
        """Stop the container

        Consider using pause/unpause when docker-py adds support
        """
        self.log.info(
            "Stopping container %s (id: %s)",
            self.container_name, self.container_id[:7])
        yield self.docker('stop', self.container_id)

        if self.remove_containers:
            self.log.info(
                "Removing container %s (id: %s)",
                self.container_name, self.container_id[:7])
            # remove the container, as well as any associated volumes
            yield self.docker('remove_container', self.container_id, v=True)

        self.clear_state()
<|endoftext|>


<commit_before>"""View API endpoint."""

from rest_framework import viewsets, response, permissions, renderers
# from rest_framework.decorators import detail_route
# from rest_framework.response import Response
from imager_images.models import Photo
from imager_api.serializers import PhotoSerializer
from imager_api.permissions import IsOwnerOrReadOnly

class PhotoViewSet(viewsets.ModelViewSet):
    """API endpoint that allows users to be viewed or edited."""

    queryset = Photo.objects.all()
    serializer_class = PhotoSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly,)

    def perform_create(self, serializer):
        serializer.save(photographer=self.request.user.profile)
<commit_msg>added list method which enables filtering of queryset for api<commit_after>"""View API endpoint."""

from rest_framework import viewsets, response, permissions
from imager_images.models import Photo
from imager_api.serializers import PhotoSerializer
from imager_api.permissions import IsOwnerOrReadOnly

class PhotoViewSet(viewsets.ModelViewSet):
    """API endpoint that allows users to be viewed or edited."""

    queryset = Photo.objects.all()
    serializer_class = PhotoSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly,)

    def list(self, request, *args, **kwargs):
        """Filter photos by user."""
        queryset = Photo.objects.filter(photographer=request.user.profile)
        serializer = PhotoSerializer(queryset, many=True)
        return response.Response(serializer.data)
<|endoftext|>


<commit_before>from mock import patch
from mock import MagicMock
from nose import with_setup
from nose.tools import raises
import requests
import os
import json
from imboclient import client as imbo
from imboclient.url import image, images, status, user, accesstoken
import __builtin__

class TestClient:

    def setup(self):
        self._client = imbo.Client(['http://imbo.local'], 'public', 'private');

    def teardown(self):
        self._client = None

    def test_server_urls_generic(self):
        self._client = imbo.Client(['imbo.local'], 'public', 'private');
        assert self._client.server_urls[0] == 'http://imbo.local'

    def test_server_urls_http(self):
        self._client = imbo.Client(['http://imbo.local'], 'public', 'private');
        assert self._client.server_urls[0] == 'http://imbo.local'

    def test_server_urls_https(self):
        self._client = imbo.Client(['https://imbo.local'], 'public', 'private');
        assert self._client.server_urls[0] == 'https://imbo.local'

    def test_server_urls_port_normal(self):
        self._client = imbo.Client(['http://imbo.local'], 'public', 'private');
        assert self._client.server_urls[0] == 'http://imbo.local'

    def test_server_urls_port_normal_explicit(self):
        self._client = imbo.Client(['http://imbo.local:80'], 'public', 'private');
        assert self._client.server_urls[0] == 'http://imbo.local'

    def test_server_urls_port_ssl(self):
        self._client = imbo.Client(['https://imbo.local:443'], 'public', 'private');
        assert self._client.server_urls[0] == 'https://imbo.local'

    def test_server_urls_port_explicit_without_protocol(self):
        self._client = imbo.Client(['imbo.local:8000'], 'public', 'private');
        assert self._client.server_urls[0] == 'http://imbo.local:8000'

    @patch('imboclient.url.status.UrlStatus')
    def test_status_url(self, mocked_url_status):
        mocked_url_status_instance = mocked_url_status.return_value
        mocked_url_status_instance.url.return_value = 'correctstatusurl'

        status_url = self._client.status_url()
        mocked_url_status_instance.url.assert_called_once()
        assert status_url == 'correctstatusurl'

    @patch('imboclient.url.user.UrlUser')
    def test_user_url(self, mocked_url_user):
        mocked_url_user_instance = mocked_url_user.return_value
        mocked_url_user_instance.url.return_value = 'correctuserurl'

        user_url = self._client.user_url()
        mocked_url_user_instance.url.assert_called_once()
        assert user_url == 'correctuserurl'

    @patch('imboclient.url.images.UrlImages')
    def test_images_url(self, mocked_url_images):
        mocked_url_images_instance = mocked_url_images.return_value
        mocked_url_images_instance.url.return_value = 'correctimagesurl'

        images_url = self._client.images_url()
        mocked_url_images.assert_called_once_with('http://imbo.local', 'public', 'private')
        mocked_url_images_instance.url.assert_called_once()
        assert images_url == 'correctimagesurl'

    @patch('imboclient.url.image.UrlImage')
    def test_image_url(self, mocked_url_image):
        mocked_url_image_instance = mocked_url_image.return_value
        mocked_url_image_instance.url.return_value = 'correctimageurl'

        image_url = self._client.image_url('ff')
        mocked_url_image.assert_called_once_with('http://imbo.local', 'public', 'private', 'ff')
        mocked_url_image_instance.url.assert_called_once()
        assert image_url == 'correctimageurl'

    @patch('imboclient.url.signed.Signed.str')
    @patch('requests.put')
    @patch('os.path.isfile')
    @patch('os.path.getsize')
    @patch('__builtin__.open')
    def test_add_image(self, mocked_open, mocked_os_path_getsize, mocked_os_path_isfile, mocked_requests_put, mocked_url_signature):
        mocked_open_return = MagicMock()
        mocked_open_return.read.return_value = 'content'
        mocked_open.return_value = mocked_open_return

        mocked_os_path_isfile.return_value = True
        mocked_os_path_getsize.return_value = 7

        mocked_url_signature.return_value = 'signedurl'

        result = self._client.add_image('/mocked/image/path.jpg')
        mocked_requests_put.assert_called_once_with('signedurl', 'content')

    @patch('requests.put')
    @patch('imboclient.client.Client._signed_url')
    @patch('imboclient.client.Client.image_url')
    @patch('imboclient.client.Client.image_identifier_from_string')
    def test_add_image_from_string(self, mock_image_identifier_from_string, mock_image_url, mock_signed_url, mock_requests_put):
        mock_image_identifier_from_string.return_value = 'imageidentifier'
        mock_image_url.return_value = 'imageurl'
        mock_signed_url.return_value = 'signedurl'

        self._client.add_image_from_string('imagestring')

        mock_requests_put.assert_called_once_with('signedurl', 'imagestring')

    @patch('imboclient.client.Client.add_image_from_string')
    @patch('requests.get')
    @patch('imboclient.url.image.UrlImage.url')
    def test_add_image_from_url(self, mock_url_url, mock_requests_get, mock_add_image_from_string):
        mock_url_url.return_value = 'avalidurlstring'
        mock_requests_get.return_value = 'imagedatafromhttp'

        self._client.add_image_from_url(image.UrlImage("", "", "", ""))
        mock_requests_get.assert_called_once_with('avalidurlstring')
        mock_add_image_from_string.assert_called_once_with('avalidurlstring', 'imagedatafromhttp')

    @patch('imboclient.client.Client.image_identifier')
    @patch('imboclient.client.Client.image_identifier_exists')
    def test_image_exists(self, mock_image_identifier_exists, mock_image_identifier):
        mock_image_identifier.return_value = 'identifier'
        mock_image_identifier_exists.return_value = True

        image_exists = self._client.image_exists('/dummy/path')

        assert image_exists == True
        mock_image_identifier_exists.assert_called_once_with('identifier')
        mock_image_identifier.assert_called_once_with('/dummy/path')

    @patch('requests.head')
    @patch('imboclient.url.image.UrlImage')
    def test_image_identifier_exists_true(self, mocked_url_image, mocked_requests_head) :
        mocked_requests_head.return_value = self._valid_requests_response_stub_ok()
        mocked_url_image_instance = mocked_url_image.return_value
        mocked_url_image_instance.url.return_value = 'http://imbo.local/users/public/ff?accessToken=aa'

        assert self._client.image_identifier_exists('ff') == True
        mocked_requests_head.assert_called_once_with('http://imbo.local/users/public/ff?accessToken=aa')
        mocked_url_image.assert_called_once_with('http://imbo.local', 'public', 'private', 'ff')

    @patch('requests.head')
    @patch('imboclient.url.image.UrlImage')
    def test_image_identifier_exists_false(self, mocked_url_image, mocked_requests_get):
        mocked_requests_get.return_value = self._invalid_requests_response_stub_not_found()
        mocked_url_image_instance = mocked_url_image.return_value
        mocked_url_image_instance.url.return_value = 'http://imbo.local/users/public/ffa?accessToken=aaf'

        assert self._client.image_identifier_exists('ffa') == False
        mocked_requests_get.assert_called_once_with('http://imbo.local/users/public/ffa?accessToken=aaf')
        mocked_url_image.assert_called_once_with('http://imbo.local', 'public', 'private', 'ffa')

    def test_head_image(self):
        raise NotImplementedError("Test missing")

    def test_delete_image(self):
        raise NotImplementedError("Test missing")

    def test_edit_metadata(self):
        raise NotImplementedError("Test missing")

    def test_replace_metadata(self):
        raise NotImplementedError("Test missing")

    def test_delete_metadata(self):
        raise NotImplementedError("Test missing")

    @patch('requests.get')
    @patch('imboclient.url.user.UrlUser.url')
    def test_num_images(self, mocked_url_user, mocked_requests_get):
        class StubResponse:
            text = '{"numImages": 2}'

        mocked_url_user.return_value = 'http://imbo.local/users/public'
        mocked_requests_get.return_value = StubResponse()

        num_images = self._client.num_images()

        mocked_url_user.resource_url.assert_called_once()
        mocked_requests_get.assert_called_once_with(mocked_url_user.return_value)

        assert num_images == 2

    @patch('imboclient.url.imagesquery.Query')
    @patch('imboclient.url.images.UrlImages.url')
    @patch('imboclient.url.images.UrlImages.add_query')
    @patch('requests.get')
    def test_images(self, mock_requests_get, mock_url_images_addquery, mock_url_images, mock_imagesquery):
        class StubResponse:
            text = '[{"key": "value"}]'

        mock_url_images.return_value = 'http://imbo.local/public/images.json'
        mock_requests_get.return_value = StubResponse()

        images = self._client.images(mock_imagesquery)
        assert len(images) == 1

        mock_requests_get.assert_called_once_with(mock_url_images.return_value)
        mock_url_images_addquery.assert_called_once_with(mock_imagesquery)

    def test_image_data(self):
        raise NotImplementedError("Test missing")

    def test_image_data_from_url(self):
        raise NotImplementedError("Test missing")

    def test_image_properties_from_url(self):
        raise NotImplementedError("Test missing")

    def test_image_properties(self):
        raise NotImplementedError("Test missing")

    @patch('os.path.isfile')
    @patch('os.path.getsize')
    @patch('__builtin__.open')
    def test_image_identifier(self, mocked_open, mocked_os_path_getsize, mocked_os_path_isfile):
        mocked_open_return = MagicMock()
        mocked_open_return.read.return_value = 'content'
        mocked_open.return_value = mocked_open_return

        mocked_os_path_isfile.return_value = True
        mocked_os_path_getsize.return_value = 7

        assert self._client.image_identifier('/path/to/file') == '9a0364b9e99bb480dd25e1f0284c8555'
        mocked_os_path_isfile.assert_called_once_with('/path/to/file')
        mocked_os_path_getsize.assert_called_once_with('/path/to/file')

    @patch('os.path.isfile')
    @patch('os.path.getsize')
    @patch('__builtin__.open')
    @raises(ValueError)
    def test_image_identifier_no_file(self, mocked_open, mocked_os_path_getsize, mocked_os_path_isfile):
        # TODO it would be better to move file related actions to a separately tested module
        mocked_open_return = MagicMock()
        mocked_open_return.read.return_value = 'content'
        mocked_open.return_value = mocked_open_return

        mocked_os_path_isfile.return_value = False
        mocked_os_path_getsize.return_value = 0

        self._client.image_identifier('/dev/null/invalid')
        mocked_os_path_isfile.assert_called_once_with('/dev/null/invalid')

    @patch('os.path.isfile')
    @patch('os.path.getsize')
    @patch('__builtin__.open')
    @raises(ValueError)
    def test_image_identifier_empty_file(self, mocked_open, mocked_os_path_getsize, mocked_os_path_isfile):
        mocked_open_return = MagicMock()
        mocked_open_return.read.return_value = 'content'
        mocked_open.return_value = mocked_open_return

        mocked_os_path_isfile.return_value = True
        mocked_os_path_getsize.return_value = 0

        self._client.image_identifier('/dev/null/invalid')
        mocked_os_path_getsize.assert_called_once_with('/dev/null/invalid')

    @patch('imboclient.client.Client._generate_image_identifier')
    def test_image_identifier_from_string(self, mock_generate_image_identifier):
        self._client.image_identifier_from_string('imagestring')
        mock_generate_image_identifier.assert_called_once_with('imagestring')

    @patch('imboclient.url.status.UrlStatus.url')
    @patch('requests.get')
    def test_server_status(self, mocked_requests_get, mocked_url_status):
        class ResponseStub:
            text = '{"statusKey": "statusValue"}'

        mocked_url_status.return_value = 'http://imbo.local/status.json'
        mocked_requests_get.return_value = ResponseStub()

        server_status = self._client.server_status()

        assert server_status['statusKey'] == 'statusValue'
        mocked_url_status.assert_called_once()
        mocked_requests_get.assert_called_once_with(mocked_url_status.return_value)

    @patch('imboclient.url.user.UrlUser.url')
    @patch('requests.get')
    def test_user_info(self, mocked_requests_get, mocked_url_user):
        class ResponseStub:
            text = '{"public": "publickey"}'

        mocked_requests_get.return_value = ResponseStub()
        mocked_url_user.return_value = 'http://imbo.local/users/public'

        user_info = self._client.user_info()
        assert user_info['public'] == 'publickey'

        mocked_url_user.assert_called_once()
        mocked_requests_get.assert_called_once_with(mocked_url_user.return_value)

    def _valid_requests_response_stub_ok(self):
        class ResponseStub:
            status_code = requests.codes.ok

        return ResponseStub()

    def _invalid_requests_response_stub_not_found(self):
        class ResponseStub:
            status_code = requests.codes.not_found

        return ResponseStub()

<commit_msg>head_image<commit_after>from mock import patch
from mock import MagicMock
from nose import with_setup
from nose.tools import raises
import requests
import os
import json
from imboclient import client as imbo
from imboclient.url import image, images, status, user, accesstoken
import __builtin__

class TestClient:

    def setup(self):
        self._client = imbo.Client(['http://imbo.local'], 'public', 'private');

    def teardown(self):
        self._client = None

    def test_server_urls_generic(self):
        self._client = imbo.Client(['imbo.local'], 'public', 'private');
        assert self._client.server_urls[0] == 'http://imbo.local'

    def test_server_urls_http(self):
        self._client = imbo.Client(['http://imbo.local'], 'public', 'private');
        assert self._client.server_urls[0] == 'http://imbo.local'

    def test_server_urls_https(self):
        self._client = imbo.Client(['https://imbo.local'], 'public', 'private');
        assert self._client.server_urls[0] == 'https://imbo.local'

    def test_server_urls_port_normal(self):
        self._client = imbo.Client(['http://imbo.local'], 'public', 'private');
        assert self._client.server_urls[0] == 'http://imbo.local'

    def test_server_urls_port_normal_explicit(self):
        self._client = imbo.Client(['http://imbo.local:80'], 'public', 'private');
        assert self._client.server_urls[0] == 'http://imbo.local'

    def test_server_urls_port_ssl(self):
        self._client = imbo.Client(['https://imbo.local:443'], 'public', 'private');
        assert self._client.server_urls[0] == 'https://imbo.local'

    def test_server_urls_port_explicit_without_protocol(self):
        self._client = imbo.Client(['imbo.local:8000'], 'public', 'private');
        assert self._client.server_urls[0] == 'http://imbo.local:8000'

    @patch('imboclient.url.status.UrlStatus')
    def test_status_url(self, mocked_url_status):
        mocked_url_status_instance = mocked_url_status.return_value
        mocked_url_status_instance.url.return_value = 'correctstatusurl'

        status_url = self._client.status_url()
        mocked_url_status_instance.url.assert_called_once()
        assert status_url == 'correctstatusurl'

    @patch('imboclient.url.user.UrlUser')
    def test_user_url(self, mocked_url_user):
        mocked_url_user_instance = mocked_url_user.return_value
        mocked_url_user_instance.url.return_value = 'correctuserurl'

        user_url = self._client.user_url()
        mocked_url_user_instance.url.assert_called_once()
        assert user_url == 'correctuserurl'

    @patch('imboclient.url.images.UrlImages')
    def test_images_url(self, mocked_url_images):
        mocked_url_images_instance = mocked_url_images.return_value
        mocked_url_images_instance.url.return_value = 'correctimagesurl'

        images_url = self._client.images_url()
        mocked_url_images.assert_called_once_with('http://imbo.local', 'public', 'private')
        mocked_url_images_instance.url.assert_called_once()
        assert images_url == 'correctimagesurl'

    @patch('imboclient.url.image.UrlImage')
    def test_image_url(self, mocked_url_image):
        mocked_url_image_instance = mocked_url_image.return_value
        mocked_url_image_instance.url.return_value = 'correctimageurl'

        image_url = self._client.image_url('ff')
        mocked_url_image.assert_called_once_with('http://imbo.local', 'public', 'private', 'ff')
        mocked_url_image_instance.url.assert_called_once()
        assert image_url == 'correctimageurl'

    @patch('imboclient.url.signed.Signed.str')
    @patch('requests.put')
    @patch('os.path.isfile')
    @patch('os.path.getsize')
    @patch('__builtin__.open')
    def test_add_image(self, mocked_open, mocked_os_path_getsize, mocked_os_path_isfile, mocked_requests_put, mocked_url_signature):
        mocked_open_return = MagicMock()
        mocked_open_return.read.return_value = 'content'
        mocked_open.return_value = mocked_open_return

        mocked_os_path_isfile.return_value = True
        mocked_os_path_getsize.return_value = 7

        mocked_url_signature.return_value = 'signedurl'

        result = self._client.add_image('/mocked/image/path.jpg')
        mocked_requests_put.assert_called_once_with('signedurl', 'content')

    @patch('requests.put')
    @patch('imboclient.client.Client._signed_url')
    @patch('imboclient.client.Client.image_url')
    @patch('imboclient.client.Client.image_identifier_from_string')
    def test_add_image_from_string(self, mock_image_identifier_from_string, mock_image_url, mock_signed_url, mock_requests_put):
        mock_image_identifier_from_string.return_value = 'imageidentifier'
        mock_image_url.return_value = 'imageurl'
        mock_signed_url.return_value = 'signedurl'

        self._client.add_image_from_string('imagestring')

        mock_requests_put.assert_called_once_with('signedurl', 'imagestring')

    @patch('imboclient.client.Client.add_image_from_string')
    @patch('requests.get')
    @patch('imboclient.url.image.UrlImage.url')
    def test_add_image_from_url(self, mock_url_url, mock_requests_get, mock_add_image_from_string):
        mock_url_url.return_value = 'avalidurlstring'
        mock_requests_get.return_value = 'imagedatafromhttp'

        self._client.add_image_from_url(image.UrlImage("", "", "", ""))
        mock_requests_get.assert_called_once_with('avalidurlstring')
        mock_add_image_from_string.assert_called_once_with('avalidurlstring', 'imagedatafromhttp')

    @patch('imboclient.client.Client.image_identifier')
    @patch('imboclient.client.Client.image_identifier_exists')
    def test_image_exists(self, mock_image_identifier_exists, mock_image_identifier):
        mock_image_identifier.return_value = 'identifier'
        mock_image_identifier_exists.return_value = True

        image_exists = self._client.image_exists('/dummy/path')

        assert image_exists == True
        mock_image_identifier_exists.assert_called_once_with('identifier')
        mock_image_identifier.assert_called_once_with('/dummy/path')

    @patch('requests.head')
    @patch('imboclient.url.image.UrlImage')
    def test_image_identifier_exists_true(self, mocked_url_image, mocked_requests_head) :
        mocked_requests_head.return_value = self._valid_requests_response_stub_ok()
        mocked_url_image_instance = mocked_url_image.return_value
        mocked_url_image_instance.url.return_value = 'http://imbo.local/users/public/ff?accessToken=aa'

        assert self._client.image_identifier_exists('ff') == True
        mocked_requests_head.assert_called_once_with('http://imbo.local/users/public/ff?accessToken=aa')
        mocked_url_image.assert_called_once_with('http://imbo.local', 'public', 'private', 'ff')

    @patch('requests.head')
    @patch('imboclient.url.image.UrlImage')
    def test_image_identifier_exists_false(self, mocked_url_image, mocked_requests_get):
        mocked_requests_get.return_value = self._invalid_requests_response_stub_not_found()
        mocked_url_image_instance = mocked_url_image.return_value
        mocked_url_image_instance.url.return_value = 'http://imbo.local/users/public/ffa?accessToken=aaf'

        assert self._client.image_identifier_exists('ffa') == False
        mocked_requests_get.assert_called_once_with('http://imbo.local/users/public/ffa?accessToken=aaf')
        mocked_url_image.assert_called_once_with('http://imbo.local', 'public', 'private', 'ffa')

    @patch('imboclient.client.Client.image_url')
    @patch('requests.head')
    def test_head_image(self, mock_requests_head, mock_image_url):
        mock_image_url.return_value = "imageurl"
        self._client.head_image("imageidentifier")
        mock_image_url.assert_called_once_with("imageidentifier")
        mock_requests_head.assert_called_once_with("imageurl")

    def test_delete_image(self):
        raise NotImplementedError("Test missing")

    def test_edit_metadata(self):
        raise NotImplementedError("Test missing")

    def test_replace_metadata(self):
        raise NotImplementedError("Test missing")

    def test_delete_metadata(self):
        raise NotImplementedError("Test missing")

    @patch('requests.get')
    @patch('imboclient.url.user.UrlUser.url')
    def test_num_images(self, mocked_url_user, mocked_requests_get):
        class StubResponse:
            text = '{"numImages": 2}'

        mocked_url_user.return_value = 'http://imbo.local/users/public'
        mocked_requests_get.return_value = StubResponse()

        num_images = self._client.num_images()

        mocked_url_user.resource_url.assert_called_once()
        mocked_requests_get.assert_called_once_with(mocked_url_user.return_value)

        assert num_images == 2

    @patch('imboclient.url.imagesquery.Query')
    @patch('imboclient.url.images.UrlImages.url')
    @patch('imboclient.url.images.UrlImages.add_query')
    @patch('requests.get')
    def test_images(self, mock_requests_get, mock_url_images_addquery, mock_url_images, mock_imagesquery):
        class StubResponse:
            text = '[{"key": "value"}]'

        mock_url_images.return_value = 'http://imbo.local/public/images.json'
        mock_requests_get.return_value = StubResponse()

        images = self._client.images(mock_imagesquery)
        assert len(images) == 1

        mock_requests_get.assert_called_once_with(mock_url_images.return_value)
        mock_url_images_addquery.assert_called_once_with(mock_imagesquery)

    def test_image_data(self):
        raise NotImplementedError("Test missing")

    def test_image_data_from_url(self):
        raise NotImplementedError("Test missing")

    def test_image_properties_from_url(self):
        raise NotImplementedError("Test missing")

    def test_image_properties(self):
        raise NotImplementedError("Test missing")

    @patch('os.path.isfile')
    @patch('os.path.getsize')
    @patch('__builtin__.open')
    def test_image_identifier(self, mocked_open, mocked_os_path_getsize, mocked_os_path_isfile):
        mocked_open_return = MagicMock()
        mocked_open_return.read.return_value = 'content'
        mocked_open.return_value = mocked_open_return

        mocked_os_path_isfile.return_value = True
        mocked_os_path_getsize.return_value = 7

        assert self._client.image_identifier('/path/to/file') == '9a0364b9e99bb480dd25e1f0284c8555'
        mocked_os_path_isfile.assert_called_once_with('/path/to/file')
        mocked_os_path_getsize.assert_called_once_with('/path/to/file')

    @patch('os.path.isfile')
    @patch('os.path.getsize')
    @patch('__builtin__.open')
    @raises(ValueError)
    def test_image_identifier_no_file(self, mocked_open, mocked_os_path_getsize, mocked_os_path_isfile):
        # TODO it would be better to move file related actions to a separately tested module
        mocked_open_return = MagicMock()
        mocked_open_return.read.return_value = 'content'
        mocked_open.return_value = mocked_open_return

        mocked_os_path_isfile.return_value = False
        mocked_os_path_getsize.return_value = 0

        self._client.image_identifier('/dev/null/invalid')
        mocked_os_path_isfile.assert_called_once_with('/dev/null/invalid')

    @patch('os.path.isfile')
    @patch('os.path.getsize')
    @patch('__builtin__.open')
    @raises(ValueError)
    def test_image_identifier_empty_file(self, mocked_open, mocked_os_path_getsize, mocked_os_path_isfile):
        mocked_open_return = MagicMock()
        mocked_open_return.read.return_value = 'content'
        mocked_open.return_value = mocked_open_return

        mocked_os_path_isfile.return_value = True
        mocked_os_path_getsize.return_value = 0

        self._client.image_identifier('/dev/null/invalid')
        mocked_os_path_getsize.assert_called_once_with('/dev/null/invalid')

    @patch('imboclient.client.Client._generate_image_identifier')
    def test_image_identifier_from_string(self, mock_generate_image_identifier):
        self._client.image_identifier_from_string('imagestring')
        mock_generate_image_identifier.assert_called_once_with('imagestring')

    @patch('imboclient.url.status.UrlStatus.url')
    @patch('requests.get')
    def test_server_status(self, mocked_requests_get, mocked_url_status):
        class ResponseStub:
            text = '{"statusKey": "statusValue"}'

        mocked_url_status.return_value = 'http://imbo.local/status.json'
        mocked_requests_get.return_value = ResponseStub()

        server_status = self._client.server_status()

        assert server_status['statusKey'] == 'statusValue'
        mocked_url_status.assert_called_once()
        mocked_requests_get.assert_called_once_with(mocked_url_status.return_value)

    @patch('imboclient.url.user.UrlUser.url')
    @patch('requests.get')
    def test_user_info(self, mocked_requests_get, mocked_url_user):
        class ResponseStub:
            text = '{"public": "publickey"}'

        mocked_requests_get.return_value = ResponseStub()
        mocked_url_user.return_value = 'http://imbo.local/users/public'

        user_info = self._client.user_info()
        assert user_info['public'] == 'publickey'

        mocked_url_user.assert_called_once()
        mocked_requests_get.assert_called_once_with(mocked_url_user.return_value)

    def _valid_requests_response_stub_ok(self):
        class ResponseStub:
            status_code = requests.codes.ok

        return ResponseStub()

    def _invalid_requests_response_stub_not_found(self):
        class ResponseStub:
            status_code = requests.codes.not_found

        return ResponseStub()

<|endoftext|>


<commit_before>import os
from functools import wraps
from geopy.distance import distance

from pattern.web import DOM
from pattern.web import download
from pattern.web import HTTP404NotFound
from pattern.web import URL
from pattern.web import URLTimeout

from flask import Flask
from flask.ext import restful
from flask.ext.restful import reqparse

DEBUG = bool(os.environ.get("DEBUG", False))

app = Flask(__name__)
api = restful.Api(app)
parser = reqparse.RequestParser()
parser.add_argument('direction', type=str, required=False,
                    help='Direction of the route (north, east, south, west)')
parser.add_argument('stop', type=int, required=False,
                    help='Stop number')
parser.add_argument('latitude', type=float, required=False,
                    help='Latitude to sort results by')
parser.add_argument('longitude', type=float, required=False,
                    help='Longitude to sort results by')

def cors(func, allow_origin=None, allow_headers=None, max_age=None):
    if not allow_origin:
        allow_origin = "*"
    if not allow_headers:
        allow_headers = "content-type, accept"
    if not max_age:
        max_age = 60

    @wraps(func)
    def wrapper(*args, **kwargs):
        response = func(*args, **kwargs)
        cors_headers = {
            "Access-Control-Allow-Origin": allow_origin,
            "Access-Control-Allow-Methods": func.__name__.upper(),
            "Access-Control-Allow-Headers": allow_headers,
            "Access-Control-Max-Age": max_age,
        }
        if isinstance(response, tuple):
            if len(response) == 3:
                headers = response[-1]
            else:
                headers = {}
            headers.update(cors_headers)
            return (response[0], response[1], headers)
        else:
            return response, 200, cors_headers
    return wrapper

class Resource(restful.Resource):
    method_decorators = [cors]

class LondonTransitComission(Resource):

    @classmethod
    def resource(cls):
        return cls, '/'

    def get(self):
        args_text = []
        for arg in parser.args:
            arg_text = {
                "name": arg.name,
                "type": arg.type.__name__,
                "help": arg.help,
                "required": arg.required,
            }
            args_text.append(arg_text)
        return {
            'message': 'Welcome to the London Transit Comission API',
            'status': 200,
            'resources': [{
                "resource": repr(LondonTransitComission),
                "endpoints": LondonTransitComission.resource()[1:],
            }, {
                "resource": repr(RoutesList),
                "endpoints": RoutesList.resource()[1:],
                "params": args_text,
            }, {
                "resource": repr(Routes),
                "endpoints": Routes.resource()[1:],
                "params": args_text,
            }],
        }, 200

class RoutesList(Resource):

    @classmethod
    def resource(cls):
        return cls, '/routes', '/routes/'

    def get(self):
        url = URL("http://www.ltconline.ca/WebWatch/ada.aspx")
        try:
            dom = DOM(url.download(cached=True))
        except (HTTP404NotFound, URLTimeout):
            return {
                "message": "LTC WebWatch service looks down",
                "status": 408,
            }, 408
        routes = {}
        for a in dom("a.ada"):
            a_split = a.content.split(",")
            routes.update({
                a_split[0].strip(): a.content.replace(", ", " - ").title(),
            })
        return routes

class Routes(Resource):

    @classmethod
    def resource(cls):
        return cls, '/routes/<string:route>', '/routes/<string:route>/'

    def get(self, route):
        args = parser.parse_args()
        url = "http://www.ltconline.ca/WebWatch/UpdateWebMap.aspx?u={}"
        try:
            html = download(url.format(route), timeout=60, cached=False)
        except (HTTP404NotFound, URLTimeout) as ex:
            msg = "LTC WebWatch service looks down ({})"
            return {
                "message": msg.format(repr(ex)),
                "status": 408,
            }, 408
        timestamp, main_stops, info_text, minor_stops = html.split("*")
        stops_lines = (main_stops + minor_stops).split(";")
        stops = []
        for stop_line in stops_lines:
            stop_line_split = stop_line.split("|")
            if len(stop_line_split) == 7:
                (latitude, longitude, name, direction,
                 stop_number, times_text) = stop_line_split[:6]
                stop_number = int(stop_number.replace("Stop Number ", ""))
                times = []
                for time_text in times_text.split("
"):
                    time_text_splits = time_text.split(" TO ")
                    if len(time_text_splits) == 2:
                        time, destination = time_text_splits
                        times.append({
                            "time": time,
                            "destination": destination.title(),
                        })
                direction = direction.lower()
                if ((not args["stop"] or args["stop"] == stop_number) and
                    (not args["direction"]
                     or (direction.startswith(args["direction"].lower())
                         or args["direction"].lower() == direction))):
                    stop = {
                        "latitude": float(latitude),
                        "longitude": float(longitude),
                        "name": name.title(),
                        "direction": direction.title(),
                        "stop_number": stop_number,
                        "times": times,
                    }
                    if args["latitude"] and args["longitude"]:
                        stop_location = stop["latitude"], stop["longitude"]
                        request_location = args["latitude"], args["longitude"]
                        stop.update({
                            "distance_in_meters": distance(stop_location,
                                                           request_location).m,
                        })
                    stops.append(stop)
        if stops and args["latitude"] and args["longitude"]:
            stops.sort(key=lambda x: x["distance_in_meters"])
        return stops

api.add_resource(*LondonTransitComission.resource())
api.add_resource(*RoutesList.resource())
api.add_resource(*Routes.resource())

if __name__ == '__main__':
    app.run(debug=DEBUG)
<commit_msg>Fixed conflict<commit_after>import os
from functools import wraps
from geopy.distance import distance

from pattern.web import DOM
from pattern.web import download
from pattern.web import HTTP404NotFound
from pattern.web import URL
from pattern.web import URLTimeout

from flask import Flask
from flask.ext import restful
from flask.ext.restful import reqparse

DEBUG = bool(os.environ.get("DEBUG", False))

app = Flask(__name__)
api = restful.Api(app)
parser = reqparse.RequestParser()
parser.add_argument('direction', type=str, required=False,
                    help='Direction of the route (north, east, south, west)')
parser.add_argument('stop', type=int, required=False,
                    help='Stop number')
parser.add_argument('latitude', type=float, required=False,
                    help='Latitude to sort results by')
parser.add_argument('longitude', type=float, required=False,
                    help='Longitude to sort results by')

def cors(func, allow_origin=None, allow_headers=None, max_age=None):
    if not allow_origin:
        allow_origin = "*"
    if not allow_headers:
        allow_headers = "content-type, accept"
    if not max_age:
        max_age = 60

    @wraps(func)
    def wrapper(*args, **kwargs):
        response = func(*args, **kwargs)
        cors_headers = {
            "Access-Control-Allow-Origin": allow_origin,
            "Access-Control-Allow-Methods": func.__name__.upper(),
            "Access-Control-Allow-Headers": allow_headers,
            "Access-Control-Max-Age": max_age,
        }
        if isinstance(response, tuple):
            if len(response) == 3:
                headers = response[-1]
            else:
                headers = {}
            headers.update(cors_headers)
            return (response[0], response[1], headers)
        else:
            return response, 200, cors_headers
    return wrapper

class Resource(restful.Resource):
    method_decorators = [cors]

class LondonTransitComission(Resource):

    @classmethod
    def resource(cls):
        return cls, '/'

    def get(self):
        args_text = []
        for arg in parser.args:
            arg_text = {
                "name": arg.name,
                "type": arg.type.__name__,
                "help": arg.help,
                "required": arg.required,
            }
            args_text.append(arg_text)
        return {
            'message': 'Welcome to the London Transit Comission API',
            'status': 200,
            'resources': [{
                "resource": repr(LondonTransitComission),
                "endpoints": LondonTransitComission.resource()[1:],
            }, {
                "resource": repr(RoutesList),
                "endpoints": RoutesList.resource()[1:],
                "params": args_text,
            }, {
                "resource": repr(Routes),
                "endpoints": Routes.resource()[1:],
                "params": args_text,
            }],
        }, 200

class RoutesList(Resource):

    @classmethod
    def resource(cls):
        return cls, '/routes', '/routes/'

    def get(self):
        url = URL("http://www.ltconline.ca/WebWatch/ada.aspx")
        try:
            dom = DOM(url.download(cached=True))
        except (HTTP404NotFound, URLTimeout):
            return {
                "message": "LTC WebWatch service looks down",
                "status": 408,
            }, 408
        routes = {}
        for a in dom("a.ada"):
            a_split = a.content.split(",")
            routes.update({
                a_split[0].strip(): a.content.replace(", ", " - ").title(),
            })
        return routes

class Routes(Resource):

    @classmethod
    def resource(cls):
        return cls, '/routes/<string:route>', '/routes/<string:route>/'

    def get(self, route):
        parser = reqparse.RequestParser()
        parser.add_argument('direction', type=str, required=False,
                            help='Direction of the route')
        parser.add_argument('stop', type=int, required=False,
                            help='Stop number')
        parser.add_argument('latitude', type=float, required=False,
                            help='Latitude to sort results by')
        parser.add_argument('longitude', type=float, required=False,
                            help='Longitude to sort results by')
        args = parser.parse_args()
        url = "http://www.ltconline.ca/WebWatch/UpdateWebMap.aspx?u={}"
        try:
            html = download(url.format(route), timeout=60, cached=False)
        except (HTTP404NotFound, URLTimeout) as ex:
            msg = "LTC WebWatch service looks down ({})"
            return {
                "message": msg.format(repr(ex)),
                "status": 408,
            }, 408
        timestamp, main_stops, info_text, minor_stops = html.split("*")
        stops_lines = (main_stops + minor_stops).split(";")
        stops = []
        for stop_line in stops_lines:
            stop_line_split = stop_line.split("|")
            if len(stop_line_split) == 7:
                (latitude, longitude, name, direction,
                 stop_number, times_text) = stop_line_split[:6]
                stop_number = int(stop_number.replace("Stop Number ", ""))
                times = []
                for time_text in times_text.split("
"):
                    time_text_splits = time_text.split(" TO ")
                    if len(time_text_splits) == 2:
                        time, destination = time_text_splits
                        times.append({
                            "time": time,
                            "destination": destination.title(),
                        })
                direction = direction.lower()
                if ((not args["stop"] or args["stop"] == stop_number) and
                    (not args["direction"]
                     or (direction.startswith(args["direction"].lower())
                         or args["direction"].lower() == direction))):
                    stop = {
                        "latitude": float(latitude),
                        "longitude": float(longitude),
                        "name": name.title(),
                        "direction": direction.title(),
                        "stop_number": stop_number,
                        "times": times,
                    }
                    if args["latitude"] and args["longitude"]:
                        stop_location = stop["latitude"], stop["longitude"]
                        request_location = args["latitude"], args["longitude"]
                        stop.update({
                            "distance_in_meters": distance(stop_location,
                                                           request_location).m,
                        })
                    stops.append(stop)
        if stops and args["latitude"] and args["longitude"]:
            stops.sort(key=lambda x: x["distance_in_meters"])
        return stops

api.add_resource(*LondonTransitComission.resource())
api.add_resource(*RoutesList.resource())
api.add_resource(*Routes.resource())

if __name__ == '__main__':
    app.run(debug=DEBUG)
<|endoftext|>


<commit_before>#! /usr/bin/python
# -*- coding: utf8 -*-

from __future__ import absolute_import, division, print_function

import os
import logging
import networkx as nx

from tldp.utils import which, firstfoundfile
from tldp.utils import arg_isexecutable, isexecutable
from tldp.utils import arg_isreadablefile, isreadablefile

from tldp.doctypes.common import BaseDoctype, SignatureChecker, depends

logger = logging.getLogger(__name__)

def docbookdsl_finder():
    locations = [
      '/usr/share/sgml/docbook/stylesheet/dsssl/modular/html/docbook.dsl',
      '/usr/share/sgml/docbook/dsssl-stylesheets/html/docbook.dsl',
      ]
    return firstfoundfile(locations)

def ldpdsl_finder():
    locations = [
      '/usr/share/sgml/docbook/stylesheet/dsssl/ldp/ldp.dsl',
      ]
    return firstfoundfile(locations)

class DocbookSGML(BaseDoctype, SignatureChecker):
    formatname = 'DocBook SGML 3.x/4.x'
    extensions = ['.sgml']
    signatures = ['-//Davenport//DTD DocBook V3.0//EN',
                  '-//OASIS//DTD DocBook V3.1//EN',
                  '-//OASIS//DTD DocBook V4.1//EN',
                  '-//OASIS//DTD DocBook V4.2//EN', ]

    required = {'docbooksgml_jw': isexecutable,
                'docbooksgml_openjade': isexecutable,
                'docbooksgml_dblatex': isexecutable,
                'docbooksgml_html2text': isexecutable,
                'docbooksgml_collateindex': isexecutable,
                'docbooksgml_ldpdsl': isreadablefile,
                'docbooksgml_docbookdsl': isreadablefile,
                }

    graph = nx.DiGraph()

    buildorder = ['buildall']

    def chdir_output(self):
        os.chdir(self.output.dirname)
        return True

    @depends(graph, chdir_output)
    def make_blank_indexsgml(self):
        '''generate an empty index.sgml file (in output dir)'''
        s = '''"{config.docbooksgml_collateindex}" \\
                  -N \\
                  -o \\
                  "index.sgml"'''
        return self.shellscript(s)

    @depends(graph, make_blank_indexsgml)
    def make_data_indexsgml(self):
        '''collect document's index entries into a data file (HTML.index)'''
        s = '''"{config.docbooksgml_openjade}" \\
                  -t sgml \\
                  -V html-index \\
                  -d "{config.docbooksgml_docbookdsl}" \\
                  "{source.filename}"'''
        return self.shellscript(s)

    @depends(graph, make_data_indexsgml)
    def make_indexsgml(self):
        '''generate the final document index file (index.sgml)'''
        s = '''"{config.docbooksgml_collateindex}" \\
                  -g \\
                  -t Index \\
                  -i doc-index \\
                  -o "index.sgml" \\
                     "HTML.index" \\
                     "{source.filename}"'''
        return self.shellscript(s)

    @depends(graph, make_indexsgml)
    def move_indexsgml_into_source(self):
        '''move the generated index.sgml file into the source tree'''
        indexsgml = os.path.join(self.source.dirname, 'index.sgml')
        s = '''mv \\
                 --no-clobber \\
                 --verbose \\
                 -- "index.sgml" "{source.dirname}/index.sgml"'''
        moved = self.shellscript(s)
        if moved:
            self.removals = indexsgml
            logger.debug("%s created %s", self.source.stem, indexsgml)
            return True
        return os.path.exists(indexsgml)

    @depends(graph, move_indexsgml_into_source)
    def cleaned_indexsgml(self):
        '''clean the junk from the output dir after building the index.sgml'''
        # -- be super cautious before removing a bunch of files
        cwd = os.getcwd()
        if not os.path.samefile(cwd, self.output.dirname):
            logger.error("%s (cowardly) refusing to clean directory %s", cwd)
            logger.error("%s expected to find %s", self.output.dirname)
            return False
        s = '''find . -mindepth 1 -maxdepth 1 -type f -print0 \
               | xargs --null --no-run-if-empty -- rm -f --'''
        return self.shellscript(s)

    @depends(graph, cleaned_indexsgml)
    def make_htmls(self):
        '''create a single page HTML output (with incorrect name)'''
        s = '''"{config.docbooksgml_jw}" \\
                  -f docbook \\
                  -b html \\
                  --dsl "{config.docbooksgml_ldpdsl}#html" \\
                  -V nochunks \\
                  -V '%callout-graphics-path%=images/callouts/' \\
                  -V '%stock-graphics-extension%=.png' \\
                  --output . \\
                  "{source.filename}"'''
        return self.shellscript(s)

    @depends(graph, make_htmls)
    def make_name_htmls(self):
        '''correct the single page HTML output name'''
        s = 'mv -v --no-clobber -- "{output.name_html}" "{output.name_htmls}"'
        return self.shellscript(s)

    @depends(graph, make_name_htmls)
    def make_name_txt(self):
        '''create text output (from single-page HTML)'''
        s = '''"{config.docbooksgml_html2text}" > "{output.name_txt}" \\
                  -style pretty \\
                  -nobs \\
                  "{output.name_htmls}"'''
        return self.shellscript(s)

    def make_pdf_with_jw(self):
        s = '''"{config.docbooksgml_jw}" \\
                  -f docbook \\
                  -b pdf \\
                  --output . \\
                  "{source.filename}"'''
        return self.shellscript(s)

    def make_pdf_with_dblatex(self):
        s = '''"{config.docbooksgml_dblatex}" \\
                  -F sgml \\
                  -t pdf \\
                  -o "{output.name_pdf}" \\
                     "{source.filename}"'''
        return self.shellscript(s)

    @depends(graph, cleaned_indexsgml)
    def make_name_pdf(self):
        if self.make_pdf_with_jw():
             return True
        return self.make_pdf_with_dblatex()

    @depends(graph, make_name_htmls)
    def make_html(self):
        '''create final index.html symlink'''
        s = '''"{config.docbooksgml_jw}" \\
                 -f docbook \\
                 -b html \\
                 --dsl "{config.docbooksgml_ldpdsl}#html" \\
                 -V '%callout-graphics-path%=images/callouts/' \\
                 -V '%stock-graphics-extension%=.png' \\
                 --output . \\
                 "{source.filename}"'''
        return self.shellscript(s)

    @depends(graph, make_html)
    def make_name_html(self):
        '''rename openjade's index.html to LDP standard name STEM.html'''
        s = 'mv -v --no-clobber -- "{output.name_indexhtml}" "{output.name_html}"'
        return self.shellscript(s)

    @depends(graph, make_name_html)
    def make_name_indexhtml(self):
        '''create final index.html symlink'''
        s = 'ln -svr -- "{output.name_html}" "{output.name_indexhtml}"'
        return self.shellscript(s)

    @staticmethod
    def argparse(p):
        p.add_argument('--docbooksgml-docbookdsl', type=arg_isreadablefile,
                       default=docbookdsl_finder(),
                       help='full path to html/docbook.dsl [%(default)s]')
        p.add_argument('--docbooksgml-ldpdsl', type=arg_isreadablefile,
                       default=ldpdsl_finder(),
                       help='full path to ldp/ldp.dsl [%(default)s]')
        p.add_argument('--docbooksgml-jw', type=arg_isexecutable,
                       default=which('jw'),
                       help='full path to jw [%(default)s]')
        p.add_argument('--docbooksgml-html2text', type=arg_isexecutable,
                       default=which('html2text'),
                       help='full path to html2text [%(default)s]')
        p.add_argument('--docbooksgml-openjade', type=arg_isexecutable,
                       default=which('openjade'),
                       help='full path to openjade [%(default)s]')
        p.add_argument('--docbooksgml-dblatex', type=arg_isexecutable,
                       default=which('dblatex'),
                       help='full path to dblatex [%(default)s]')
        p.add_argument('--docbooksgml-collateindex', type=arg_isexecutable,
                       default=which('collateindex'),
                       help='full path to collateindex [%(default)s]')

#
# -- end of file
<commit_msg>better PDF generation logic; add cleanup()<commit_after>#! /usr/bin/python
# -*- coding: utf8 -*-

from __future__ import absolute_import, division, print_function

import os
import errno
import logging
import networkx as nx

from tldp.utils import which, firstfoundfile
from tldp.utils import arg_isexecutable, isexecutable
from tldp.utils import arg_isreadablefile, isreadablefile

from tldp.doctypes.common import BaseDoctype, SignatureChecker, depends

logger = logging.getLogger(__name__)

def docbookdsl_finder():
    locations = [
      '/usr/share/sgml/docbook/stylesheet/dsssl/modular/html/docbook.dsl',
      '/usr/share/sgml/docbook/dsssl-stylesheets/html/docbook.dsl',
      ]
    return firstfoundfile(locations)

def ldpdsl_finder():
    locations = [
      '/usr/share/sgml/docbook/stylesheet/dsssl/ldp/ldp.dsl',
      ]
    return firstfoundfile(locations)

class DocbookSGML(BaseDoctype, SignatureChecker):
    formatname = 'DocBook SGML 3.x/4.x'
    extensions = ['.sgml']
    signatures = ['-//Davenport//DTD DocBook V3.0//EN',
                  '-//OASIS//DTD DocBook V3.1//EN',
                  '-//OASIS//DTD DocBook V4.1//EN',
                  '-//OASIS//DTD DocBook V4.2//EN', ]

    required = {'docbooksgml_jw': isexecutable,
                'docbooksgml_openjade': isexecutable,
                'docbooksgml_dblatex': isexecutable,
                'docbooksgml_html2text': isexecutable,
                'docbooksgml_collateindex': isexecutable,
                'docbooksgml_ldpdsl': isreadablefile,
                'docbooksgml_docbookdsl': isreadablefile,
                }

    graph = nx.DiGraph()

    buildorder = ['buildall']

    def cleanup(self):
        stem = self.source.stem
        removals = getattr(self, 'removals', None)
        if removals:
            for fn in removals:
                logger.debug("%s cleaning up file %s", stem, fn)
                try:
                    os.unlink(fn)
                except OSError as e:
                    if e.errno is errno.ENOENT:
                        logger.error("%s missing file at cleanup %s", stem, fn)
                    else:
                        raise e

    def hook_build_success(self):
        self.cleanup()

    def hook_build_failure(self):
        self.cleanup()

    def chdir_output(self):
        os.chdir(self.output.dirname)
        return True

    @depends(graph, chdir_output)
    def make_blank_indexsgml(self):
        '''generate an empty index.sgml file (in output dir)'''
        s = '''"{config.docbooksgml_collateindex}" \\
                  -N \\
                  -o \\
                  "index.sgml"'''
        return self.shellscript(s)

    @depends(graph, make_blank_indexsgml)
    def make_data_indexsgml(self):
        '''collect document's index entries into a data file (HTML.index)'''
        s = '''"{config.docbooksgml_openjade}" \\
                  -t sgml \\
                  -V html-index \\
                  -d "{config.docbooksgml_docbookdsl}" \\
                  "{source.filename}"'''
        return self.shellscript(s)

    @depends(graph, make_data_indexsgml)
    def make_indexsgml(self):
        '''generate the final document index file (index.sgml)'''
        s = '''"{config.docbooksgml_collateindex}" \\
                  -g \\
                  -t Index \\
                  -i doc-index \\
                  -o "index.sgml" \\
                     "HTML.index" \\
                     "{source.filename}"'''
        return self.shellscript(s)

    @depends(graph, make_indexsgml)
    def move_indexsgml_into_source(self):
        '''move the generated index.sgml file into the source tree'''
        indexsgml = os.path.join(self.source.dirname, 'index.sgml')
        s = '''mv \\
                 --no-clobber \\
                 --verbose \\
                 -- "index.sgml" "{source.dirname}/index.sgml"'''
        moved = self.shellscript(s)
        if moved:
            logger.debug("%s created %s", self.source.stem, indexsgml)
            self.removals = [indexsgml]
            return True
        return os.path.exists(indexsgml)

    @depends(graph, move_indexsgml_into_source)
    def cleaned_indexsgml(self):
        '''clean the junk from the output dir after building the index.sgml'''
        # -- be super cautious before removing a bunch of files
        cwd = os.getcwd()
        if not os.path.samefile(cwd, self.output.dirname):
            logger.error("%s (cowardly) refusing to clean directory %s", cwd)
            logger.error("%s expected to find %s", self.output.dirname)
            return False
        s = '''find . -mindepth 1 -maxdepth 1 -type f -print0 \
               | xargs --null --no-run-if-empty -- rm -f --'''
        return self.shellscript(s)

    @depends(graph, cleaned_indexsgml)
    def make_htmls(self):
        '''create a single page HTML output (with incorrect name)'''
        s = '''"{config.docbooksgml_jw}" \\
                  -f docbook \\
                  -b html \\
                  --dsl "{config.docbooksgml_ldpdsl}#html" \\
                  -V nochunks \\
                  -V '%callout-graphics-path%=images/callouts/' \\
                  -V '%stock-graphics-extension%=.png' \\
                  --output . \\
                  "{source.filename}"'''
        return self.shellscript(s)

    @depends(graph, make_htmls)
    def make_name_htmls(self):
        '''correct the single page HTML output name'''
        s = 'mv -v --no-clobber -- "{output.name_html}" "{output.name_htmls}"'
        return self.shellscript(s)

    @depends(graph, make_name_htmls)
    def make_name_txt(self):
        '''create text output (from single-page HTML)'''
        s = '''"{config.docbooksgml_html2text}" > "{output.name_txt}" \\
                  -style pretty \\
                  -nobs \\
                  "{output.name_htmls}"'''
        return self.shellscript(s)

    def make_pdf_with_jw(self):
        s = '''"{config.docbooksgml_jw}" \\
                  -f docbook \\
                  -b pdf \\
                  --output . \\
                  "{source.filename}"'''
        return self.shellscript(s)

    def make_pdf_with_dblatex(self):
        s = '''"{config.docbooksgml_dblatex}" \\
                  -F sgml \\
                  -t pdf \\
                  -o "{output.name_pdf}" \\
                     "{source.filename}"'''
        return self.shellscript(s)

    @depends(graph, cleaned_indexsgml)
    def make_name_pdf(self):
        stem = self.source.stem
        classname = self.__class__.__name__
        logger.info("%s calling method %s.%s",
                    stem, classname, 'make_pdf_with_jw')
        if self.make_pdf_with_jw():
             return True
        logger.error("%s jw failed creating PDF, falling back to dblatex...",
                     stem)
        logger.info("%s calling method %s.%s",
                    stem, classname, 'make_pdf_with_dblatex')
        return self.make_pdf_with_dblatex()

    @depends(graph, make_name_htmls)
    def make_html(self):
        '''create final index.html symlink'''
        s = '''"{config.docbooksgml_jw}" \\
                 -f docbook \\
                 -b html \\
                 --dsl "{config.docbooksgml_ldpdsl}#html" \\
                 -V '%callout-graphics-path%=images/callouts/' \\
                 -V '%stock-graphics-extension%=.png' \\
                 --output . \\
                 "{source.filename}"'''
        return self.shellscript(s)

    @depends(graph, make_html)
    def make_name_html(self):
        '''rename openjade's index.html to LDP standard name STEM.html'''
        s = 'mv -v --no-clobber -- "{output.name_indexhtml}" "{output.name_html}"'
        return self.shellscript(s)

    @depends(graph, make_name_html)
    def make_name_indexhtml(self):
        '''create final index.html symlink'''
        s = 'ln -svr -- "{output.name_html}" "{output.name_indexhtml}"'
        return self.shellscript(s)

    @staticmethod
    def argparse(p):
        p.add_argument('--docbooksgml-docbookdsl', type=arg_isreadablefile,
                       default=docbookdsl_finder(),
                       help='full path to html/docbook.dsl [%(default)s]')
        p.add_argument('--docbooksgml-ldpdsl', type=arg_isreadablefile,
                       default=ldpdsl_finder(),
                       help='full path to ldp/ldp.dsl [%(default)s]')
        p.add_argument('--docbooksgml-jw', type=arg_isexecutable,
                       default=which('jw'),
                       help='full path to jw [%(default)s]')
        p.add_argument('--docbooksgml-html2text', type=arg_isexecutable,
                       default=which('html2text'),
                       help='full path to html2text [%(default)s]')
        p.add_argument('--docbooksgml-openjade', type=arg_isexecutable,
                       default=which('openjade'),
                       help='full path to openjade [%(default)s]')
        p.add_argument('--docbooksgml-dblatex', type=arg_isexecutable,
                       default=which('dblatex'),
                       help='full path to dblatex [%(default)s]')
        p.add_argument('--docbooksgml-collateindex', type=arg_isexecutable,
                       default=which('collateindex'),
                       help='full path to collateindex [%(default)s]')

#
# -- end of file
<|endoftext|>