<commit_before># Copyright 2011 Element 34
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
===============
SaunterTestCase
===============
"""
import ConfigParser
import logging
import os
import os.path
import requests

import saunter.ConfigWrapper
try:
    if saunter.ConfigWrapper.ConfigWrapper().config.getboolean("SauceLabs", "ondemand"):
        import json
except ConfigParser.NoSectionError as e:
    if "DOCGENERATION" not in os.environ:
        raise

try:
    from tailored.webdriver import WebDriver
except ImportError as e:
    if "DOCGENERATION" not in os.environ:
        raise

from selenium.webdriver.common.by import By
from selenium.common.exceptions import WebDriverException
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from saunter.testcase.base import BaseTestCase
from saunter.SaunterWebDriver import SaunterWebDriver
import py.test
from _pytest.mark import MarkInfo

from saunter.matchers import Matchers

capabilities_map = {
    "firefox": DesiredCapabilities.FIREFOX,
    "internet explorer": DesiredCapabilities.INTERNETEXPLORER,
    "internetexplorer": DesiredCapabilities.INTERNETEXPLORER,
    "iexplore": DesiredCapabilities.INTERNETEXPLORER,
    "ie": DesiredCapabilities.INTERNETEXPLORER,
    "chrome": DesiredCapabilities.CHROME,
    "opera": DesiredCapabilities.OPERA,
    "chrome": DesiredCapabilities.CHROME,
    "htmlunitjs": DesiredCapabilities.HTMLUNITWITHJS,
    "htmlunit": DesiredCapabilities.HTMLUNIT,
    "iphone": DesiredCapabilities.IPHONE,
    "ipad": DesiredCapabilities.IPAD,
    "android": DesiredCapabilities.ANDROID
}

os_map = {
    "XP": "XP",
    "Windows 2003": "XP",
    "VISTA": "VISTA",
    "Windows 2008": "VISTA",
    "Linux": "LINUX",
    "LINUX": "LINUX",
    "MAC": "MAC"
}

class SaunterTestCase(BaseTestCase):
    """
    Parent class of all script classes used for custom asserts (usually 'soft' asserts) and shared fixture setup
    and teardown
    """
    def setup_method(self, method):
        """
        Parent class of all script classes used for custom asserts (usually 'soft' asserts) and shared fixture setup
        and teardown
        """
        self.verificationErrors = []
        self.cf = saunter.ConfigWrapper.ConfigWrapper().config
        self.config = self.cf
        if self.cf.getboolean("SauceLabs", "ondemand"):
            desired_capabilities = {
                "platform": self.cf.get("SauceLabs", "os"),
                "browserName": self.cf.get("SauceLabs", "browser"),
                "version": self.cf.get("SauceLabs", "browser_version"),
                "name": self._testMethodName
            }
            if desired_capabilities["browserName"][0] == "*":
                desired_capabilities["browserName"] = desired_capabilities["browserName"][1:]
            if desired_capabilities["platform"] in os_map:
                desired_capabilities["platform"] = os_map[desired_capabilities["platform"]]
            command_executor = "http://%s:%s@ondemand.saucelabs.com:80/wd/hub" % (self.cf.get("SauceLabs", "username"), self.cf.get("SauceLabs", "key"))
        else:
            browser = self.cf.get("Selenium", "browser")
            if browser[0] == "*":
                browser = browser[1:]
            if browser == "chrome":
                os.environ["webdriver.chrome.driver"] = self.cf.get("Selenium", "chromedriver_path")
            desired_capabilities = capabilities_map[browser]
            if self.cf.has_section("Proxy") \
                and self.cf.has_option("Proxy", "proxy_url") \
                and (self.cf.has_option("Proxy", "browsermob") and self.cf.getboolean("Proxy", "browsermob")):
                from browsermobproxy import Client
                self.client = Client(self.cf.get("Proxy", "proxy_url"))
                self.client.add_to_webdriver_capabilities(desired_capabilities)
            if self.cf.has_section("Grid"):
                if self.cf.getboolean("Grid", "use_grid") and self.cf.get("Grid", "type") == "selenium":
                    if self.cf.has_option("Grid", "platform"):
                        desired_capabilities["platform"] = self.cf.get("Grid", "platform").upper()
                    if self.cf.has_option("Grid", "version"):
                        desired_capabilities["version"] = str(self.cf.get("Grid", "browser_version"))

            command_executor = "http://%s:%s/wd/hub" % (self.cf.get("Selenium", "server_host"), self.cf.get("Selenium", "server_port"))
        self.driver = WebDriver(desired_capabilities = desired_capabilities, command_executor = command_executor)

        self.verificationErrors = []
        self.matchers = Matchers(self.driver, self.verificationErrors)

        if self.cf.getboolean("SauceLabs", "ondemand"):
            self.sauce_session = self.driver.session_id

    def teardown_method(self, method):
        """
        Default teardown method for all scripts. If run through Sauce Labs OnDemand, the job name, status and tags
        are updated. Also the video and server log are downloaded if so configured.
        """

        if hasattr(self, "driver"):
            self.driver.quit()

        if hasattr(self, "cf") and self.cf.getboolean("SauceLabs", "ondemand"):
            self._saucelabs(method)

    # def fetch_artifact(session, which):
    #     which_url = "https://saucelabs.com/rest/%s/jobs/%s/results/%s" % (self.cf.get("SauceLabs", "username"), session, which)
    #     code = 404
    #     timeout = 0
    #     while code in [401, 404]:
    #         r = requests.get(which_url, auth = (cf.get("SauceLabs", "username"), self.cf.get("SauceLabs", "key")))
    #         try:
    #             code = r.status_code
    #             r.raise_for_status()
    #         except urllib2.HTTPError, e:
    #             time.sleep(4)
    #
    #     artifact = open(os.path.join(os.path.dirname(__file__), "logs", which), "wb")
    #     artifact.write(r.content)<commit_msg>setting the the method name in the saunter config object<commit_after># Copyright 2011 Element 34
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
===============
SaunterTestCase
===============
"""
import ConfigParser
import logging
import os
import os.path
import requests

import saunter.ConfigWrapper
try:
    if saunter.ConfigWrapper.ConfigWrapper().config.getboolean("SauceLabs", "ondemand"):
        import json
except ConfigParser.NoSectionError as e:
    if "DOCGENERATION" not in os.environ:
        raise

try:
    from tailored.webdriver import WebDriver
except ImportError as e:
    if "DOCGENERATION" not in os.environ:
        raise

from selenium.webdriver.common.by import By
from selenium.common.exceptions import WebDriverException
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from saunter.testcase.base import BaseTestCase
from saunter.SaunterWebDriver import SaunterWebDriver
import py.test
from _pytest.mark import MarkInfo

from saunter.matchers import Matchers

capabilities_map = {
    "firefox": DesiredCapabilities.FIREFOX,
    "internet explorer": DesiredCapabilities.INTERNETEXPLORER,
    "internetexplorer": DesiredCapabilities.INTERNETEXPLORER,
    "iexplore": DesiredCapabilities.INTERNETEXPLORER,
    "ie": DesiredCapabilities.INTERNETEXPLORER,
    "chrome": DesiredCapabilities.CHROME,
    "opera": DesiredCapabilities.OPERA,
    "chrome": DesiredCapabilities.CHROME,
    "htmlunitjs": DesiredCapabilities.HTMLUNITWITHJS,
    "htmlunit": DesiredCapabilities.HTMLUNIT,
    "iphone": DesiredCapabilities.IPHONE,
    "ipad": DesiredCapabilities.IPAD,
    "android": DesiredCapabilities.ANDROID
}

os_map = {
    "XP": "XP",
    "Windows 2003": "XP",
    "VISTA": "VISTA",
    "Windows 2008": "VISTA",
    "Linux": "LINUX",
    "LINUX": "LINUX",
    "MAC": "MAC"
}

class SaunterTestCase(BaseTestCase):
    """
    Parent class of all script classes used for custom asserts (usually 'soft' asserts) and shared fixture setup
    and teardown
    """
    def setup_method(self, method):
        """
        Parent class of all script classes used for custom asserts (usually 'soft' asserts) and shared fixture setup
        and teardown
        """
        self.verificationErrors = []
        self.cf = saunter.ConfigWrapper.ConfigWrapper().config
        self.cf.set("Saunter", "name", method.__name__)
        self.config = self.cf
        if self.cf.getboolean("SauceLabs", "ondemand"):
            desired_capabilities = {
                "platform": self.cf.get("SauceLabs", "os"),
                "browserName": self.cf.get("SauceLabs", "browser"),
                "version": self.cf.get("SauceLabs", "browser_version"),
                "name": method.__name__
            }
            if desired_capabilities["browserName"][0] == "*":
                desired_capabilities["browserName"] = desired_capabilities["browserName"][1:]
            if desired_capabilities["platform"] in os_map:
                desired_capabilities["platform"] = os_map[desired_capabilities["platform"]]
            command_executor = "http://%s:%s@ondemand.saucelabs.com:80/wd/hub" % (self.cf.get("SauceLabs", "username"), self.cf.get("SauceLabs", "key"))
        else:
            browser = self.cf.get("Selenium", "browser")
            if browser[0] == "*":
                browser = browser[1:]
            if browser == "chrome":
                os.environ["webdriver.chrome.driver"] = self.cf.get("Selenium", "chromedriver_path")
            desired_capabilities = capabilities_map[browser]
            if self.cf.has_section("Proxy") \
                and self.cf.has_option("Proxy", "proxy_url") \
                and (self.cf.has_option("Proxy", "browsermob") and self.cf.getboolean("Proxy", "browsermob")):
                from browsermobproxy import Client
                self.client = Client(self.cf.get("Proxy", "proxy_url"))
                self.client.add_to_webdriver_capabilities(desired_capabilities)
            if self.cf.has_section("Grid"):
                if self.cf.getboolean("Grid", "use_grid") and self.cf.get("Grid", "type") == "selenium":
                    if self.cf.has_option("Grid", "platform"):
                        desired_capabilities["platform"] = self.cf.get("Grid", "platform").upper()
                    if self.cf.has_option("Grid", "version"):
                        desired_capabilities["version"] = str(self.cf.get("Grid", "browser_version"))

            command_executor = "http://%s:%s/wd/hub" % (self.cf.get("Selenium", "server_host"), self.cf.get("Selenium", "server_port"))
        self.driver = WebDriver(desired_capabilities = desired_capabilities, command_executor = command_executor)

        self.verificationErrors = []
        self.matchers = Matchers(self.driver, self.verificationErrors)

        if self.cf.getboolean("SauceLabs", "ondemand"):
            self.sauce_session = self.driver.session_id

    def teardown_method(self, method):
        """
        Default teardown method for all scripts. If run through Sauce Labs OnDemand, the job name, status and tags
        are updated. Also the video and server log are downloaded if so configured.
        """

        if hasattr(self, "driver"):
            self.driver.quit()

        if hasattr(self, "cf") and self.cf.getboolean("SauceLabs", "ondemand"):
            self._saucelabs(method)

    # def fetch_artifact(session, which):
    #     which_url = "https://saucelabs.com/rest/%s/jobs/%s/results/%s" % (self.cf.get("SauceLabs", "username"), session, which)
    #     code = 404
    #     timeout = 0
    #     while code in [401, 404]:
    #         r = requests.get(which_url, auth = (cf.get("SauceLabs", "username"), self.cf.get("SauceLabs", "key")))
    #         try:
    #             code = r.status_code
    #             r.raise_for_status()
    #         except urllib2.HTTPError, e:
    #             time.sleep(4)
    #
    #     artifact = open(os.path.join(os.path.dirname(__file__), "logs", which), "wb")
    #     artifact.write(r.content)<|endoftext|>


