Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Is there a way to add timeout, when Google browser popup is closed? #54

Closed
Teraskull opened this issue Nov 6, 2020 · 11 comments · Fixed by #69
Closed

Is there a way to add timeout, when Google browser popup is closed? #54

Teraskull opened this issue Nov 6, 2020 · 11 comments · Fixed by #69

Comments

@Teraskull
Copy link

Right now, if you close the browser window, the script will hang, waiting for user permission.

Is there a way to add a timeout?

For example, the local server:

credentials = flow.run_local_server()

If there are no credentials returned for 1 minute, close local server, and return error.

@kuzmoyev
Copy link
Owner

kuzmoyev commented Nov 10, 2020

Hmm. It is not natively supported in gcsa. Couldn't find this possibility with flow.run_local_server().

You can do it with

import signal

def signal_handler(signum, frame):
    raise Exception("Timed out!")

signal.signal(signal.SIGALRM, signal_handler)
signal.alarm(60)   # 60 seconds
try:
    gc = GoogleCalendar(...)
except Exception:
    print("Timed out!")
else:
    signal.alarm(0)

I will think if I want to add it natively to the library.

Thank you for the idea and issue submission.

@Teraskull
Copy link
Author

Thank you! Will try this solution. Closed for now.

@kuzmoyev
Copy link
Owner

kuzmoyev commented Nov 10, 2020

I'll leave it open for now. It is a good feature to think about. And probably a common use case.

@kuzmoyev kuzmoyev reopened this Nov 10, 2020
@Teraskull
Copy link
Author

Teraskull commented Nov 10, 2020

Oh, once you opened the issue, I tried your solution, but there is no signal.SIGALRM on Windows, so it does not work.

@Teraskull
Copy link
Author

Teraskull commented Nov 10, 2020

Ok, so I found an option that "kind of" works on Windows. (Not recommended due to threading issues)

Create a timeout.py file, and add this:

import threading


class TimeoutError(Exception):
    pass


class InterruptableThread(threading.Thread):
    def __init__(self, func, *args, **kwargs):
        threading.Thread.__init__(self)
        self._func = func
        self._args = args
        self._kwargs = kwargs
        self._result = None

    def run(self):
        self._result = self._func(*self._args, **self._kwargs)

    @property
    def result(self):
        return self._result


class timeout(object):
    def __init__(self, sec):
        self._sec = sec

    def __call__(self, f):
        def wrapped_f(*args, **kwargs):
            it = InterruptableThread(f, *args, **kwargs)
            it.start()
            it.join(self._sec)
            if not it.is_alive():
                it.join()
                return it.result
            raise TimeoutError('execution expired')
        return wrapped_f

In google_calendar.py import timeout and add a decorator for _get_credentials, like this:

from timeout import timeout, TimeoutError

...

@timeout(60)  # 60 seconds, but we can also pass a custom int via the GoogleCalendar class.
def _get_credentials(self):
	...

And when creating a calendar, wrap it in a try/except:

try:
	calendar = GoogleCalendar('primary', ...)
except TimeoutError:
	pass

Hope this helps, when implementing this feature!

@Teraskull
Copy link
Author

Teraskull commented Nov 12, 2020

Or, even a better option. The previous one has issues with catching other exceptions, and the timeout thread is not closed when finished, leading to future errors.

This code is the only one that worked for me on Windows, without any hidden bugs with closing threads.

$ pip install pebble
from oauthlib.oauth2.rfc6749.errors import AccessDeniedError, MismatchingStateError
from gcsa.google_calendar import GoogleCalendar
from concurrent.futures import TimeoutError
from pebble import concurrent


@concurrent.process(timeout=60)  # Raise TimeoutError, if the function exceeds the given deadline (in seconds).
def create_process():
    return GoogleCalendar('primary', credentials_path='credentials.json')


def do_something():
    try:

        process = create_process()  # Return concurrent process.
        calendar = process.result()  # Wait for process result. Return calendar instance, or exceptions, if any were raised.

    # Catch exceptions, received from process result.
    except TimeoutError:  # If user did not log into Google Calendar on time.
        pass

    except AccessDeniedError:  # If user denied access to Google Calendar.
        pass

    except MismatchingStateError:  # If user attempted to login using an outdated browser window.
        pass

@kuzmoyev
Copy link
Owner

That looks perfect. Thank you! I'll try to incorporate it into the library

@kuzmoyev
Copy link
Owner

Decided to not implement it into the library. Instead provided an example in the documentation if anybody needs this. Thank you @Teraskull for your suggestion!

@kuzmoyev kuzmoyev linked a pull request Feb 27, 2021 that will close this issue
@Teraskull
Copy link
Author

Yes, this is a good idea. This feature is to vague to be useful for everyone. Thanks!

@Teraskull
Copy link
Author

You have some typos in the docs, you can see them here: https://github.com/Teraskull/google-calendar-simple-api/commit/8ab725826c0eaab2b8352ea9baebdfd3d4507569

I don't want to open a whole PR for a few fixes. Just edit them yourself. Thanks.

@kuzmoyev
Copy link
Owner

Thanks, updated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants