Changes between Initial Version and Version 1 of Examples/PA-API Python Example


Ignore:
Timestamp:
Oct 4, 2024, 3:35:10 PM (12 days ago)
Author:
edwin
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Examples/PA-API Python Example

    v1 v1  
     1**The following is an example of accessing the Telldus PA-API using Python**
     2
     3This example requires the following 3rd party Python packages:\\
     4Flask
     5requests_oauthlib
     6
     7You can install them using pip:\\
     8{{{
     9pip install -U flask requests_oauthlib
     10}}}
     11
     12== The example project
     13
     14**Running the project**
     15
     16You can run the project's main application using python from the root of the project folder:
     17
     18{{{
     19python app.py
     20}}}
     21
     22
     23**Project directory**
     24
     25You can organise your project directory as follows:
     26
     27{{{
     28telldus_flask_app/
     29
     30├── app.py
     31├── config.py
     32├── requirements.txt
     33└── templates/
     34    ├── index.html
     35    ├── clients.html
     36    ├── devices.html
     37    └── authorization_failed.html
     38
     39}}}
     40
     41== App & configurations
     42
     43**Configuration**
     44
     45We define some commonly used constants here.
     46
     47`config.py`
     48
     49{{{
     50# config.py
     51import os
     52
     53# Obtain the public and private key from https://pa-api.telldus.com/keys/showToken
     54CONSUMER_KEY = 'Public key'
     55CONSUMER_SECRET = 'Private key'
     56
     57# Defines the API end points
     58REQUEST_TOKEN_URL = 'https://pa-api.telldus.com/oauth/requestToken'
     59AUTHORIZE_URL = 'https://pa-api.telldus.com/oauth/authorize'
     60ACCESS_TOKEN_URL = 'https://pa-api.telldus.com/oauth/accessToken'
     61
     62API_BASE_URL = 'https://pa-api.telldus.com/json'
     63
     64CALLBACK_URL = 'http://127.0.0.1:5000/callback'  # Update with your domain
     65
     66# Flask secret key for session management
     67SECRET_KEY = os.urandom(24)
     68
     69# Add to config.py
     70
     71TURNON = 1    # Example value, replace with actual constant
     72TURNOFF = 2   # Example value, replace with actual constant
     73}}}
     74
     75
     76**The Flask application**
     77
     78The main application.
     79
     80`app.py`
     81
     82{{{
     83# app.py
     84
     85from flask import Flask, session, redirect, url_for, request, render_template, flash
     86from requests_oauthlib import OAuth1Session
     87import config
     88import json
     89
     90app = Flask(__name__)
     91app.config.from_object('config')
     92
     93# OAuth1Session setup
     94def get_oauth_session(token=None, token_secret=None):
     95    return OAuth1Session(
     96        client_key=config.CONSUMER_KEY,
     97        client_secret=config.CONSUMER_SECRET,
     98        resource_owner_key=token,
     99        resource_owner_secret=token_secret,
     100        callback_uri=config.CALLBACK_URL
     101    )
     102
     103def print_debug_message(message):
     104    print(f'[DEBUG] {message}')
     105
     106@app.route('/')
     107def index():
     108    access_token = session.get('access_token')
     109    access_token_secret = session.get('access_token_secret')
     110
     111    print_debug_message("Accessing home page.")
     112    print_debug_message(f"Access Token: {access_token}")
     113    print_debug_message(f"Access Token Secret: {access_token_secret}")
     114
     115    if not access_token:
     116        print_debug_message("No access token found. User not authenticated.")
     117        return render_template('index.html', authenticated=False)
     118
     119    # Example API call: List devices
     120    oauth = get_oauth_session(access_token, access_token_secret)
     121    params = {
     122        'supportedMethods': config.TURNOFF|config.TURNON,  # Example parameter
     123        'format': 'json'
     124    }
     125    print_debug_message(f"Making API call to {config.API_BASE_URL}/devices/list with params {params}")
     126
     127    try:
     128        response = oauth.get(f"{config.API_BASE_URL}/devices/list", params=params)
     129        print_debug_message(f"API Response Status Code: {response.status_code}")
     130        if response.status_code != 200:
     131            flash('Failed to fetch devices.', 'danger')
     132            print_debug_message("Failed to fetch devices from API.")
     133            devices = []
     134        else:
     135            devices = response.json().get('device', [])
     136            print_debug_message(f"Devices fetched: {json.dumps(devices, indent=2)}")
     137    except Exception as e:
     138        flash('An error occurred while fetching devices.', 'danger')
     139        print_debug_message(f"Exception during API call: {str(e)}")
     140        devices = []
     141
     142
     143    return render_template('index.html', authenticated=True, devices=devices)
     144
     145@app.route('/login')
     146def login():
     147    print_debug_message("Initiating OAuth login process.")
     148    oauth = get_oauth_session()
     149    try:
     150        fetch_response = oauth.fetch_request_token(config.REQUEST_TOKEN_URL)
     151        print_debug_message(f"Request Token fetched: {fetch_response}")
     152    except ValueError as e:
     153        flash('Failed to obtain request token.', 'danger')
     154        print_debug_message(f"Failed to fetch request token: {str(e)}")
     155        return redirect(url_for('index'))
     156
     157    session['resource_owner_key'] = fetch_response.get('oauth_token')
     158    session['resource_owner_secret'] = fetch_response.get('oauth_token_secret')
     159    print_debug_message(f"Stored request token in session: {session['resource_owner_key']}")
     160
     161    authorization_url = oauth.authorization_url(config.AUTHORIZE_URL)
     162    print_debug_message(f"Redirecting to authorization URL: {authorization_url}")
     163    return redirect(authorization_url)
     164
     165@app.route('/callback')
     166def callback():
     167    print_debug_message("Handling OAuth callback.")
     168    resource_owner_key = session.get('resource_owner_key')
     169    resource_owner_secret = session.get('resource_owner_secret')
     170
     171    print_debug_message(f"Resource Owner Key: {resource_owner_key}")
     172    print_debug_message(f"Resource Owner Secret: {resource_owner_secret}")
     173
     174    if not resource_owner_key or not resource_owner_secret:
     175        flash('Missing request token.', 'danger')
     176        print_debug_message("Missing request token in session.")
     177        return redirect(url_for('index'))
     178
     179    oauth = get_oauth_session(resource_owner_key, resource_owner_secret)
     180
     181    oauth_response = oauth.parse_authorization_response(request.url)
     182    verifier = oauth_response.get('oauth_verifier')
     183    print_debug_message(f"OAuth Verifier obtained: {verifier}")
     184
     185    if not verifier:
     186        flash('Authorization failed or was denied.', 'danger')
     187        print_debug_message("Authorization failed or was denied by the user.")
     188        return redirect(url_for('index'))
     189
     190    try:
     191        oauth_tokens = oauth.fetch_access_token(config.ACCESS_TOKEN_URL, verifier=verifier)
     192        print_debug_message(f"Access Token fetched: {oauth_tokens}")
     193    except ValueError as e:
     194        flash('Failed to obtain access token.', 'danger')
     195        print_debug_message(f"Failed to fetch access token: {str(e)}")
     196        return redirect(url_for('index'))
     197
     198    session['access_token'] = oauth_tokens.get('oauth_token')
     199    session['access_token_secret'] = oauth_tokens.get('oauth_token_secret')
     200    print_debug_message(f"Stored access token in session: {session['access_token']}")
     201
     202    # Clear request tokens
     203    session.pop('resource_owner_key', None)
     204    session.pop('resource_owner_secret', None)
     205    print_debug_message("Cleared request tokens from session.")
     206
     207    flash('You have successfully logged in.', 'success')
     208    print_debug_message("User successfully authenticated and logged in.")
     209
     210
     211    return redirect(url_for('index'))
     212
     213@app.route('/logout')
     214def logout():
     215    print_debug_message("Logging out user.")
     216    session.pop('access_token', None)
     217    session.pop('access_token_secret', None)
     218    flash('You have been logged out.', 'success')
     219    print_debug_message("Access tokens removed from session.")
     220    return redirect(url_for('index'))
     221
     222@app.route('/devices')
     223def devices():
     224    print_debug_message("Accessing devices route.")
     225    access_token = session.get('access_token')
     226    access_token_secret = session.get('access_token_secret')
     227
     228    print_debug_message(f"Access Token: {access_token}")
     229    print_debug_message(f"Access Token Secret: {access_token_secret}")
     230
     231    if not access_token:
     232        flash('You need to log in first.', 'warning')
     233        print_debug_message("User not authenticated. Redirecting to home.")
     234        return redirect(url_for('index'))
     235
     236    oauth = get_oauth_session(access_token, access_token_secret)
     237    params = {
     238        'supportedMethods': config.TURNON|config.TURNOFF,  # Example parameter
     239        'format': 'json'
     240    }
     241    print_debug_message(f"Making API call to {config.API_BASE_URL}/devices/list with params {params}")
     242
     243    try:
     244        response = oauth.get(f"{config.API_BASE_URL}/devices/list", params=params)
     245        print_debug_message(f"API Response Status Code: {response.status_code}")
     246        if response.status_code != 200:
     247            flash('Failed to fetch devices.', 'danger')
     248            print_debug_message("Failed to fetch devices from API.")
     249            devices = []
     250        else:
     251            devices = response.json().get('devices', [])
     252            print_debug_message(f"Devices fetched: {json.dumps(devices, indent=2)}")
     253    except Exception as e:
     254        flash('An error occurred while fetching devices.', 'danger')
     255        print_debug_message(f"Exception during API call: {str(e)}")
     256        devices = []
     257   
     258
     259    return render_template('devices.html', devices=devices)
     260
     261@app.route('/clients')
     262def clients():
     263    print_debug_message("Accessing clients route.")
     264    access_token = session.get('access_token')
     265    access_token_secret = session.get('access_token_secret')
     266
     267    print_debug_message(f"Access Token: {access_token}")
     268    print_debug_message(f"Access Token Secret: {access_token_secret}")
     269
     270    if not access_token:
     271        flash('You need to log in first.', 'warning')
     272        print_debug_message("User not authenticated. Redirecting to home.")
     273        return redirect(url_for('index'))
     274
     275    oauth = get_oauth_session(access_token, access_token_secret)
     276    params = {
     277        'format': 'json'
     278    }
     279    print_debug_message(f"Making API call to {config.API_BASE_URL}/json/clients/list with params {params}")
     280
     281    try:
     282        response = oauth.get(f"{config.API_BASE_URL}/clients/list", params=params)
     283        print_debug_message(f"API Response Status Code: {response.status_code}")
     284        if response.status_code != 200:
     285            flash('Failed to fetch clients.', 'danger')
     286            print_debug_message("Failed to fetch clients from API.")
     287            clients = []
     288        else:
     289            clients = response.json().get('client', [])
     290            print_debug_message(f"Clients fetched: {json.dumps(clients, indent=2)}")
     291    except Exception as e:
     292        flash('An error occurred while fetching clients.', 'danger')
     293        print_debug_message(f"Exception during API call: {str(e)}")
     294        clients = []
     295   
     296
     297    return render_template('clients.html', clients=clients)
     298
     299if __name__ == '__main__':
     300    app.secret_key = config.SECRET_KEY
     301    app.run(debug=True)
     302
     303}}}
     304
     305
     306== The html templates
     307
     308**The home page**
     309
     310`templates/index.html`
     311
     312{{{
     313<!DOCTYPE html>
     314<html lang="en">
     315<head>
     316    <meta charset="UTF-8">
     317    <title>Telldus Flask App</title>
     318    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
     319</head>
     320<body>
     321<div class="container mt-5">
     322    {% with messages = get_flashed_messages(with_categories=true) %}
     323      {% if messages %}
     324        {% for category, message in messages %}
     325          <div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
     326            {{ message }}
     327            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
     328              <span aria-hidden="true">&times;</span>
     329            </button>
     330          </div>
     331        {% endfor %}
     332      {% endif %}
     333    {% endwith %}
     334
     335    <h1 class="mb-4">Telldus Live! Integration</h1>
     336
     337    {% if not authenticated %}
     338        <p>You are not connected to Telldus Live!.</p>
     339        <a href="{{ url_for('login') }}" class="btn btn-primary">Connect with Telldus</a>
     340    {% else %}
     341        <p>You are connected to Telldus Live!</p>
     342        <a href="{{ url_for('logout') }}" class="btn btn-danger">Logout</a>
     343
     344        <hr>
     345
     346        <h2>Devices</h2>
     347        {% if devices %}
     348            <ul class="list-group">
     349                {% for device in devices %}
     350                    <li class="list-group-item">
     351                        <strong>{{ device.name }}</strong> (ID: {{ device.id }})
     352                        <!-- Add more device details as needed -->
     353                    </li>
     354                {% endfor %}
     355            </ul>
     356        {% else %}
     357            <p>No devices found.</p>
     358        {% endif %}
     359
     360        <hr>
     361
     362        <h2>Clients</h2>
     363        <a href="{{ url_for('clients') }}" class="btn btn-info">View Clients</a>
     364    {% endif %}
     365</div>
     366
     367<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
     368<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.0/dist/js/bootstrap.bundle.min.js"></script>
     369
     370</body>
     371</html>
     372
     373}}}
     374
     375**The devices page**
     376
     377`templates/devices.html`
     378
     379{{{
     380<!DOCTYPE html>
     381<html lang="en">
     382<head>
     383    <meta charset="UTF-8">
     384    <title>Your Devices</title>
     385    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
     386</head>
     387<body>
     388<div class="container mt-5">
     389    {% with messages = get_flashed_messages(with_categories=true) %}
     390      {% if messages %}
     391        {% for category, message in messages %}
     392          <div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
     393            {{ message }}
     394            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
     395              <span aria-hidden="true">&times;</span>
     396            </button>
     397          </div>
     398        {% endfor %}
     399      {% endif %}
     400    {% endwith %}
     401
     402    <h1>Your Devices</h1>
     403    <a href="{{ url_for('index') }}" class="btn btn-secondary mb-3">Back to Home</a>
     404
     405    {% if devices %}
     406        <ul class="list-group">
     407            {% for device in devices %}
     408                <li class="list-group-item">
     409                    <strong>{{ device.name }}</strong> (ID: {{ device.id }})
     410                    <!-- Add more device details as needed -->
     411                </li>
     412            {% endfor %}
     413        </ul>
     414    {% else %}
     415        <p>No devices found.</p>
     416    {% endif %}
     417</div>
     418
     419<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
     420<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.0/dist/js/bootstrap.bundle.min.js"></script>
     421
     422</body>
     423</html>
     424
     425}}}
     426
     427**The clients page**
     428
     429`templates/clients.html`
     430
     431{{{
     432<!DOCTYPE html>
     433<html lang="en">
     434<head>
     435    <meta charset="UTF-8">
     436    <title>Your Clients</title>
     437    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
     438</head>
     439<body>
     440<div class="container mt-5">
     441    {% with messages = get_flashed_messages(with_categories=true) %}
     442      {% if messages %}
     443        {% for category, message in messages %}
     444          <div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
     445            {{ message }}
     446            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
     447              <span aria-hidden="true">&times;</span>
     448            </button>
     449          </div>
     450        {% endfor %}
     451      {% endif %}
     452    {% endwith %}
     453
     454    <h1>Your Clients</h1>
     455    <a href="{{ url_for('index') }}" class="btn btn-secondary mb-3">Back to Home</a>
     456
     457    {% if clients %}
     458        <ul class="list-group">
     459            {% for client in clients %}
     460                <li class="list-group-item">
     461                    <strong>{{ client.name }}</strong> (ID: {{ client.id }})
     462                    <!-- Add more client details as needed -->
     463                </li>
     464            {% endfor %}
     465        </ul>
     466    {% else %}
     467        <p>No clients found.</p>
     468    {% endif %}
     469</div>
     470
     471<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
     472<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.0/dist/js/bootstrap.bundle.min.js"></script>
     473
     474</body>
     475</html>
     476
     477}}}
     478
     479**The authorisation failed page**
     480
     481`templates/authorization_failed.html`
     482
     483{{{
     484<!-- templates/authorisation_failed.html -->
     485
     486<!DOCTYPE html>
     487<html lang="en">
     488<head>
     489    <meta charset="UTF-8">
     490    <title>Authorisation Failed</title>
     491</head>
     492<body>
     493    <h1>Authorization Failed!</h1>
     494    <p><a href="{{ url_for('index') }}">Go back</a></p>
     495</body>
     496</html>
     497
     498}}}
     499