<commit_before># By design, pylint: disable=C0302
from __future__ import annotations

import asyncio
import threading
from typing import Any, Callable, Iterable, Optional, TypeVar, Union, cast, overload

from rx.core import abc
from rx.disposable import Disposable
from rx.scheduler import CurrentThreadScheduler
from rx.scheduler.eventloop import AsyncIOScheduler

from ..observer import AutoDetachObserver

_A = TypeVar("_A")
_B = TypeVar("_B")
_C = TypeVar("_C")
_D = TypeVar("_D")
_E = TypeVar("_E")
_F = TypeVar("_F")
_G = TypeVar("_G")

_T = TypeVar("_T")

class Observable(abc.ObservableBase[_T]):
    """Observable base class.

    Represents a push-style collection, which you can :func:`pipe <pipe>` into
    :mod:`operators <rx.operators>`."""

    def __init__(self, subscribe: Optional[abc.Subscription[_T]] = None) -> None:
        """Creates an observable sequence object from the specified
        subscription function.

        Args:
            subscribe: [Optional] Subscription function
        """
        super().__init__()

        self.lock = threading.RLock()
        self._subscribe = subscribe

    def _subscribe_core(
        self,
        observer: abc.ObserverBase[_T],
        scheduler: Optional[abc.SchedulerBase] = None,
    ) -> abc.DisposableBase:
        return self._subscribe(observer, scheduler) if self._subscribe else Disposable()

    @overload
    def subscribe(
        self,
        observer: abc.ObserverBase[_T],
        *,
        scheduler: Optional[abc.SchedulerBase] = None,
    ) -> abc.DisposableBase:
        ...

    @overload
    def subscribe(
        self,
        observer: Optional[abc.OnNext[_T]] = None,
        *,
        scheduler: Optional[abc.SchedulerBase] = None,
    ) -> abc.DisposableBase:
        ...

    def subscribe(
        self,
        observer: Union[abc.ObserverBase[_T], abc.OnNext[_T], None] = None,
        on_error: Optional[abc.OnError] = None,
        on_completed: Optional[abc.OnCompleted] = None,
        on_next: Optional[abc.OnNext[_T]] = None,
        *,
        scheduler: Optional[abc.SchedulerBase] = None,
    ) -> abc.DisposableBase:
        """Subscribe an observer to the observable sequence.

        You may subscribe using an observer or callbacks, not both; if the first
        argument is an instance of :class:`Observer <..abc.ObserverBase>` or if
        it has a (callable) attribute named :code:`on_next`, then any callback
        arguments will be ignored.

        Examples:
            >>> source.subscribe()
            >>> source.subscribe(observer)
            >>> source.subscribe(observer, scheduler=scheduler)
            >>> source.subscribe(on_next)
            >>> source.subscribe(on_next, on_error)
            >>> source.subscribe(on_next, on_error, on_completed)
            >>> source.subscribe(on_next, on_error, on_completed, scheduler=scheduler)

        Args:
            observer: [Optional] The object that is to receive
                notifications.
            on_error: [Optional] Action to invoke upon exceptional termination
                of the observable sequence.
            on_completed: [Optional] Action to invoke upon graceful termination
                of the observable sequence.
            on_next: [Optional] Action to invoke for each element in the
                observable sequence.
            scheduler: [Optional] The default scheduler to use for this
                subscription.

        Returns:
            Disposable object representing an observer's subscription to
            the observable sequence.
        """
        if observer:
            if (
                isinstance(observer, abc.ObserverBase)
                or hasattr(observer, "on_next")
                and callable(getattr(observer, "on_next"))
            ):
                on_next = cast(abc.ObserverBase[_T], observer).on_next
                on_error = cast(abc.ObserverBase[_T], observer).on_error
                on_completed = cast(abc.ObserverBase[_T], observer).on_completed
            else:
                on_next = observer
        return self.subscribe_(on_next, on_error, on_completed, scheduler=scheduler)

    def subscribe_(
        self,
        on_next: Optional[abc.OnNext[_T]] = None,
        on_error: Optional[abc.OnError] = None,
        on_completed: Optional[abc.OnCompleted] = None,
        scheduler: Optional[abc.SchedulerBase] = None,
    ) -> abc.DisposableBase:
        """Subscribe callbacks to the observable sequence.

        Examples:
            >>> source.subscribe_(on_next)
            >>> source.subscribe_(on_next, on_error)
            >>> source.subscribe_(on_next, on_error, on_completed)

        Args:
            on_next: [Optional] Action to invoke for each element in the
                observable sequence.
            on_error: [Optional] Action to invoke upon exceptional termination
                of the observable sequence.
            on_completed: [Optional] Action to invoke upon graceful termination
                of the observable sequence.
            scheduler: [Optional] The scheduler to use for this subscription.

        Returns:
            Disposable object representing an observer's subscription to
            the observable sequence.
        """

        auto_detach_observer: AutoDetachObserver[_T] = AutoDetachObserver(
            on_next, on_error, on_completed
        )

        def fix_subscriber(
            subscriber: Union[abc.DisposableBase, Callable[[], None]]
        ) -> abc.DisposableBase:
            """Fixes subscriber to make sure it returns a Disposable instead
            of None or a dispose function"""

            if isinstance(subscriber, abc.DisposableBase) or hasattr(
                subscriber, "dispose"
            ):
                # Note: cast can be avoided using Protocols (Python 3.9)
                return cast(abc.DisposableBase, subscriber)

            return Disposable(subscriber)

        def set_disposable(_: Optional[abc.SchedulerBase] = None, __: Any = None):
            try:
                subscriber = self._subscribe_core(auto_detach_observer, scheduler)
            except Exception as ex:  # By design. pylint: disable=W0703
                if not auto_detach_observer.fail(ex):
                    raise
            else:
                auto_detach_observer.subscription = fix_subscriber(subscriber)

        # Subscribe needs to set up the trampoline before for subscribing.
        # Actually, the first call to Subscribe creates the trampoline so
        # that it may assign its disposable before any observer executes
        # OnNext over the CurrentThreadScheduler. This enables single-
        # threaded cancellation
        # https://social.msdn.microsoft.com/Forums/en-US/eb82f593-9684-4e27-
        # 97b9-8b8886da5c33/whats-the-rationale-behind-how-currentthreadsche
        # dulerschedulerequired-behaves?forum=rx
        current_thread_scheduler = CurrentThreadScheduler.singleton()
        if current_thread_scheduler.schedule_required():
            current_thread_scheduler.schedule(set_disposable)
        else:
            set_disposable()

        # Hide the identity of the auto detach observer
        return Disposable(auto_detach_observer.dispose)

    @overload
    def pipe(
        self, *operators: Callable[[Observable[_T]], Observable[_A]]
    ) -> Observable[_A]:  # pylint: disable=no-self-use
        """Compose multiple operators left to right.

        Composes zero or more operators into a functional composition.
        The operators are composed from left to right. A composition of zero
        operators gives back the original source.

        Examples:
            >>> source.pipe() == source
            >>> source.pipe(f) == f(source)
            >>> source.pipe(g, f) == f(g(source))
            >>> source.pipe(h, g, f) == f(g(h(source)))

        Args:
            operators: Sequence of operators.

        Returns:
             The composed observable.
        """
        ...

    @overload
    def pipe(self, __op1: Callable[[Observable[_T]], _A]) -> _A:
        ...

    @overload
    def pipe(
        self,
        __op1: Callable[[Observable[_T]], _A],
        __op2: Callable[[_A], _B],
    ) -> _B:
        ...

    @overload
    def pipe(
        self,
        op1: Callable[[Observable[_T]], _A],
        op2: Callable[[_A], _B],
        op3: Callable[[_B], _C],
    ) -> _C:
        ...

    @overload
    def pipe(
        self,
        op1: Callable[[Observable[_T]], _A],
        op2: Callable[[_A], _B],
        op3: Callable[[_B], _C],
        op4: Callable[[_C], _D],
    ) -> _D:
        ...

    @overload
    def pipe(
        self,
        op1: Callable[[Observable[_T]], _A],
        op2: Callable[[_A], _B],
        op3: Callable[[_B], _C],
        op4: Callable[[_C], _D],
        op5: Callable[[_D], _E],
    ) -> _E:
        ...

    @overload
    def pipe(
        self,  # pylint: disable=function-redefined, no-self-use, too-many-arguments
        op1: Callable[[Observable[_T]], _A],
        op2: Callable[[_A], _B],
        op3: Callable[[_B], _C],
        op4: Callable[[_C], _D],
        op5: Callable[[_D], _E],
        op6: Callable[[_E], _F],
    ) -> _F:
        ...

    @overload
    def pipe(
        self,  # pylint: too-many-arguments
        op1: Callable[[Observable[_T]], _A],
        op2: Callable[[_A], _B],
        op3: Callable[[_B], _C],
        op4: Callable[[_C], _D],
        op5: Callable[[_D], _E],
        op6: Callable[[_E], _F],
        op7: Callable[[_F], _G],
    ) -> _G:
        ...

    def pipe(self, *operators: Callable[[Any], Any]) -> Any:
        """Compose multiple operators left to right.

        Composes zero or more operators into a functional composition.
        The operators are composed from left to right. A composition of zero
        operators gives back the original source.

        Examples:
            >>> source.pipe() == source
            >>> source.pipe(f) == f(source)
            >>> source.pipe(g, f) == f(g(source))
            >>> source.pipe(h, g, f) == f(g(h(source)))

        Args:
            operators: Sequence of operators.

        Returns:
             The composed observable.
        """
        from ..pipe import pipe

        return pipe(*operators)(self)

    def run(self) -> Any:
        """Run source synchronously.

        Subscribes to the observable source. Then blocks and waits for the
        observable source to either complete or error. Returns the
        last value emitted, or throws exception if any error occurred.

        Examples:
            >>> result = run(source)

        Raises:
            SequenceContainsNoElementsError: if observable completes
                (on_completed) without any values being emitted.
            Exception: raises exception if any error (on_error) occurred.

        Returns:
            The last element emitted from the observable.
        """
        from ..run import run

        return run(self)

    def __await__(self) -> Iterable[_T]:
        """Awaits the given observable.

        Returns:
            The last item of the observable sequence.
        """
        from ..operators.tofuture import to_future_

        loop = asyncio.get_event_loop()
        return iter(self.pipe(to_future_(scheduler=AsyncIOScheduler(loop=loop))))

    def __add__(self, other: Observable[_T]) -> Observable[_T]:
        """Pythonic version of :func:`concat <rx.concat>`.

        Example:
            >>> zs = xs + ys

        Args:
            other: The second observable sequence in the concatenation.

        Returns:
            Concatenated observable sequence.
        """
        from rx import concat

        return concat(self, other)

    def __iadd__(self, other: Observable[_T]) -> "Observable[_T]":
        """Pythonic use of :func:`concat <rx.concat>`.

        Example:
            >>> xs += ys

        Args:
            other: The second observable sequence in the concatenation.

        Returns:
            Concatenated observable sequence.
        """
        from rx import concat

        return concat(self, other)

    def __getitem__(self, key: slice) -> Observable[_T]:
        """
        Pythonic version of :func:`slice <rx.operators.slice>`.

        Slices the given observable using Python slice notation. The arguments
        to slice are `start`, `stop` and `step` given within brackets `[]` and
        separated by the colons `:`.

        It is basically a wrapper around the operators
        :func:`skip <rx.operators.skip>`,
        :func:`skip_last <rx.operators.skip_last>`,
        :func:`take <rx.operators.take>`,
        :func:`take_last <rx.operators.take_last>` and
        :func:`filter <rx.operators.filter>`.

        The following diagram helps you remember how slices works with streams.
        Positive numbers are relative to the start of the events, while negative
        numbers are relative to the end (close) of the stream.

        .. code::

            r---e---a---c---t---i---v---e---!
            0   1   2   3   4   5   6   7   8
           -8  -7  -6  -5  -4  -3  -2  -1   0

        Examples:
            >>> result = source[1:10]
            >>> result = source[1:-2]
            >>> result = source[1:-1:2]

        Args:
            key: Slice object

        Returns:
            Sliced observable sequence.

        Raises:
            TypeError: If key is not of type :code:`int` or :code:`slice`
        """

        if isinstance(key, slice):
            start, stop, step = key.start, key.stop, key.step
        elif isinstance(key, int):
            start, stop, step = key, key + 1, 1
        else:
            raise TypeError("Invalid argument type.")

        from ..operators.slice import slice_

        return slice_(start, stop, step)(self)

