File: __main__.py

package info (click to toggle)
python-vsure 2.6.7-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 140 kB
  • sloc: python: 773; makefile: 4
file content (150 lines) | stat: -rw-r--r-- 4,886 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
""" Command line interface for Verisure MyPages """

import inspect
import json
import re
import click
import logging
from verisure import VariableTypes, Session, ResponseError, LoginError


class DeviceLabel(click.ParamType):
    """Click param for device label"""
    name = "DeviceLabel"

    def convert(self, value, param, ctx):
        if re.match(r"^([A-Z]|[0-9]){4} ([A-Z]|[0-9]){4}$", value):
            return value
        self.fail(f"{value!r} is not a device label", param, ctx)


class ArmFutureState(click.ParamType):
    """Click param for arm future state"""
    name = "FutureState"


class LockFutureState(click.ParamType):
    """Click param for lock future state"""
    name = "FutureState"


class TransactionId(click.ParamType):
    """Click param for transaction id"""
    name = "TransactionId"


class RequestId(click.ParamType):
    """Click param for request id"""
    name = "RequestId"


class Code(click.ParamType):
    """Click param for code"""
    name = "Code"

    def convert(self, value, param, ctx):
        if re.match(r"^[0-9]{4,6}$", value):
            return value
        self.fail(f"{value!r} is not a code", param, ctx)


VariableTypeMap = {
    VariableTypes.DeviceLabel: DeviceLabel(),
    VariableTypes.ArmFutureState: ArmFutureState(),
    VariableTypes.LockFutureState: LockFutureState(),
    bool: click.BOOL,
    VariableTypes.TransactionId: TransactionId(),
    VariableTypes.RequestId: RequestId(),
    VariableTypes.Code: Code(),
}


def options_from_operator_list():
    """Get all query operations and build query cli"""
    def decorator(f):
        ops = inspect.getmembers(Session, predicate=inspect.isfunction)
        for name, operation in reversed(ops):
            if not hasattr(operation, 'is_query'):
                continue
            variables = list(operation.__annotations__.values())
            # Remove Giid type from variables, not supported by CLI
            if VariableTypes.Giid in variables:
                variables.remove(VariableTypes.Giid)
            dashed_name = name.replace('_', '-')
            if len(variables) == 0:
                click.option(
                    '--'+dashed_name,
                    is_flag=True,
                    help=operation.__doc__)(f)
            elif len(variables) == 1:
                click.option(
                    '--'+dashed_name,
                    type=VariableTypeMap[variables[0]],
                    help=operation.__doc__)(f)
            else:
                types = [VariableTypeMap[variable] for variable in variables]
                click.option(
                    '--'+dashed_name,
                    type=click.Tuple(types),
                    help=operation.__doc__)(f)
        return f
    return decorator


def make_query(session, name, arguments):
    """make query operation"""
    if arguments is True:
        return getattr(session, name)()
    if isinstance(arguments, str):
        return getattr(session, name)(arguments)
    return getattr(session, name)(*arguments)


@click.command()
@click.argument('username')
@click.argument('password')
@click.option('-i', '--installation', 'installation', help='Installation number', type=int, default=0)  # noqa: E501
@click.option('-c', '--cookie', 'cookie', help='File to store cookie in', default='~/.verisure-cookie')  # noqa: E501
@click.option('--mfa', 'mfa', help='Login using MFA', default=False, is_flag=True)  # noqa: E501
@click.option('--log-level', type=click.Choice(['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], case_sensitive=False))  # noqa: E501
@options_from_operator_list()
def cli(username, password, installation, cookie, mfa, log_level, **kwargs):
    """Read and change status of verisure devices through verisure app API"""

    if log_level:
        logging.basicConfig(level=logging.getLevelName(log_level))

    session = Session(username, password, cookie)

    try:
        # try using the cookie first
        installations = session.login_cookie()
    except LoginError:
        installations = None

    try:
        if mfa and not installations:
            session.request_mfa()
            code = input("Enter verification code: ")
            session.validate_mfa(code)
            installations = session.login_cookie()
        elif not installations:
            installations = session.login()

        session.set_giid(
            installations['data']['account']
            ['installations'][installation]['giid'])
        queries = [
            make_query(session, name, arguments)
            for name, arguments in kwargs.items()
            if arguments]
        result = session.request(*queries)
        click.echo(json.dumps(result, indent=4, separators=(',', ': ')))

    except ResponseError as ex:
        click.echo(ex,err=True)


if __name__ == "__main__":
    # pylint: disable=no-value-for-parameter
    cli()