__all__ = ["Observable"]
<commit_msg>Make pipe arguments anonymous<commit_after># By design, pylint: disable=C0302
from __future__ import annotations

import asyncio
import threading
from typing import Any, Callable, Iterable, Optional, TypeVar, Union, cast, overload

from rx.core import abc
from rx.disposable import Disposable
from rx.scheduler import CurrentThreadScheduler
from rx.scheduler.eventloop import AsyncIOScheduler

from ..observer import AutoDetachObserver

_A = TypeVar("_A")
_B = TypeVar("_B")
_C = TypeVar("_C")
_D = TypeVar("_D")
_E = TypeVar("_E")
_F = TypeVar("_F")
_G = TypeVar("_G")

_T = TypeVar("_T")

class Observable(abc.ObservableBase[_T]):
    """Observable base class.

    Represents a push-style collection, which you can :func:`pipe <pipe>` into
    :mod:`operators <rx.operators>`."""

    def __init__(self, subscribe: Optional[abc.Subscription[_T]] = None) -> None:
        """Creates an observable sequence object from the specified
        subscription function.

        Args:
            subscribe: [Optional] Subscription function
        """
        super().__init__()

        self.lock = threading.RLock()
        self._subscribe = subscribe

    def _subscribe_core(
        self,
        observer: abc.ObserverBase[_T],
        scheduler: Optional[abc.SchedulerBase] = None,
    ) -> abc.DisposableBase:
        return self._subscribe(observer, scheduler) if self._subscribe else Disposable()

    @overload
    def subscribe(
        self,
        observer: abc.ObserverBase[_T],
        *,
        scheduler: Optional[abc.SchedulerBase] = None,
    ) -> abc.DisposableBase:
        ...

    @overload
    def subscribe(
        self,
        observer: Optional[abc.OnNext[_T]] = None,
        *,
        scheduler: Optional[abc.SchedulerBase] = None,
    ) -> abc.DisposableBase:
        ...

    def subscribe(
        self,
        observer: Union[abc.ObserverBase[_T], abc.OnNext[_T], None] = None,
        on_error: Optional[abc.OnError] = None,
        on_completed: Optional[abc.OnCompleted] = None,
        on_next: Optional[abc.OnNext[_T]] = None,
        *,
        scheduler: Optional[abc.SchedulerBase] = None,
    ) -> abc.DisposableBase:
        """Subscribe an observer to the observable sequence.

        You may subscribe using an observer or callbacks, not both; if the first
        argument is an instance of :class:`Observer <..abc.ObserverBase>` or if
        it has a (callable) attribute named :code:`on_next`, then any callback
        arguments will be ignored.

        Examples:
            >>> source.subscribe()
            >>> source.subscribe(observer)
            >>> source.subscribe(observer, scheduler=scheduler)
            >>> source.subscribe(on_next)
            >>> source.subscribe(on_next, on_error)
            >>> source.subscribe(on_next, on_error, on_completed)
            >>> source.subscribe(on_next, on_error, on_completed, scheduler=scheduler)

        Args:
            observer: [Optional] The object that is to receive
                notifications.
            on_error: [Optional] Action to invoke upon exceptional termination
                of the observable sequence.
            on_completed: [Optional] Action to invoke upon graceful termination
                of the observable sequence.
            on_next: [Optional] Action to invoke for each element in the
                observable sequence.
            scheduler: [Optional] The default scheduler to use for this
                subscription.

        Returns:
            Disposable object representing an observer's subscription to
            the observable sequence.
        """
        if observer:
            if (
                isinstance(observer, abc.ObserverBase)
                or hasattr(observer, "on_next")
                and callable(getattr(observer, "on_next"))
            ):
                on_next = cast(abc.ObserverBase[_T], observer).on_next
                on_error = cast(abc.ObserverBase[_T], observer).on_error
                on_completed = cast(abc.ObserverBase[_T], observer).on_completed
            else:
                on_next = observer
        return self.subscribe_(on_next, on_error, on_completed, scheduler=scheduler)

    def subscribe_(
        self,
        on_next: Optional[abc.OnNext[_T]] = None,
        on_error: Optional[abc.OnError] = None,
        on_completed: Optional[abc.OnCompleted] = None,
        scheduler: Optional[abc.SchedulerBase] = None,
    ) -> abc.DisposableBase:
        """Subscribe callbacks to the observable sequence.

        Examples:
            >>> source.subscribe_(on_next)
            >>> source.subscribe_(on_next, on_error)
            >>> source.subscribe_(on_next, on_error, on_completed)

        Args:
            on_next: [Optional] Action to invoke for each element in the
                observable sequence.
            on_error: [Optional] Action to invoke upon exceptional termination
                of the observable sequence.
            on_completed: [Optional] Action to invoke upon graceful termination
                of the observable sequence.
            scheduler: [Optional] The scheduler to use for this subscription.

        Returns:
            Disposable object representing an observer's subscription to
            the observable sequence.
        """

        auto_detach_observer: AutoDetachObserver[_T] = AutoDetachObserver(
            on_next, on_error, on_completed
        )

        def fix_subscriber(
            subscriber: Union[abc.DisposableBase, Callable[[], None]]
        ) -> abc.DisposableBase:
            """Fixes subscriber to make sure it returns a Disposable instead
            of None or a dispose function"""

            if isinstance(subscriber, abc.DisposableBase) or hasattr(
                subscriber, "dispose"
            ):
                # Note: cast can be avoided using Protocols (Python 3.9)
                return cast(abc.DisposableBase, subscriber)

            return Disposable(subscriber)

        def set_disposable(_: Optional[abc.SchedulerBase] = None, __: Any = None):
            try:
                subscriber = self._subscribe_core(auto_detach_observer, scheduler)
            except Exception as ex:  # By design. pylint: disable=W0703
                if not auto_detach_observer.fail(ex):
                    raise
            else:
                auto_detach_observer.subscription = fix_subscriber(subscriber)

        # Subscribe needs to set up the trampoline before for subscribing.
        # Actually, the first call to Subscribe creates the trampoline so
        # that it may assign its disposable before any observer executes
        # OnNext over the CurrentThreadScheduler. This enables single-
        # threaded cancellation
        # https://social.msdn.microsoft.com/Forums/en-US/eb82f593-9684-4e27-
        # 97b9-8b8886da5c33/whats-the-rationale-behind-how-currentthreadsche
        # dulerschedulerequired-behaves?forum=rx
        current_thread_scheduler = CurrentThreadScheduler.singleton()
        if current_thread_scheduler.schedule_required():
            current_thread_scheduler.schedule(set_disposable)
        else:
            set_disposable()

        # Hide the identity of the auto detach observer
        return Disposable(auto_detach_observer.dispose)

    @overload
    def pipe(
        self, *operators: Callable[[Observable[_T]], Observable[_A]]
    ) -> Observable[_A]:  # pylint: disable=no-self-use
        """Compose multiple operators left to right.

        Composes zero or more operators into a functional composition.
        The operators are composed from left to right. A composition of zero
        operators gives back the original source.

        Examples:
            >>> source.pipe() == source
            >>> source.pipe(f) == f(source)
            >>> source.pipe(g, f) == f(g(source))
            >>> source.pipe(h, g, f) == f(g(h(source)))

        Args:
            operators: Sequence of operators.

        Returns:
             The composed observable.
        """
        ...

    @overload
    def pipe(self, __op1: Callable[[Observable[_T]], _A]) -> _A:
        ...

    @overload
    def pipe(
        self,
        __op1: Callable[[Observable[_T]], _A],
        __op2: Callable[[_A], _B],
    ) -> _B:
        ...

    @overload
    def pipe(
        self,
        __op1: Callable[[Observable[_T]], _A],
        __op2: Callable[[_A], _B],
        __op3: Callable[[_B], _C],
    ) -> _C:
        ...

    @overload
    def pipe(
        self,
        __op1: Callable[[Observable[_T]], _A],
        __op2: Callable[[_A], _B],
        __op3: Callable[[_B], _C],
        __op4: Callable[[_C], _D],
    ) -> _D:
        ...

    @overload
    def pipe(
        self,
        __op1: Callable[[Observable[_T]], _A],
        __op2: Callable[[_A], _B],
        __op3: Callable[[_B], _C],
        __op4: Callable[[_C], _D],
        __op5: Callable[[_D], _E],
    ) -> _E:
        ...

    @overload
    def pipe(
        self,
        __op1: Callable[[Observable[_T]], _A],
        __op2: Callable[[_A], _B],
        __op3: Callable[[_B], _C],
        __op4: Callable[[_C], _D],
        __op5: Callable[[_D], _E],
        __op6: Callable[[_E], _F],
    ) -> _F:
        ...

    @overload
    def pipe(
        self,
        __op1: Callable[[Observable[_T]], _A],
        __op2: Callable[[_A], _B],
        __op3: Callable[[_B], _C],
        __op4: Callable[[_C], _D],
        __op5: Callable[[_D], _E],
        __op6: Callable[[_E], _F],
        __op7: Callable[[_F], _G],
    ) -> _G:
        ...

    def pipe(self, *operators: Callable[[Any], Any]) -> Any:
        """Compose multiple operators left to right.

        Composes zero or more operators into a functional composition.
        The operators are composed from left to right. A composition of zero
        operators gives back the original source.

        Examples:
            >>> source.pipe() == source
            >>> source.pipe(f) == f(source)
            >>> source.pipe(g, f) == f(g(source))
            >>> source.pipe(h, g, f) == f(g(h(source)))

        Args:
            operators: Sequence of operators.

        Returns:
             The composed observable.
        """
        from ..pipe import pipe

        return pipe(*operators)(self)

    def run(self) -> Any:
        """Run source synchronously.

        Subscribes to the observable source. Then blocks and waits for the
        observable source to either complete or error. Returns the
        last value emitted, or throws exception if any error occurred.

        Examples:
            >>> result = run(source)

        Raises:
            SequenceContainsNoElementsError: if observable completes
                (on_completed) without any values being emitted.
            Exception: raises exception if any error (on_error) occurred.

        Returns:
            The last element emitted from the observable.
        """
        from ..run import run

        return run(self)

    def __await__(self) -> Iterable[_T]:
        """Awaits the given observable.

        Returns:
            The last item of the observable sequence.
        """
        from ..operators.tofuture import to_future_

        loop = asyncio.get_event_loop()
        return iter(self.pipe(to_future_(scheduler=AsyncIOScheduler(loop=loop))))

    def __add__(self, other: Observable[_T]) -> Observable[_T]:
        """Pythonic version of :func:`concat <rx.concat>`.

        Example:
            >>> zs = xs + ys

        Args:
            other: The second observable sequence in the concatenation.

        Returns:
            Concatenated observable sequence.
        """
        from rx import concat

        return concat(self, other)

    def __iadd__(self, other: Observable[_T]) -> "Observable[_T]":
        """Pythonic use of :func:`concat <rx.concat>`.

        Example:
            >>> xs += ys

        Args:
            other: The second observable sequence in the concatenation.

        Returns:
            Concatenated observable sequence.
        """
        from rx import concat

        return concat(self, other)

    def __getitem__(self, key: Union[slice, int]) -> Observable[_T]:
        """
        Pythonic version of :func:`slice <rx.operators.slice>`.

        Slices the given observable using Python slice notation. The arguments
        to slice are `start`, `stop` and `step` given within brackets `[]` and
        separated by the colons `:`.

        It is basically a wrapper around the operators
        :func:`skip <rx.operators.skip>`,
        :func:`skip_last <rx.operators.skip_last>`,
        :func:`take <rx.operators.take>`,
        :func:`take_last <rx.operators.take_last>` and
        :func:`filter <rx.operators.filter>`.

        The following diagram helps you remember how slices works with streams.
        Positive numbers are relative to the start of the events, while negative
        numbers are relative to the end (close) of the stream.

        .. code::

            r---e---a---c---t---i---v---e---!
            0   1   2   3   4   5   6   7   8
           -8  -7  -6  -5  -4  -3  -2  -1   0

        Examples:
            >>> result = source[1:10]
            >>> result = source[1:-2]
            >>> result = source[1:-1:2]

        Args:
            key: Slice object

        Returns:
            Sliced observable sequence.

        Raises:
            TypeError: If key is not of type :code:`int` or :code:`slice`
        """

        if isinstance(key, slice):
            start, stop, step = key.start, key.stop, key.step
        else:
            start, stop, step = key, key + 1, 1

        from ..operators.slice import slice_

        return slice_(start, stop, step)(self)

__all__ = ["Observable"]
<|endoftext|>


<commit_before>"""
Naver Search Workflow for Alfred 2
Copyright (C) 2013  Jinuk Baek
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
"""

import sys

from workflow import web, Workflow

def get_data(word):
	url = 'http://ac.search.naver.com/nx/ac'

	params = dict(q_enc='UTF-8',
		st=100,
		r_format='json',
		r_enc='UTF-8',
		r_unicode=0,
		t_koreng=1,
		ans=1,
		run=2,
		rev=4,
		q=word
	)

	r = web.get(url, params)
	r.raise_for_status()
	return r.json()

def main(wf):
	import cgi;

	args = wf.args[0]

	wf.add_item(title = 'Search Naver Endic for \'%s\'' % args,
				autocomplete=args,
				arg=args,
				valid=True)

	def wrapper():
		return get_data(args)

	res_json = wf.cached_data('nav_%s' % args, wrapper , max_age=30)

	for ltxt in res_json['items'][0]:
		if len(ltxt) > 0  :
			txt = ltxt[0];
			wf.add_item(
				title = 'Search Naver for \'%s\'' % txt,
				autocomplete=txt,
				arg=txt,
				valid=True);

	wf.send_feedback()

if __name__ == '__main__':
	wf = Workflow()
	sys.exit(wf.run(main))

<commit_msg>Fixed typo<commit_after>"""
Naver Search Workflow for Alfred 2
Copyright (C) 2013  Jinuk Baek
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
"""

import sys

from workflow import web, Workflow

def get_data(word):
	url = 'http://ac.search.naver.com/nx/ac'

	params = dict(q_enc='UTF-8',
		st=100,
		r_format='json',
		r_enc='UTF-8',
		r_unicode=0,
		t_koreng=1,
		ans=1,
		run=2,
		rev=4,
		q=word
	)

	r = web.get(url, params)
	r.raise_for_status()
	return r.json()

def main(wf):
	import cgi;

	args = wf.args[0]

	wf.add_item(title = 'Search Naver for \'%s\'' % args,
				autocomplete=args,
				arg=args,
				valid=True)

	def wrapper():
		return get_data(args)

	res_json = wf.cached_data('nav_%s' % args, wrapper , max_age=30)

	for ltxt in res_json['items'][0]:
		if len(ltxt) > 0  :
			txt = ltxt[0];
			wf.add_item(
				title = 'Search Naver for \'%s\'' % txt,
				autocomplete=txt,
				arg=txt,
				valid=True);

	wf.send_feedback()

if __name__ == '__main__':
	wf = Workflow()
	sys.exit(wf.run(main))

<|endoftext|>


<commit_before><commit_msg>add members and class methods to allow filtering and passing template engine specific initialization and rendering options<commit_after><|endoftext|>


<commit_before># -*- coding: utf-8 -*-
import math
import enum

from ._brotli import ffi, lib

class Error(Exception):
    """
    Raised whenever an error is encountered with compressing or decompressing
    data using brotlipy.

    .. versionadded:: 0.5.1
    """
    pass

#: An alias of :class:`Error <brotli.Error>` that exists for compatibility with
#: the original C brotli module.
#:
#: .. versionadded: 0.5.1
error = Error

class BrotliEncoderMode(enum.IntEnum):
    """
    Compression modes for the Brotli encoder.

    .. versionadded:: 0.5.0
    """
    #: Default compression mode. The compressor does not know anything in
    #: advance about the properties of the input.
    GENERIC = lib.BROTLI_MODE_GENERIC

    #: Compression mode for UTF-8 format text input.
    TEXT = lib.BROTLI_MODE_TEXT

    #: Compression mode used in WOFF 2.0
    FONT = lib.BROTLI_MODE_FONT

# Define some names for compatibility with the C module.

#: The default compression mode for brotli.
DEFAULT_MODE = BrotliEncoderMode(lib.BROTLI_DEFAULT_MODE)

#: A compression mode where the compressor does not know anything in advance
#: about the properties of the input.
#:
#: .. note:: This name is defined for compatibility with the Brotli C
#:           extension. If you're not interested in that compatibility, it is
#:           recommended that you use :class:`BrotliEncoderMode
#:           <brotli.BrotliEncoderMode>` instead.
#:
#: .. versionadded:: 0.5.0
MODE_GENERIC = BrotliEncoderMode.GENERIC

#: A compression mode for UTF-8 format text input.
#:
#: .. note:: This name is defined for compatibility with the Brotli C
#:           extension. If you're not interested in that compatibility, it is
#:           recommended that you use :class:`BrotliEncoderMode
#:           <brotli.BrotliEncoderMode>` instead.
#:
#: .. versionadded:: 0.5.0
MODE_TEXT = BrotliEncoderMode.TEXT

#: The compression mode used in WOFF 2.0.
#:
#: .. note:: This name is defined for compatibility with the Brotli C
#:           extension. If you're not interested in that compatibility, it is
#:           recommended that you use :class:`BrotliEncoderMode
#:           <brotli.BrotliEncoderMode>` instead.
#:
#: .. versionadded:: 0.5.0
MODE_FONT = BrotliEncoderMode.FONT

def decompress(data):
    """
    Decompress a complete Brotli-compressed string.

    :param data: A bytestring containing Brotli-compressed data.
    """
    d = Decompressor()
    return d.decompress(data)

def compress(data,
             mode=DEFAULT_MODE,
             quality=lib.BROTLI_DEFAULT_QUALITY,
             lgwin=lib.BROTLI_DEFAULT_WINDOW,
             lgblock=0,
             dictionary=b''):
    """
    Compress a string using Brotli.

    .. versionchanged:: 0.5.0
       Added ``mode``, ``quality``, `lgwin``, ``lgblock``, and ``dictionary``
       parameters.

    :param data: A bytestring containing the data to compress.
    :type data: ``bytes``

    :param mode: The encoder mode.
    :type mode: :class:`BrotliEncoderMode` or ``int``

    :param quality: Controls the compression-speed vs compression-density
        tradeoffs. The higher the quality, the slower the compression. The
        range of this value is 0 to 11.
    :type quality: ``int``

    :param lgwin: The base-2 logarithm of the sliding window size. The range of
        this value is 10 to 24.
    :type lgwin: ``int``

    :param lgblock: The base-2 logarithm of the maximum input block size. The
        range of this value is 16 to 24. If set to 0, the value will be set
        based on ``quality``.
    :type lgblock: ``int``

    :param dictionary: A pre-set dictionary for LZ77. Please use this with
        caution: if a dictionary is used for compression, the same dictionary
        **must** be used for decompression!
    :type dictionary: ``bytes``

    :returns: The compressed bytestring.
    :rtype: ``bytes``
    """
    # This method uses private variables on the Compressor object, and
    # generally does a whole lot of stuff that's not supported by the public
    # API. The goal here is to minimise the number of allocations and copies
    # we have to do. Users should prefer this method over the Compressor if
    # they know they have single-shot data.
    compressor = Compressor(
        mode=mode,
        quality=quality,
        lgwin=lgwin,
        lgblock=lgblock,
        dictionary=dictionary
    )
    compressed_data = compressor._compress(data, lib.BROTLI_OPERATION_FINISH)
    assert lib.BrotliEncoderIsFinished(compressor._encoder) == lib.BROTLI_TRUE
    assert (
        lib.BrotliEncoderHasMoreOutput(compressor._encoder) == lib.BROTLI_FALSE
    )
    return compressed_data

def _validate_mode(val):
    """
    Validate that the mode is valid.
    """
    try:
        val = BrotliEncoderMode(val)
    except ValueError:
        raise Error("%s is not a valid encoder mode" % val)

def _validate_quality(val):
    """
    Validate that the quality setting is valid.
    """
    if not (0 <= val <= 11):
        raise Error(
            "%d is not a valid quality, must be between 0 and 11" % val
        )

def _validate_lgwin(val):
    """
    Validate that the lgwin setting is valid.
    """
    if not (10 <= val <= 24):
        raise Error("%d is not a valid lgwin, must be between 10 and 24" % val)

def _validate_lgblock(val):
    """
    Validate that the lgblock setting is valid.
    """
    if (val != 0) and not (16 <= val <= 24):
        raise Error(
            "%d is not a valid lgblock, must be either 0 or between 16 and 24"
            % val
        )

def _set_parameter(encoder, parameter, parameter_name, val):
    """
    This helper function sets a specific Brotli encoder parameter, checking
    the return code and raising :class:`Error <brotli.Error>` if it is
    invalid.
    """
    rc = lib.BrotliEncoderSetParameter(encoder, parameter, val)

    if parameter == lib.BROTLI_PARAM_MODE:
        _validate_mode(val)
    elif parameter == lib.BROTLI_PARAM_QUALITY:
        _validate_quality(val)
    elif parameter == lib.BROTLI_PARAM_LGWIN:
        _validate_lgwin(val)
    elif parameter == lib.BROTLI_PARAM_LGBLOCK:
        _validate_lgblock(val)
    else:  # pragma: no cover
        raise RuntimeError("Unexpected parameter!")

    # This block is defensive: I see no way to hit it, but as long as the
    # function returns a value we can live in hope that the brotli folks will
    # enforce their own constraints.
    if rc != lib.BROTLI_TRUE:  # pragma: no cover
        raise Error(
            "Error setting parameter %s: %d" % (parameter_name, val)
        )

class Compressor(object):
    """
    An object that allows for streaming compression of data using the Brotli
    compression algorithm.

    .. versionadded:: 0.5.0

    :param mode: The encoder mode.
    :type mode: :class:`BrotliEncoderMode` or ``int``

    :param quality: Controls the compression-speed vs compression-density
        tradeoffs. The higher the quality, the slower the compression. The
        range of this value is 0 to 11.
    :type quality: ``int``

    :param lgwin: The base-2 logarithm of the sliding window size. The range of
        this value is 10 to 24.
    :type lgwin: ``int``

    :param lgblock: The base-2 logarithm of the maximum input block size. The
        range of this value is 16 to 24. If set to 0, the value will be set
        based on ``quality``.
    :type lgblock: ``int``

    :param dictionary: A pre-set dictionary for LZ77. Please use this with
        caution: if a dictionary is used for compression, the same dictionary
        **must** be used for decompression!
    :type dictionary: ``bytes``
    """
    _dictionary = None
    _dictionary_size = None

    def __init__(self,
                 mode=DEFAULT_MODE,
                 quality=lib.BROTLI_DEFAULT_QUALITY,
                 lgwin=lib.BROTLI_DEFAULT_WINDOW,
                 lgblock=0,
                 dictionary=b''):
        enc = lib.BrotliEncoderCreateInstance(
            ffi.NULL, ffi.NULL, ffi.NULL
        )
        if not enc:  # pragma: no cover
            raise RuntimeError("Unable to allocate Brotli encoder!")

        enc = ffi.gc(enc, lib.BrotliEncoderDestroyInstance)

        # Configure the encoder appropriately.
        _set_parameter(enc, lib.BROTLI_PARAM_MODE, "mode", mode)
        _set_parameter(enc, lib.BROTLI_PARAM_QUALITY, "quality", quality)
        _set_parameter(enc, lib.BROTLI_PARAM_LGWIN, "lgwin", lgwin)
        _set_parameter(enc, lib.BROTLI_PARAM_LGBLOCK, "lgblock", lgblock)

        if dictionary:
            self._dictionary = ffi.new("uint8_t []", dictionary)
            self._dictionary_size = len(dictionary)
            lib.BrotliEncoderSetCustomDictionary(
                enc, self._dictionary_size, self._dictionary
            )

        self._encoder = enc

    def _compress(self, data, operation):
        """
        This private method compresses some data in a given mode. This is used
        because almost all of the code uses the exact same setup. It wouldn't
        have to, but it doesn't hurt at all.
        """
        # The 'algorithm' for working out how big to make this buffer is from
        # the Brotli source code, brotlimodule.cc.
        original_output_size = int(
            math.ceil(len(data) + (len(data) >> 2) + 10240)
        )
        available_out = ffi.new("size_t *")
        available_out[0] = original_output_size
        output_buffer = ffi.new("uint8_t []", available_out[0])
        ptr_to_output_buffer = ffi.new("uint8_t **", output_buffer)
        input_size = ffi.new("size_t *", len(data))
        input_buffer = ffi.new("uint8_t []", data)
        ptr_to_input_buffer = ffi.new("uint8_t **", input_buffer)

        rc = lib.BrotliEncoderCompressStream(
            self._encoder,
            operation,
            input_size,
            ptr_to_input_buffer,
            available_out,
            ptr_to_output_buffer,
            ffi.NULL
        )
        if rc != lib.BROTLI_TRUE:  # pragma: no cover
            raise Error("Error encountered compressing data.")

        assert not input_size[0]

        size_of_output = original_output_size - available_out[0]
        return ffi.buffer(output_buffer, size_of_output)[:]

    def compress(self, data):
        """
        Incrementally compress more data.

        :param data: A bytestring containing data to compress.
        :returns: A bytestring containing some compressed data. May return the
            empty bytestring if not enough data has been inserted into the
            compressor to create the output yet.
        """
        return self._compress(data, lib.BROTLI_OPERATION_PROCESS)

    def flush(self):
        """
        Flush the compressor. This will emit the remaining output data, but
        will not destroy the compressor. It can be used, for example, to ensure
        that given chunks of content will decompress immediately.
        """
        chunks = []
        chunks.append(self._compress(b'', lib.BROTLI_OPERATION_FLUSH))

        while lib.BrotliEncoderHasMoreOutput(self._encoder) == lib.BROTLI_TRUE:
            chunks.append(self._compress(b'', lib.BROTLI_OPERATION_FLUSH))

        return b''.join(chunks)

    def finish(self):
        """
        Finish the compressor. This will emit the remaining output data and
        transition the compressor to a completed state. The compressor cannot
        be used again after this point, and must be replaced.
        """
        chunks = []
        while lib.BrotliEncoderIsFinished(self._encoder) == lib.BROTLI_FALSE:
            chunks.append(self._compress(b'', lib.BROTLI_OPERATION_FINISH))

        return b''.join(chunks)

class Decompressor(object):
    """
    An object that allows for streaming decompression of Brotli-compressed
    data.

    .. versionchanged:: 0.5.0
       Added ``dictionary`` parameter.

    :param dictionary: A pre-set dictionary for LZ77. Please use this with
        caution: if a dictionary is used for compression, the same dictionary
        **must** be used for decompression!
    :type dictionary: ``bytes``
    """
    _dictionary = None
    _dictionary_size = None

    def __init__(self, dictionary=b''):
        dec = lib.BrotliDecoderCreateInstance(ffi.NULL, ffi.NULL, ffi.NULL)
        self._decoder = ffi.gc(dec, lib.BrotliDecoderDestroyInstance)

        if dictionary:
            self._dictionary = ffi.new("uint8_t []", dictionary)
            self._dictionary_size = len(dictionary)
            lib.BrotliDecoderSetCustomDictionary(
                self._decoder,
                self._dictionary_size,
                self._dictionary
            )

    def decompress(self, data):
        """
        Decompress part of a complete Brotli-compressed string.

        :param data: A bytestring containing Brotli-compressed data.
        :returns: A bytestring containing the decompressed data.
        """
        chunks = []

        available_in = ffi.new("size_t *", len(data))
        in_buffer = ffi.new("uint8_t[]", data)
        next_in = ffi.new("uint8_t **", in_buffer)

        while True:
            # Allocate a buffer that's hopefully overlarge, but if it's not we
            # don't mind: we'll spin around again.
            buffer_size = 5 * len(data)
            available_out = ffi.new("size_t *", buffer_size)
            out_buffer = ffi.new("uint8_t[]", buffer_size)
            next_out = ffi.new("uint8_t **", out_buffer)

            rc = lib.BrotliDecoderDecompressStream(self._decoder,
                                                   available_in,
                                                   next_in,
                                                   available_out,
                                                   next_out,
                                                   ffi.NULL)

            # First, check for errors.
            if rc == lib.BROTLI_DECODER_RESULT_ERROR:
                error_code = lib.BrotliDecoderGetErrorCode(self._decoder)
                error_message = lib.BrotliDecoderErrorString(error_code)
                raise Error(
                    "Decompression error: %s" % ffi.string(error_message)
                )

            # Next, copy the result out.
            chunk = ffi.buffer(out_buffer, buffer_size - available_out[0])[:]
            chunks.append(chunk)

            if rc == lib.BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:
                assert available_in[0] == 0
                break
            elif rc == lib.BROTLI_DECODER_RESULT_SUCCESS:
                break
            else:
                # It's cool if we need more output, we just loop again.
                assert rc == lib.BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT

        return b''.join(chunks)

    def flush(self):
        """
        Complete the decompression, return whatever data is remaining to be
        decompressed.

        This action also resets the decompression state, allowing the
        decompressor to be used again.

        .. deprecated:: 0.4.0

            This method is no longer required, as decompress() will now
            decompress eagerly.

        :returns: A bytestring containing the remaining decompressed data.
        """

        return b''
<commit_msg>Add finish() method to decompressor.<commit_after># -*- coding: utf-8 -*-
import math
import enum

from ._brotli import ffi, lib

class Error(Exception):
    """
    Raised whenever an error is encountered with compressing or decompressing
    data using brotlipy.

    .. versionadded:: 0.5.1
    """
    pass

#: An alias of :class:`Error <brotli.Error>` that exists for compatibility with
#: the original C brotli module.
#:
#: .. versionadded: 0.5.1
error = Error

class BrotliEncoderMode(enum.IntEnum):
    """
    Compression modes for the Brotli encoder.

    .. versionadded:: 0.5.0
    """
    #: Default compression mode. The compressor does not know anything in
    #: advance about the properties of the input.
    GENERIC = lib.BROTLI_MODE_GENERIC

    #: Compression mode for UTF-8 format text input.
    TEXT = lib.BROTLI_MODE_TEXT

    #: Compression mode used in WOFF 2.0
    FONT = lib.BROTLI_MODE_FONT

# Define some names for compatibility with the C module.

#: The default compression mode for brotli.
DEFAULT_MODE = BrotliEncoderMode(lib.BROTLI_DEFAULT_MODE)

#: A compression mode where the compressor does not know anything in advance
#: about the properties of the input.
#:
#: .. note:: This name is defined for compatibility with the Brotli C
#:           extension. If you're not interested in that compatibility, it is
#:           recommended that you use :class:`BrotliEncoderMode
#:           <brotli.BrotliEncoderMode>` instead.
#:
#: .. versionadded:: 0.5.0
MODE_GENERIC = BrotliEncoderMode.GENERIC

#: A compression mode for UTF-8 format text input.
#:
#: .. note:: This name is defined for compatibility with the Brotli C
#:           extension. If you're not interested in that compatibility, it is
#:           recommended that you use :class:`BrotliEncoderMode
#:           <brotli.BrotliEncoderMode>` instead.
#:
#: .. versionadded:: 0.5.0
MODE_TEXT = BrotliEncoderMode.TEXT

#: The compression mode used in WOFF 2.0.
#:
#: .. note:: This name is defined for compatibility with the Brotli C
#:           extension. If you're not interested in that compatibility, it is
#:           recommended that you use :class:`BrotliEncoderMode
#:           <brotli.BrotliEncoderMode>` instead.
#:
#: .. versionadded:: 0.5.0
MODE_FONT = BrotliEncoderMode.FONT

def decompress(data):
    """
    Decompress a complete Brotli-compressed string.

    :param data: A bytestring containing Brotli-compressed data.
    """
    d = Decompressor()
    data = d.decompress(data)
    d.finish()
    return data

def compress(data,
             mode=DEFAULT_MODE,
             quality=lib.BROTLI_DEFAULT_QUALITY,
             lgwin=lib.BROTLI_DEFAULT_WINDOW,
             lgblock=0,
             dictionary=b''):
    """
    Compress a string using Brotli.

    .. versionchanged:: 0.5.0
       Added ``mode``, ``quality``, `lgwin``, ``lgblock``, and ``dictionary``
       parameters.

    :param data: A bytestring containing the data to compress.
    :type data: ``bytes``

    :param mode: The encoder mode.
    :type mode: :class:`BrotliEncoderMode` or ``int``

    :param quality: Controls the compression-speed vs compression-density
        tradeoffs. The higher the quality, the slower the compression. The
        range of this value is 0 to 11.
    :type quality: ``int``

    :param lgwin: The base-2 logarithm of the sliding window size. The range of
        this value is 10 to 24.
    :type lgwin: ``int``

    :param lgblock: The base-2 logarithm of the maximum input block size. The
        range of this value is 16 to 24. If set to 0, the value will be set
        based on ``quality``.
    :type lgblock: ``int``

    :param dictionary: A pre-set dictionary for LZ77. Please use this with
        caution: if a dictionary is used for compression, the same dictionary
        **must** be used for decompression!
    :type dictionary: ``bytes``

    :returns: The compressed bytestring.
    :rtype: ``bytes``
    """
    # This method uses private variables on the Compressor object, and
    # generally does a whole lot of stuff that's not supported by the public
    # API. The goal here is to minimise the number of allocations and copies
    # we have to do. Users should prefer this method over the Compressor if
    # they know they have single-shot data.
    compressor = Compressor(
        mode=mode,
        quality=quality,
        lgwin=lgwin,
        lgblock=lgblock,
        dictionary=dictionary
    )
    compressed_data = compressor._compress(data, lib.BROTLI_OPERATION_FINISH)
    assert lib.BrotliEncoderIsFinished(compressor._encoder) == lib.BROTLI_TRUE
    assert (
        lib.BrotliEncoderHasMoreOutput(compressor._encoder) == lib.BROTLI_FALSE
    )
    return compressed_data

def _validate_mode(val):
    """
    Validate that the mode is valid.
    """
    try:
        val = BrotliEncoderMode(val)
    except ValueError:
        raise Error("%s is not a valid encoder mode" % val)

def _validate_quality(val):
    """
    Validate that the quality setting is valid.
    """
    if not (0 <= val <= 11):
        raise Error(
            "%d is not a valid quality, must be between 0 and 11" % val
        )

def _validate_lgwin(val):
    """
    Validate that the lgwin setting is valid.
    """
    if not (10 <= val <= 24):
        raise Error("%d is not a valid lgwin, must be between 10 and 24" % val)

def _validate_lgblock(val):
    """
    Validate that the lgblock setting is valid.
    """
    if (val != 0) and not (16 <= val <= 24):
        raise Error(
            "%d is not a valid lgblock, must be either 0 or between 16 and 24"
            % val
        )

def _set_parameter(encoder, parameter, parameter_name, val):
    """
    This helper function sets a specific Brotli encoder parameter, checking
    the return code and raising :class:`Error <brotli.Error>` if it is
    invalid.
    """
    rc = lib.BrotliEncoderSetParameter(encoder, parameter, val)

    if parameter == lib.BROTLI_PARAM_MODE:
        _validate_mode(val)
    elif parameter == lib.BROTLI_PARAM_QUALITY:
        _validate_quality(val)
    elif parameter == lib.BROTLI_PARAM_LGWIN:
        _validate_lgwin(val)
    elif parameter == lib.BROTLI_PARAM_LGBLOCK:
        _validate_lgblock(val)
    else:  # pragma: no cover
        raise RuntimeError("Unexpected parameter!")

    # This block is defensive: I see no way to hit it, but as long as the
    # function returns a value we can live in hope that the brotli folks will
    # enforce their own constraints.
    if rc != lib.BROTLI_TRUE:  # pragma: no cover
        raise Error(
            "Error setting parameter %s: %d" % (parameter_name, val)
        )

class Compressor(object):
    """
    An object that allows for streaming compression of data using the Brotli
    compression algorithm.

    .. versionadded:: 0.5.0

    :param mode: The encoder mode.
    :type mode: :class:`BrotliEncoderMode` or ``int``

    :param quality: Controls the compression-speed vs compression-density
        tradeoffs. The higher the quality, the slower the compression. The
        range of this value is 0 to 11.
    :type quality: ``int``

    :param lgwin: The base-2 logarithm of the sliding window size. The range of
        this value is 10 to 24.
    :type lgwin: ``int``

    :param lgblock: The base-2 logarithm of the maximum input block size. The
        range of this value is 16 to 24. If set to 0, the value will be set
        based on ``quality``.
    :type lgblock: ``int``

    :param dictionary: A pre-set dictionary for LZ77. Please use this with
        caution: if a dictionary is used for compression, the same dictionary
        **must** be used for decompression!
    :type dictionary: ``bytes``
    """
    _dictionary = None
    _dictionary_size = None

    def __init__(self,
                 mode=DEFAULT_MODE,
                 quality=lib.BROTLI_DEFAULT_QUALITY,
                 lgwin=lib.BROTLI_DEFAULT_WINDOW,
                 lgblock=0,
                 dictionary=b''):
        enc = lib.BrotliEncoderCreateInstance(
            ffi.NULL, ffi.NULL, ffi.NULL
        )
        if not enc:  # pragma: no cover
            raise RuntimeError("Unable to allocate Brotli encoder!")

        enc = ffi.gc(enc, lib.BrotliEncoderDestroyInstance)

        # Configure the encoder appropriately.
        _set_parameter(enc, lib.BROTLI_PARAM_MODE, "mode", mode)
        _set_parameter(enc, lib.BROTLI_PARAM_QUALITY, "quality", quality)
        _set_parameter(enc, lib.BROTLI_PARAM_LGWIN, "lgwin", lgwin)
        _set_parameter(enc, lib.BROTLI_PARAM_LGBLOCK, "lgblock", lgblock)

        if dictionary:
            self._dictionary = ffi.new("uint8_t []", dictionary)
            self._dictionary_size = len(dictionary)
            lib.BrotliEncoderSetCustomDictionary(
                enc, self._dictionary_size, self._dictionary
            )

        self._encoder = enc

    def _compress(self, data, operation):
        """
        This private method compresses some data in a given mode. This is used
        because almost all of the code uses the exact same setup. It wouldn't
        have to, but it doesn't hurt at all.
        """
        # The 'algorithm' for working out how big to make this buffer is from
        # the Brotli source code, brotlimodule.cc.
        original_output_size = int(
            math.ceil(len(data) + (len(data) >> 2) + 10240)
        )
        available_out = ffi.new("size_t *")
        available_out[0] = original_output_size
        output_buffer = ffi.new("uint8_t []", available_out[0])
        ptr_to_output_buffer = ffi.new("uint8_t **", output_buffer)
        input_size = ffi.new("size_t *", len(data))
        input_buffer = ffi.new("uint8_t []", data)
        ptr_to_input_buffer = ffi.new("uint8_t **", input_buffer)

        rc = lib.BrotliEncoderCompressStream(
            self._encoder,
            operation,
            input_size,
            ptr_to_input_buffer,
            available_out,
            ptr_to_output_buffer,
            ffi.NULL
        )
        if rc != lib.BROTLI_TRUE:  # pragma: no cover
            raise Error("Error encountered compressing data.")

        assert not input_size[0]

        size_of_output = original_output_size - available_out[0]
        return ffi.buffer(output_buffer, size_of_output)[:]

    def compress(self, data):
        """
        Incrementally compress more data.

        :param data: A bytestring containing data to compress.
        :returns: A bytestring containing some compressed data. May return the
            empty bytestring if not enough data has been inserted into the
            compressor to create the output yet.
        """
        return self._compress(data, lib.BROTLI_OPERATION_PROCESS)

    def flush(self):
        """
        Flush the compressor. This will emit the remaining output data, but
        will not destroy the compressor. It can be used, for example, to ensure
        that given chunks of content will decompress immediately.
        """
        chunks = []
        chunks.append(self._compress(b'', lib.BROTLI_OPERATION_FLUSH))

        while lib.BrotliEncoderHasMoreOutput(self._encoder) == lib.BROTLI_TRUE:
            chunks.append(self._compress(b'', lib.BROTLI_OPERATION_FLUSH))

        return b''.join(chunks)

    def finish(self):
        """
        Finish the compressor. This will emit the remaining output data and
        transition the compressor to a completed state. The compressor cannot
        be used again after this point, and must be replaced.
        """
        chunks = []
        while lib.BrotliEncoderIsFinished(self._encoder) == lib.BROTLI_FALSE:
            chunks.append(self._compress(b'', lib.BROTLI_OPERATION_FINISH))

        return b''.join(chunks)

class Decompressor(object):
    """
    An object that allows for streaming decompression of Brotli-compressed
    data.

    .. versionchanged:: 0.5.0
       Added ``dictionary`` parameter.

    :param dictionary: A pre-set dictionary for LZ77. Please use this with
        caution: if a dictionary is used for compression, the same dictionary
        **must** be used for decompression!
    :type dictionary: ``bytes``
    """
    _dictionary = None
    _dictionary_size = None

    def __init__(self, dictionary=b''):
        dec = lib.BrotliDecoderCreateInstance(ffi.NULL, ffi.NULL, ffi.NULL)
        self._decoder = ffi.gc(dec, lib.BrotliDecoderDestroyInstance)

        if dictionary:
            self._dictionary = ffi.new("uint8_t []", dictionary)
            self._dictionary_size = len(dictionary)
            lib.BrotliDecoderSetCustomDictionary(
                self._decoder,
                self._dictionary_size,
                self._dictionary
            )

    def decompress(self, data):
        """
        Decompress part of a complete Brotli-compressed string.

        :param data: A bytestring containing Brotli-compressed data.
        :returns: A bytestring containing the decompressed data.
        """
        chunks = []

        available_in = ffi.new("size_t *", len(data))
        in_buffer = ffi.new("uint8_t[]", data)
        next_in = ffi.new("uint8_t **", in_buffer)

        while True:
            # Allocate a buffer that's hopefully overlarge, but if it's not we
            # don't mind: we'll spin around again.
            buffer_size = 5 * len(data)
            available_out = ffi.new("size_t *", buffer_size)
            out_buffer = ffi.new("uint8_t[]", buffer_size)
            next_out = ffi.new("uint8_t **", out_buffer)

            rc = lib.BrotliDecoderDecompressStream(self._decoder,
                                                   available_in,
                                                   next_in,
                                                   available_out,
                                                   next_out,
                                                   ffi.NULL)

            # First, check for errors.
            if rc == lib.BROTLI_DECODER_RESULT_ERROR:
                error_code = lib.BrotliDecoderGetErrorCode(self._decoder)
                error_message = lib.BrotliDecoderErrorString(error_code)
                raise Error(
                    "Decompression error: %s" % ffi.string(error_message)
                )

            # Next, copy the result out.
            chunk = ffi.buffer(out_buffer, buffer_size - available_out[0])[:]
            chunks.append(chunk)

            if rc == lib.BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:
                print "needs more input"
                assert available_in[0] == 0
                break
            elif rc == lib.BROTLI_DECODER_RESULT_SUCCESS:
                break
            else:
                # It's cool if we need more output, we just loop again.
                assert rc == lib.BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT

        return b''.join(chunks)

    def flush(self):
        """
        Complete the decompression, return whatever data is remaining to be
        decompressed.

        .. deprecated:: 0.4.0

            This method is no longer required, as decompress() will now
            decompress eagerly.

        :returns: A bytestring containing the remaining decompressed data.
        """
        return b''

    def finish(self):
        """
        Finish the decompressor. As the decompressor decompresses eagerly, this
        will never actually emit any data. However, it will potentially throw
        errors if a truncated or damaged data stream has been used.

        Note that, once this method is called, the decompressor is no longer
        safe for further use and must be thrown away.
        """
        assert (
            lib.BrotliDecoderHasMoreOutput(self._decoder) == lib.BROTLI_FALSE
        )
        if lib.BrotliDecoderIsFinished(self._decoder) == lib.BROTLI_FALSE:
            raise Error("Decompression error: incomplete compressed stream.")

        return b''
<|endoftext|>


<commit_before># -*- coding: utf-8; -*-
#
# This file is part of Superdesk.
#
# Copyright 2013, 2014 Sourcefabric z.u. and contributors.
#
# For the full copyright and license information, please see the
# AUTHORS and LICENSE files distributed with this source code, or
# at https://www.sourcefabric.org/superdesk/license

import flask
import logging
import superdesk

from superdesk.metadata.item import ITEM_STATE, CONTENT_STATE, ITEM_TYPE, CONTENT_TYPE, PUBLISH_STATES
from superdesk.errors import SuperdeskApiError
from superdesk.notification import push_notification
from superdesk.users.services import current_user_has_privilege
from superdesk.utc import utcnow
from superdesk.lock import lock, unlock
from eve.utils import config
from eve.versioning import resolve_document_version, insert_versioning_documents

from apps.common.components.base_component import BaseComponent
from apps.common.models.utils import get_model
from apps.content import push_content_notification
from apps.packages.package_service import PackageService
from ..models.item import ItemModel

LOCK_USER = 'lock_user'
LOCK_SESSION = 'lock_session'
STATUS = '_status'
TASK = 'task'
logger = logging.getLogger(__name__)

def push_unlock_notification(item, user_id, session_id):
    push_notification(
        'item:unlock',
        item=str(item.get(config.ID_FIELD)),
        item_version=str(item.get(config.VERSION)),
        state=item.get(ITEM_STATE),
        user=str(user_id),
        lock_session=str(session_id),
        _etag=item.get(config.ETAG)
    )

class ItemLock(BaseComponent):
    def __init__(self, app):
        self.app = app
        self.app.on_session_end += self.on_session_end

    @classmethod
    def name(cls):
        return 'item_lock'

    def lock(self, item_filter, user_id, session_id, action):
        item_model = get_model(ItemModel)
        item = item_model.find_one(item_filter)

        # set the lock_id it per item
        lock_id = "item_lock {}".format(item.get(config.ID_FIELD))

        if not item:
            raise SuperdeskApiError.notFoundError()

        # get the lock it not raise forbidden exception
        if not lock(lock_id, expire=5):
            raise SuperdeskApiError.forbiddenError(message="Item is locked by another user.")

        try:
            can_user_lock, error_message = self.can_lock(item, user_id, session_id)

            if can_user_lock:
                self.app.on_item_lock(item, user_id)
                updates = {LOCK_USER: user_id, LOCK_SESSION: session_id, 'lock_time': utcnow()}
                if action:
                    updates['lock_action'] = action

                item_model.update(item_filter, updates)

                if item.get(TASK):
                    item[TASK]['user'] = user_id
                else:
                    item[TASK] = {'user': user_id}

                superdesk.get_resource_service('tasks').assign_user(item[config.ID_FIELD], item[TASK])
                self.app.on_item_locked(item, user_id)
                item = item_model.find_one(item_filter)
                push_notification('item:lock',
                                  item=str(item.get(config.ID_FIELD)),
                                  item_version=str(item.get(config.VERSION)),
                                  user=str(user_id), lock_time=updates['lock_time'],
                                  lock_session=str(session_id),
                                  _etag=item.get(config.ETAG))
            else:
                raise SuperdeskApiError.forbiddenError(message=error_message)

            item = item_model.find_one(item_filter)
            return item
        finally:
            # unlock the lock :)
            unlock(lock_id, remove=True)

    def unlock(self, item_filter, user_id, session_id, etag):
        item_model = get_model(ItemModel)
        item = item_model.find_one(item_filter)

        if not item:
            raise SuperdeskApiError.notFoundError()

        if not item.get(LOCK_USER):
            raise SuperdeskApiError.badRequestError(message="Item is not locked.")

        can_user_unlock, error_message = self.can_unlock(item, user_id)

        if can_user_unlock:
            self.app.on_item_unlock(item, user_id)
            updates = {}

            # delete the item if nothing is saved so far
            # version 0 created on lock item
            if item.get(config.VERSION, 0) == 0 and item[ITEM_STATE] == CONTENT_STATE.DRAFT:
                if item.get(ITEM_TYPE) == CONTENT_TYPE.COMPOSITE:
                    # if item is composite then update referenced items in package.
                    PackageService().update_groups({}, item)

                superdesk.get_resource_service('archive').delete_action(lookup={'_id': item['_id']})
                push_content_notification([item])
            else:
                updates = {LOCK_USER: None, LOCK_SESSION: None, 'lock_time': None,
                           'lock_action': None, 'force_unlock': True}
                autosave = superdesk.get_resource_service('archive_autosave').find_one(req=None, _id=item['_id'])
                if autosave and item[ITEM_STATE] not in PUBLISH_STATES:
                    if not hasattr(flask.g, 'user'):  # user is not set when session expires
                        flask.g.user = superdesk.get_resource_service('users').find_one(req=None, _id=user_id)
                    autosave.update(updates)
                    resolve_document_version(autosave, 'archive', 'PATCH', item)
                    superdesk.get_resource_service('archive').patch(item['_id'], autosave)
                    item = superdesk.get_resource_service('archive').find_one(req=None, _id=item['_id'])
                    insert_versioning_documents('archive', item)
                else:
                    item_model.update(item_filter, updates)
                    item = item_model.find_one(item_filter)
                self.app.on_item_unlocked(item, user_id)

            push_unlock_notification(item, user_id, session_id)
        else:
            raise SuperdeskApiError.forbiddenError(message=error_message)

        return item

    def unlock_session(self, user_id, session_id):
        item_model = get_model(ItemModel)
        items = item_model.find({'lock_session': session_id})

        for item in items:
            self.unlock({'_id': item['_id']}, user_id, session_id, None)

    def can_lock(self, item, user_id, session_id):
        """
        Function checks whether user can lock the item or not. If not then raises exception.
        """
        can_user_edit, error_message = superdesk.get_resource_service('archive').can_edit(item, user_id)

        if can_user_edit:
            if item.get(LOCK_USER):
                if str(item.get(LOCK_USER, '')) == str(user_id) and str(item.get(LOCK_SESSION)) != str(session_id):
                    return False, 'Item is locked by you in another session.'
                else:
                    if str(item.get(LOCK_USER, '')) != str(user_id):
                        return False, 'Item is locked by another user.'
        else:
            return False, error_message

        return True, ''

    def can_unlock(self, item, user_id):
        """
        Function checks whether user can unlock the item or not.
        """
        can_user_edit, error_message = superdesk.get_resource_service('archive').can_edit(item, user_id)

        if can_user_edit:
            if not (str(item.get(LOCK_USER, '')) == str(user_id) or
                    (current_user_has_privilege('archive') and current_user_has_privilege('unlock'))):
                return False, 'You don\'t have permissions to unlock an item.'
        else:
            return False, error_message

        return True, ''

    def on_session_end(self, user_id, session_id):
        self.unlock_session(user_id, session_id)
<commit_msg>(fix): on_item_locked not passing updated item<commit_after># -*- coding: utf-8; -*-
#
# This file is part of Superdesk.
#
# Copyright 2013, 2014 Sourcefabric z.u. and contributors.
#
# For the full copyright and license information, please see the
# AUTHORS and LICENSE files distributed with this source code, or
# at https://www.sourcefabric.org/superdesk/license

import flask
import logging
import superdesk

from superdesk.metadata.item import ITEM_STATE, CONTENT_STATE, ITEM_TYPE, CONTENT_TYPE, PUBLISH_STATES
from superdesk.errors import SuperdeskApiError
from superdesk.notification import push_notification
from superdesk.users.services import current_user_has_privilege
from superdesk.utc import utcnow
from superdesk.lock import lock, unlock
from eve.utils import config
from eve.versioning import resolve_document_version, insert_versioning_documents

from apps.common.components.base_component import BaseComponent
from apps.common.models.utils import get_model
from apps.content import push_content_notification
from apps.packages.package_service import PackageService
from ..models.item import ItemModel

LOCK_USER = 'lock_user'
LOCK_SESSION = 'lock_session'
STATUS = '_status'
TASK = 'task'
logger = logging.getLogger(__name__)

def push_unlock_notification(item, user_id, session_id):
    push_notification(
        'item:unlock',
        item=str(item.get(config.ID_FIELD)),
        item_version=str(item.get(config.VERSION)),
        state=item.get(ITEM_STATE),
        user=str(user_id),
        lock_session=str(session_id),
        _etag=item.get(config.ETAG)
    )

class ItemLock(BaseComponent):
    def __init__(self, app):
        self.app = app
        self.app.on_session_end += self.on_session_end

    @classmethod
    def name(cls):
        return 'item_lock'

    def lock(self, item_filter, user_id, session_id, action):
        item_model = get_model(ItemModel)
        item = item_model.find_one(item_filter)

        # set the lock_id it per item
        lock_id = "item_lock {}".format(item.get(config.ID_FIELD))

        if not item:
            raise SuperdeskApiError.notFoundError()

        # get the lock it not raise forbidden exception
        if not lock(lock_id, expire=5):
            raise SuperdeskApiError.forbiddenError(message="Item is locked by another user.")

        try:
            can_user_lock, error_message = self.can_lock(item, user_id, session_id)

            if can_user_lock:
                self.app.on_item_lock(item, user_id)
                updates = {LOCK_USER: user_id, LOCK_SESSION: session_id, 'lock_time': utcnow()}
                if action:
                    updates['lock_action'] = action

                item_model.update(item_filter, updates)

                if item.get(TASK):
                    item[TASK]['user'] = user_id
                else:
                    item[TASK] = {'user': user_id}

                superdesk.get_resource_service('tasks').assign_user(item[config.ID_FIELD], item[TASK])
                item = item_model.find_one(item_filter)
                self.app.on_item_locked(item, user_id)
                push_notification('item:lock',
                                  item=str(item.get(config.ID_FIELD)),
                                  item_version=str(item.get(config.VERSION)),
                                  user=str(user_id), lock_time=updates['lock_time'],
                                  lock_session=str(session_id),
                                  _etag=item.get(config.ETAG))
            else:
                raise SuperdeskApiError.forbiddenError(message=error_message)

            item = item_model.find_one(item_filter)
            return item
        finally:
            # unlock the lock :)
            unlock(lock_id, remove=True)

    def unlock(self, item_filter, user_id, session_id, etag):
        item_model = get_model(ItemModel)
        item = item_model.find_one(item_filter)

        if not item:
            raise SuperdeskApiError.notFoundError()

        if not item.get(LOCK_USER):
            raise SuperdeskApiError.badRequestError(message="Item is not locked.")

        can_user_unlock, error_message = self.can_unlock(item, user_id)

        if can_user_unlock:
            self.app.on_item_unlock(item, user_id)
            updates = {}

            # delete the item if nothing is saved so far
            # version 0 created on lock item
            if item.get(config.VERSION, 0) == 0 and item[ITEM_STATE] == CONTENT_STATE.DRAFT:
                if item.get(ITEM_TYPE) == CONTENT_TYPE.COMPOSITE:
                    # if item is composite then update referenced items in package.
                    PackageService().update_groups({}, item)

                superdesk.get_resource_service('archive').delete_action(lookup={'_id': item['_id']})
                push_content_notification([item])
            else:
                updates = {LOCK_USER: None, LOCK_SESSION: None, 'lock_time': None,
                           'lock_action': None, 'force_unlock': True}
                autosave = superdesk.get_resource_service('archive_autosave').find_one(req=None, _id=item['_id'])
                if autosave and item[ITEM_STATE] not in PUBLISH_STATES:
                    if not hasattr(flask.g, 'user'):  # user is not set when session expires
                        flask.g.user = superdesk.get_resource_service('users').find_one(req=None, _id=user_id)
                    autosave.update(updates)
                    resolve_document_version(autosave, 'archive', 'PATCH', item)
                    superdesk.get_resource_service('archive').patch(item['_id'], autosave)
                    item = superdesk.get_resource_service('archive').find_one(req=None, _id=item['_id'])
                    insert_versioning_documents('archive', item)
                else:
                    item_model.update(item_filter, updates)
                    item = item_model.find_one(item_filter)
                self.app.on_item_unlocked(item, user_id)

            push_unlock_notification(item, user_id, session_id)
        else:
            raise SuperdeskApiError.forbiddenError(message=error_message)

        return item

    def unlock_session(self, user_id, session_id):
        item_model = get_model(ItemModel)
        items = item_model.find({'lock_session': session_id})

        for item in items:
            self.unlock({'_id': item['_id']}, user_id, session_id, None)

    def can_lock(self, item, user_id, session_id):
        """
        Function checks whether user can lock the item or not. If not then raises exception.
        """
        can_user_edit, error_message = superdesk.get_resource_service('archive').can_edit(item, user_id)

        if can_user_edit:
            if item.get(LOCK_USER):
                if str(item.get(LOCK_USER, '')) == str(user_id) and str(item.get(LOCK_SESSION)) != str(session_id):
                    return False, 'Item is locked by you in another session.'
                else:
                    if str(item.get(LOCK_USER, '')) != str(user_id):
                        return False, 'Item is locked by another user.'
        else:
            return False, error_message

        return True, ''

    def can_unlock(self, item, user_id):
        """
        Function checks whether user can unlock the item or not.
        """
        can_user_edit, error_message = superdesk.get_resource_service('archive').can_edit(item, user_id)

        if can_user_edit:
            if not (str(item.get(LOCK_USER, '')) == str(user_id) or
                    (current_user_has_privilege('archive') and current_user_has_privilege('unlock'))):
                return False, 'You don\'t have permissions to unlock an item.'
        else:
            return False, error_message

        return True, ''

    def on_session_end(self, user_id, session_id):
        self.unlock_session(user_id, session_id)
<|endoftext|>


<commit_before># Tools/paths finder resolves external paths from settings and/or environment
# variables
#
# Currently supported:
#
#   PYLMM_PATH finds the root of the git repository of the pylmm_gn2 tool

import os
import sys
from wqflask import app

def get_setting(command_id,guess=None):
    """Resolve a setting from the environment or the global settings in
    app.config, with get_valid_path is a function checking whether the
    path points to an expected directory and returns the full path to
    the binary command

      guess = os.environ.get('HOME')+'/pylmm'
      get_setting('PYLMM_PATH',guess)

    first tries the environment variable in +id+, next gets the Flask
    app setting for the same +id+ and finally does an educated
    +guess+.

    In all, the environment overrides the others, next is the flask
    setting, then the guess. A valid path to the binary command is
    returned. If none is resolved an exception is thrown.

    Note that we do not use the system path. This is on purpose
    because it will mess up controlled (reproducible) deployment. The
    proper way is to either use the GNU Guix defaults as listed in
    etc/default_settings.py or override them yourself by creating a
    different settings.py file (or setting the environment).

    """
    def value(command):
        if command:
            sys.stderr.write("Found value "+command+"\n")
            return command
        else:
            return None

    # ---- Check whether environment exists
    sys.stderr.write("Looking for "+command_id+"\n")
    command = value(os.environ.get(command_id))
    if not command:
        # ---- Check whether setting exists in app
        command = value(app.config.get(command_id))
        if not command:
            command = value(guess)
            if not command:
                raise Exception(command_id+' path unknown or faulty (update settings.py?). '+command_id+' should point to the path')
    return command

def valid_bin(bin):
    if os.path.islink(bin) or os.path.isfile(bin):
        return bin
    return None

def valid_path(dir):
    if os.path.isdir(dir):
        return dir
    return None

def pylmm_command(guess=None):
    return valid_bin(get_setting("PYLMM_RUN",guess))

def gemma_command(guess=None):
    return valid_bin(get_setting("GEMMA_RUN",guess))

def plink_command(guess=None):
    return valid_bin(get_setting("PLINK_RUN",guess))

def flat_files(subdir=None):
    base = get_setting("GENENETWORK_FILES")
    if subdir:
        return valid_path(base+"/"+subdir)
    return valid_path(base)

def locate(name, subdir=None):
    base = get_setting("GENENETWORK_FILES")
    if subdir:
        base = base+"/"+subdir
    if valid_path(base):
        lookfor = base + "/" + name
        if valid_path(lookfor):
            return lookfor
        else:
            raise IOError("Can not locate "+lookfor)
    raise IOError("Can not locate "+name)

def tempdir():
    return valid_path(get_setting("TEMPDIR","/tmp"))

# Cached values
PYLMM_COMMAND = pylmm_command()
GEMMA_COMMAND = pylmm_command()
PLINK_COMMAND = pylmm_command()
FLAT_FILES    = flat_files()
TEMPDIR       = tempdir()
<commit_msg>tools.py: add function locate_without_error<commit_after># Tools/paths finder resolves external paths from settings and/or environment
# variables
#
# Currently supported:
#
#   PYLMM_PATH finds the root of the git repository of the pylmm_gn2 tool

import os
import sys
from wqflask import app

def get_setting(command_id,guess=None):
    """Resolve a setting from the environment or the global settings in
    app.config, with get_valid_path is a function checking whether the
    path points to an expected directory and returns the full path to
    the binary command

      guess = os.environ.get('HOME')+'/pylmm'
      get_setting('PYLMM_PATH',guess)

    first tries the environment variable in +id+, next gets the Flask
    app setting for the same +id+ and finally does an educated
    +guess+.

    In all, the environment overrides the others, next is the flask
    setting, then the guess. A valid path to the binary command is
    returned. If none is resolved an exception is thrown.

    Note that we do not use the system path. This is on purpose
    because it will mess up controlled (reproducible) deployment. The
    proper way is to either use the GNU Guix defaults as listed in
    etc/default_settings.py or override them yourself by creating a
    different settings.py file (or setting the environment).

    """
    def value(command):
        if command:
            sys.stderr.write("Found value "+command+"\n")
            return command
        else:
            return None

    # ---- Check whether environment exists
    sys.stderr.write("Looking for "+command_id+"\n")
    command = value(os.environ.get(command_id))
    if not command:
        # ---- Check whether setting exists in app
        command = value(app.config.get(command_id))
        if not command:
            command = value(guess)
            if not command:
                raise Exception(command_id+' path unknown or faulty (update settings.py?). '+command_id+' should point to the path')
    return command

def valid_bin(bin):
    if os.path.islink(bin) or os.path.isfile(bin):
        return bin
    return None

def valid_path(dir):
    if os.path.isdir(dir):
        return dir
    return None

def pylmm_command(guess=None):
    return valid_bin(get_setting("PYLMM_RUN",guess))

def gemma_command(guess=None):
    return valid_bin(get_setting("GEMMA_RUN",guess))

def plink_command(guess=None):
    return valid_bin(get_setting("PLINK_RUN",guess))

def flat_files(subdir=None):
    base = get_setting("GENENETWORK_FILES")
    if subdir:
        return valid_path(base+"/"+subdir)
    return valid_path(base)

def locate(name, subdir=None):
    """
    Locate a static flat file in the GENENETWORK_FILES environment.

    This function throws an error when the file is not found.
    """
    base = get_setting("GENENETWORK_FILES")
    if subdir:
        base = base+"/"+subdir
    if valid_path(base):
        lookfor = base + "/" + name
        if valid_path(lookfor):
            return lookfor
        else:
            raise IOError("Can not locate "+lookfor)
    raise IOError("Can not locate "+name)

def locate_without_error(name, subdir=None):
    """
    Locate a static flat file in the GENENETWORK_FILES environment.

    This function does not throw an error when the file is not found
    but returns None.
    """
    base = get_setting("GENENETWORK_FILES")
    if subdir:
        base = base+"/"+subdir
    if valid_path(base):
        lookfor = base + "/" + name
        if valid_path(lookfor):
            return lookfor
    sys.stderr.write("WARNING: file "+name+" not found\n")
    return None

def tempdir():
    return valid_path(get_setting("TEMPDIR","/tmp"))

# Cached values
PYLMM_COMMAND = pylmm_command()
GEMMA_COMMAND = pylmm_command()
PLINK_COMMAND = pylmm_command()
FLAT_FILES    = flat_files()
TEMPDIR       = tempdir()
<|endoftext|>