Aby sprawdzić co można uzyskać dla tokena w pełniejszej nieco formie, np do wykorzystania w widgecie WordPressa albo na stronie, możemy użyć prostego Pythonowego skruptu:

# coding=utf-8

import requests
import json


def tes_json_resp(url):
    """

    :param url:
    :return:
    """
    print '==========================================================================='
    print 'pytanie POST - odpowiedź w formacie JSON -kompleksowa, na podstawie tokena'
    print
    data = {'t': 'for_test_cntcXBWYJDQ0H6VwanN3Zrh',
            }
    r = requests.post(url, data=data)
    print r.content


if __name__ == "__main__":

    url = 'http://pogoda.wiks.eu/api.php'
    tes_json_resp(url)

dla miejsc, dla których posiadamy pogodę i prognozę:

{"w":[
{"name":"\u015awinouj\u015bcie",
"dt":"2018-07-16 20:30:00",
"descr_forecast":"w godz.2-3 przelotny deszcz, w godz.5-... umiarkowany deszcz, temp. +18...+21\u00b0C, \u0142agodny...do\u015b\u0107 silny wiatr (3-5B) z kierunk\u00f3w zmiennych: NE, NW, ci\u015bnienie 1026 hPa, zachmurzenie 6-7\/8 , wilgotno\u015b\u0107 96...100 %",
"descr_now":"bezchmurne niebo, temperatura +23\u00b0C, wiatr z kierunku N 2B (ok.3m\/s), ci\u015bnienie 1012hPa, wilgotno\u015b\u0107 49% ",
"urln":"http:\/\/pogoda.wiks.eu\/a\/16210432_7AKPGSDO.wav",
"urlf":"http:\/\/pogoda.wiks.eu\/a\/16210432_R3YV8CUO.wav",
"img_forecast":"http:\/\/pbs.twimg.com\/tweet_video_thumb\/DiP7XOHXcAEQXR8.jpg",
"lat":"53.923","lng":"14.278",
"windspeed":"3","winddir":"360",
"humidity":"49","pressure":"1012",
"clouds":"0","temp":"23"}
],
"f":[
{"name":"\u015awinouj\u015bcie",
"dt":"2018-07-16 18:18:49","for_date":"2018-07-16",
"forec_descr":"temp. +22\u00b0C, \u0142agodny wiatr ( 3B ) z kierunku NE, ci\u015bnienie 1026 hPa, zachmurzenie 6\/8, wilgotno\u015b\u0107 92 %",
"url":"http:\/\/pogoda.wiks.eu\/a\/16181849_CLFSLKYG.wav"},
{"name":"\u015awinouj\u015bcie",
"dt":"2018-07-16 21:20:00","for_date":"2018-07-17",
"forec_descr":"w godz.2-3 i 11-... przelotny deszcz, w godz.5-10 umiarkowany deszcz, temp. +18...+20\u00b0C, \u0142agodny...do\u015b\u0107 silny wiatr (3-5B), z kierunku N, ci\u015bnienie 1024...1026 hPa, zachmurzenie 6-7\/8, wilgotno\u015b\u0107 91...100 %",
"url":"http:\/\/pogoda.wiks.eu\/a\/16212000_76M4KUED.wav"},
{"name":"\u015awinouj\u015bcie",
"dt":"2018-07-16 21:20:01","for_date":"2018-07-18",
"forec_descr":"w godz....-4 umiarkowany deszcz, w godz.5-10 przelotny deszcz, temp. +18...+22\u00b0C, \u0142agodny...umiarkowany wiatr (3-4B), z kierunku N, ci\u015bnienie 1024...1028 hPa, zachmurzenie 1-7\/8, wilgotno\u015b\u0107 84...100 %","url":"http:\/\/pogoda.wiks.eu\/a\/16212000_C4JIZ0HF.wav"},{"name":"\u015awinouj\u015bcie",
"dt":"2018-07-16 21:20:02","for_date":"2018-07-19",
"forec_descr":"bezchmurne niebo, temp. +19...+21\u00b0C, umiarkowany...do\u015b\u0107 silny wiatr (4-5B), z kierunk\u00f3w NW, N, ci\u015bnienie 1028...1030 hPa, wilgotno\u015b\u0107 84...98 %",
"url":"http:\/\/pogoda.wiks.eu\/a\/16212001_LYK9PYBZ.wav"}
]}

– gdzie:
w = pogoda, f = prognoza,
dt = moment opracowania,
for_date = dla prognozy dzień na który jest opracowana,
„urln” i „urlf” = adresy url plików audio odczytów pogody i prognozy,
„img_forecast” = url obrazka (jeśli występuje) lub None/NULL
„lat”:”53.923″,”lng”:”14.278″, = współrzędne geograficzne,
„windspeed”:”3″,”winddir”:”360″, = prędkość i kierunek wiatru,
„humidity”:”49″,”pressure”:”1012″, wilgotność i ciśnienie,
„clouds”:”0″,”temp”:”23″} zachmurzenie i temperatura

oraz dla miejsc, dla których mamy tylko prognozę:

{"p":{
"0":{"name":"Krosno Odrza\u0144skie",
"dt":"2018-07-16 11:03:41","for_date":"2018-07-16",
"forec_descr":"przelotny deszcz, temp. +19...+30\u00b0C och\u0142odzi si\u0119, powiew...s\u0142aby wiatr (1-2B), ci\u015bnienie 1016 hPa, zachmurzenie 3-7\/8 , wilgotno\u015b\u0107 40...87 %",
"url":"http:\/\/pogoda.wiks.eu\/a\/16110340_TGBUNSLC.wav"},
"1":{"name":"Krosno Odrza\u0144skie",
"dt":"2018-07-16 11:03:42","for_date":"2018-07-17",
"forec_descr":"w godz.11-16 przelotny deszcz, w godz.17-... umiarkowany deszcz, temp. +16...+23\u00b0C, s\u0142aby...do\u015b\u0107 silny wiatr (2-5B), z kierunk\u00f3w NW, N, ci\u015bnienie 1015...1016 hPa, zachmurzenie 1-7\/8, wilgotno\u015b\u0107 64...98 %","url":"http:\/\/pogoda.wiks.eu\/a\/16110341_HM7AWIYM.wav"},
"2":{"name":"Krosno Odrza\u0144skie",
"dt":"2018-07-16 11:03:42","for_date":"2018-07-18",
"forec_descr":"w godz....-4 umiarkowany deszcz, w godz.5-13 przelotny deszcz, temp. +17...+24\u00b0C, \u0142agodny...umiarkowany wiatr (3-4B), z kierunk\u00f3w NW, N, ci\u015bnienie 1015...1020 hPa, zachmurzenie 2-7\/8, wilgotno\u015b\u0107 70...99 %",
"url":"http:\/\/pogoda.wiks.eu\/a\/16110342_X4AU9PJJ.wav"},
"3":{"name":"Krosno Odrza\u0144skie",
"dt":"2018-07-16 11:03:43","for_date":"2018-07-19",
"forec_descr":"w godz.14-... bezchmurne niebo, temp. +16...+26\u00b0C, s\u0142aby...umiarkowany wiatr (2-4B), z kierunk\u00f3w NW, N, ci\u015bnienie 1021...1022 hPa, zachmurzenie 0-2\/8, wilgotno\u015b\u0107 48...83 %",
"url":"http:\/\/pogoda.wiks.eu\/a\/16110342_B4PA7PEW.wav"},
"name":"Lulla"}}

Prognoza pogody „p” występuje na kolejne dni „for_date”, zazwyczaj są to 2 do 4 dni.
Prognoza jest przygotowywana raz na około 24h.
„name” występująca wewnątrz to nazwa miejsca dla którego dokładnie jest przygotowana prognoza (np. nazwa powiatu), zaś „name” występująca na końcu – jeden raz to nazwa miejsca leżącego bardzo blisko powyższego powiatu (w jego administracyjnym obrębie), dla której pogodę/prognozę potrzebujemy.

Jak użyć tego np dla WordPress?

Radości!
WikS

run Python script from PHP (2)

Pomyślałem sobie, skoro umiem testować to i wywołać ze strony www poprzez PHP, to może pójść krok dalej? JavaScript?

Da to większe możliwości operacyjności stroną, dzięki temu JavaScript – strona www zyska moc Pythona. No oczywiście – można to zrobić poprzez Django. Ale nie zawsze mamy server Django, czasem po prostu PHP/Apache, który można w ten sposób nomen-omen ubogacić…

Skoro wiem już jak to hula w środku, usunąłem wyświetlanie komentarzy z Pythona i wewnętrznego PHP.

skrypt wewnętrzny Pythona mógłby wyglądać tak:

#!/usr/bin/env python
# coding=utf-8

import os, sys
from datetime import datetime
from time import sleep
import logging.handlers

log_main = logging.getLogger('main')
formatter = logging.Formatter('%(asctime)s.%(msecs)03d  %(levelname)-8s  %(name)-15s  %(message)s',
                       datefmt='%Y-%m-%d %H:%M:%S')
file_handler = logging.handlers.RotatingFileHandler(os.path.join(
                                          os.path.dirname(os.path.abspath(__file__)),
                                          'log.txt'
                                       ),
                                       maxBytes=1000000,
                                       backupCount=3)
file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
log_main.setLevel(logging.DEBUG)
log_main.addHandler(file_handler)
log_main.addHandler(stream_handler)

log_main.info(u'start ' + unicode(datetime.now()) + u' ...')

args = {}
if sys.argv:
    for pair in sys.argv:
        one = pair.split('=', 1)
        if one and len(one) == 2 and one[0]:
            args[one[0].decode('utf-8')] = one[1].decode('utf-8')
args[u'pdt'] = unicode(datetime.now())
args[u'p3g'] = u'Pythona 3 grosze'
sleep(3)
print(args)

log_main.info(u'stop ' + unicode(datetime.now()) + u' ...')

Wprowadzone zmiany to:

– dołożony log w lokalnym pliku <code>log.txt</code> dzięki czemu wiemy co się wewnątrz dzieje, -tutaj jedynie wpisuję czasy startu i stopu, ale można cokolwiek 🙂

– Python dokłada swoje dane do otrzymanych data wejściowych -tutaj pola ‚p3g’ i ‚pdt’

– zakładam, że procedura Pythona będzie trwała chwilę, zanim zwróci wynik do JavyScript (Python –> PHP –> JavaScript) -tutaj 3 sekundy stąd dodane sleep(3)

Tworzenie logu, wiąże się z koniecznością dostępu do pliku logu (folderu w którym jest/będzie utworzony plik logu) – trzeba więc zadbać o uprawnienia do tego katalogu ( chown/chmod )

Właściwie, aby było to (jeszcze) ładniej, obuduję to w klasę:

#!/usr/bin/env python
# coding=utf-8

import os, sys
from datetime import datetime
from time import sleep
import logging.handlers


class PyMain:
    """

    """

    def __init__(self):
        """

        """

        self.log_main = logging.getLogger('main')
        formatter = logging.Formatter('%(asctime)s.%(msecs)03d  %(levelname)-8s  %(name)-15s  %(message)s',
                                      datefmt='%Y-%m-%d %H:%M:%S')
        file_handler = logging.handlers.RotatingFileHandler(os.path.join(
            os.path.dirname(os.path.abspath(__file__)),
            'log.txt'
        ),
            maxBytes=1000000,
            backupCount=3)
        file_handler.setFormatter(formatter)
        stream_handler = logging.StreamHandler()
        stream_handler.setFormatter(formatter)
        self.log_main.setLevel(logging.DEBUG)
        self.log_main.addHandler(file_handler)
        self.log_main.addHandler(stream_handler)
        self.log_main.info(u'init ' + unicode(datetime.now()))

    def clue(self):
        """
        return dict inomming plus some new fields
        :return:
        """

        self.log_main.info(u'start ' + unicode(datetime.now()) + u' ...')

        args = {}
        if sys.argv:
            for pair in sys.argv:
                one = pair.split('=', 1)
                if one and len(one) == 2 and one[0]:
                    args[one[0].decode('utf-8')] = one[1].decode('utf-8')
        args[u'pdt'] = unicode(datetime.now())
        args[u'p3g'] = u'Pythona 3 grosze'
        sleep(3)
        self.log_main.info(u'stop ' + unicode(datetime.now()) + u' ...')
        # print(args)
        return args


if __name__ == "__main__":
    c = PyMain()
    print(c.clue())

Musiałem zmienić z print(args) na return args, print zaś dopiero po wyjściu z metody.

Teraz pora na PHP odpalające …i też odrobina zmian:

<?php
header("Access-Control-Allow-Origin: *");

$log = 'log.txt';
$tolog = 'start';
file_put_contents($log, (new DateTime())->format('Y-m-d H:i:s').' '.$tolog.PHP_EOL, FILE_APPEND | LOCK_EX);

$python_filename = 'php_fired2.py';
$python_dir_path = '/home/wiks/Dokumenty/projects/weather/python2';

$raw_data = file_get_contents('php://input'); 
$data = json_decode( $raw_data, true ); 
$cli_data = ''; 
if($data) { 
    foreach($data as $key => $value) { 
        $cli_data .= '"'.$key.'='.$value.'" '; 
    } 
} 
$command = $python_dir_path.'/'.$python_filename.' '.$cli_data; 
$output_from_python = shell_exec($command); 
echo $output_from_python; 
$tolog = print_r($output_from_python, true); 
file_put_contents($log, (new DateTime())->format('Y-m-d H:i:s').' '.$tolog.PHP_EOL, FILE_APPEND | LOCK_EX); 
$tolog = 'stop'; 
file_put_contents($log, (new DateTime())->format('Y-m-d H:i:s').' '.$tolog.PHP_EOL, FILE_APPEND | LOCK_EX);

Zmiany:

– dodałem log zamiast echo, tak więc interesujące momenty, wartości i zachowania będą umieszczone w pliku log.txt;

– linijka header("Access-Control-Allow-Origin: *"); umożliwiająca dostęp skryptowi JavaScript do tego pliku. Wcześniej dostęp nie był potrzebny, bo usyskiwał go skrypt PHP będący na tym samym serwerze. Teraz dostęp uzyskuje JavaScript – działająca gdziekolwiek na komputerze użytkownika…

 

Właściwie 🙂 , aby było to (jeszcze) ładniej, obuduję to w klasę:

<?php
header("Access-Control-Allow-Origin: *");

class PhpFiredPython {
/**
 *
 */

/** ustala plik logu, zapisuje moment startu
 * ustala zmienne dla klasy
 */
public function __construct() {

    $this->filelog = 'log.txt';
    $this->log('>start');
    $this->python_filename = 'php_fired2.py';
    $this->python_dir_path = '/home/wiks/Dokumenty/projects/weather/python2';
}

/** prowadzi plik logu
 *
 * @param type $tolog
 */
private function log($tolog) {

    file_put_contents($this->filelog,
            (new DateTime())->format('Y-m-d H:i:s').' '.
            $tolog.
            PHP_EOL,
            FILE_APPEND | LOCK_EX);
}

/** wykonuje właściwą pracę - odpala skrypt Pythona
 *
 * @return type
 */
public function get_post_and_fire_python() {

    $raw_data = file_get_contents('php://input');
    $data = json_decode( $raw_data, true );
    $cli_data = '';
    if($data) {
        foreach($data as $key => $value) {
            $cli_data .= '"'.$key.'='.$value.'" ';
        }
    }
    $command = $this->python_dir_path.'/'.$this->python_filename.' '.$cli_data;
    $output_from_python = shell_exec($command);
    $this->log(print_r($output_from_python, true));
    $this->log('>stop');
    //echo $output_from_python;
    return $output_from_python;
    }

}

$c = new PhpFiredPython();
echo $c->get_post_and_fire_python();

Podobnie jak z Pythonem zamieniłem echo na return.

Do szczęścia tego etapu potrzeba jeszcze JavaScript. która to wszystko potrząśnie:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="resp">miejsce na odpowiedź</div>
<?php
require 'url_and_data.php';
$dataj = json_encode( $data );
?>
<script>
    var d1 = new Date();
    console.log('JS start: ' + d1.toLocaleTimeString());
    var xhr = new XMLHttpRequest();
    xhr.open('POST', '<?php echo $url; ?>', true);
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xhr.onload = function () {
        var resp = this.responseText;
        console.log(resp);
        var element = document.getElementById("resp");
        if (resp) {
            element.innerHTML = resp;
            var d2 = new Date();
            console.log('JS stop: ' + d2.toLocaleTimeString());
        }
    };
    xhr.send('<?php echo $dataj; ?>');
</script>
</body>
</html>

Co my tu mamy:

Miejsce w HTML, do którego JS wpisze wynik: <div id="resp">miejsce na odpowiedź</div>, czyli DIV identyfikowany poprzez id=”resp” ,  oraz skrypt, który wywoła XMLHttpRequest metodą POST, na adres -nieładnie wdrukowany przez PHP: <?php echo $url; ?>  i wyśle tam dane w postaci JSON -również niełądnie wdrukowane przez PHP.

Po otrzymaniu zaś odpowiedzi (a będzie to czas który ustaliliśmy na 3 sekundy w skrypcie Pythona) w element DIV o id-„resp” wpisze otrzymaną odpowiedź. Ta sama odpowiedź pojawi się w konsoli przeglądarki (te miejsca, w których wpisujemy console.log(… ).

Na konsoli Firefoxa wygląda to podobnie do:

Albo nawet:

Gdzie ładnie widać co JS wysyła (dane formularza) i co otrzymuje (przedostatnia linijka).

Jak teraz wygląda nasz schemat?

A jak działa?

Otwieramy stronę PHP/HTML, przeglądarka tworzy element DIV z zawartością ‚miejsce na odpowiedź’.

Po wczytaniu skryptu JavaScript wykonuje go, czyli: wysyła dane JSON utworzone przez PHP na adres również utworzony przez PHP.

Dane trafiają do servera i wywołują skrypt PHP. Skrypt wywołuje skrypt Pythona, ten zaś po 3 skundach zwraca odpowiedź -też w formie JSON.

Odpowiedź trafia do PHP a następnie jest zwracana do JavaScript, który po jej odebraniu wykonuje
xhr.onload = function () { odszukuje element DOM o ID=”resp” i wpisuje otrzymaną odpowiedź do jego wnętrza jako HTML.

…to właściwie został mały kroczek do AngularJS, kiedyś to opiszę. Początek będzie zapewne wyglądał tak…

…run Python script from PHP

Kolejnym krokiem – chciałem sprawdzić to co wcześniej udało się osiągnąć, ale w ten sposób, aby miast schematu Python <–> PHP <–> Python użyć PHP <–> PHP <–> Python. Idąc dalej, można sobie wyobrazić, że to pierwsze PHP to strona z której wysyłamy zapytanie do (drugiego) PHP etc…

Rozbudowałem także część opisową, teraz schemat wygląda tak:

kod końcowego skryptu – Pythona:

Python wczytuje argumenty podane przez wywołujący program w linii komend, są to pary klucz=wartość, rozdzielone spacją, sys.argv odczytuje je jako listę, przy czym na pierwszym miejscu [0] występuje ścieżka pliku Pythona.
Dodałem też przedstawienie się versji Pythona (będzie to ważne przy testowaniu np. z venv) i moment startu i końca działania skryptu (wrócimy do tego później).

#!/usr/bin/env python
# coding=utf-8

import sys
from datetime import datetime

print 'Python: start: ' + str(datetime.now()) + '<br>'
args = {}
if sys.argv:
   for pair in sys.argv:
      #print pair
      #print '<br>'
      one = pair.split('=', 1)
      if one and len(one) == 2 and one[0]:
         #print one[0]
         #print '<br>'
         args[one[0].decode('utf-8')] = one[1].decode('utf-8')
print 'Python: Python-version: ' + str(sys.version) + '<br>'
print 'Python: RX-DATA:<br>'
print(args)
print '<br>Python: stop: ' + str(datetime.now()) + '<br>'
print 'Python: tested by: http://blog.wiks.eu'

wyjście Pythona może wyglądać np tak:

Python: start: 2018-05-12 09:10:49.836750
Python: Python-version: 2.7.9 (default, Aug 13 2016, 16:41:35) [GCC 4.9.2]
Python: RX-DATA:
{u'city_name': u'Cz\u0119stochowa', u'lat': u'50.7651978', u'lon': u'19.1587636'}
Python: stop: 2018-05-12 09:10:49.836967
Python: tested by: http://blog.wiks.eu 

kod (środkowego/)’kopiącego’ PHP:
Podobnie jak w Pythonie – przedstawiamy się versją i mierzymy czas.
Wyświetlamy także sposób w jaki wywołujemy Pythona i to co nam Python zwrócił.

<?php
/* PHP running Python
 */
echo 'PHP: PHPversion: '.phpversion().'<br>'."\n"; // 5.6.33-0+deb8u1
echo 'PHP: start: '.date("Y-m-d H:i:s.").gettimeofday()['usec'].'<br>'."\n";
$raw_data = file_get_contents('php://input');
echo 'PHP: RX RAW-DATA: '.gettype($raw_data).' --> '; // string
print_r($raw_data); // {"arg1": "tralala", "arg2": 123.45, "arg3": "unicode str ąśćęłóźć "}
echo "<br>"."\n";
$data = json_decode( $raw_data, true );
echo 'PHP: JSON DECODED: '.gettype($data).' --> '; // string
print_r( $data );
echo "<br>"."\n";
$cli_data = '';
if($data){
    foreach($data as $key => $value) {
        $cli_data .= '"'.$key.'='.$value.'" ';
    }
}
$python_dir_path = '/home/wiks/Dokumenty/projects/weather/python2';
$python_filename = 'php_fired.py';
$command = $python_dir_path.'/'.$python_filename.' '.$cli_data;
echo 'PHP: COMMAND-LINE for Python: '.$command."<br>"."\n";
$output_from_python = shell_exec($command);
echo 'PHP: RX from Python: <br>'."\n";
echo '--------<br>'."\n";
echo $output_from_python;
echo "\n".'<br>--------<br>'."\n";
echo 'PHP: stop: '.date("Y-m-d H:i:s.").gettimeofday()['usec'].'<br>'."\n";
echo 'PHP: tested by: http://blog.wiks.eu'."\n";
?>

kod (pierwszego) PHP/html:
PHP osadzony w kodzie HTML -nadaje się do odpalenia ze strony www. Sprawdziłem działanie na localhost i na zdalnym host -oba postawione na Apache2.

Pobieramy URL i dane do wysłania require 'url_and_data.php';, przedstawiamy się versją, zamieniamy array wejściową na JSON, wysyłamy – przy pomocy cURL /niże też bez cURL/, wyświetlamy to co odebrano. Voila:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test PHP fired Python</title>
</head>
<body>
<?php
require 'url_and_data.php';
echo 'PHP-TEST: PHPversion: '.phpversion().'<br>';
echo 'PHP-TEST: ARRAY TO SEND: ';
print_r($data);
echo '<br>';
$dataj = json_encode( $data );
echo 'PHP-TEST: TX JSON_ENCODE --> ';
print_r($dataj);
echo ' --> TYPE: '.gettype($dataj).'<br>'; // string

$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-type: application/json"));
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $dataj);
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ( $status != 200 ) {
    die("Error: call to URL $url failed with status $status, response $result, curl_error " . curl_error($curl) . ", curl_errno " . curl_errno($curl));
}
curl_close($curl);
print 'PHP-TEST: RX-result: (with curl:)<br>';

print '------------<br>';
print($result);
print '<br>------------<br>';
print 'PHP-TEST: tested by: http://blog.wiks.eu';
?>
</body>
</html>

Pod require 'url_and_data.php' kryją się definicje URL i DATA do przesłania, np:

<?php 
$url = 'http://localhost/run_python.php'; 
$data = array('city_name' => 'Częstochowa',
              'lat' => '50.7651978', 
              'lon' => '19.1587636'
              );        

w powyższych danych przykładowo – przesyłam nazwę miejscowości i współrzędne geograficzne…

Można to oczywiście też wykonać bez cURL`a:

echo ' --> TYPE: '.gettype($dataj).'<br>'; // string

// use key 'http' even if you send the request to https://...
$options = array(
    'http' => array(
        'header'  => "Content-type: application/x-www-form-urlencoded\r\n",
        'method'  => 'POST',
        'content' => $dataj 
    )
);
$context  = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if ($result === FALSE) { // Handle error  
}
print 'PHP-TEST: RX-result: (curl-less:)<br>';

wynik działania:

PHP-TEST: PHPversion: 5.6.33-0+deb8u1
PHP-TEST: ARRAY TO SEND: Array ( [city_name] => Częstochowa [lat] => 50.7651978 [lon] => 19.1587636 )
PHP-TEST: TX JSON_ENCODE --> {"city_name":"Cz\u0119stochowa","lat":"50.7651978","lon":"19.1587636"} --> TYPE: string
PHP-TEST: RX-result: (with curl:)
------------
PHP: PHPversion: 5.6.33-0+deb8u1
PHP: start: 2018-05-12 09:10:49.818623
PHP: RX RAW-DATA: string --> {"city_name":"Cz\u0119stochowa","lat":"50.7651978","lon":"19.1587636"}
PHP: JSON DECODED: array --> Array ( [city_name] => Częstochowa [lat] => 50.7651978 [lon] => 19.1587636 )
PHP: COMMAND-LINE for Python: /home/wiks/Dokumenty/projects/JS_PHP_Python/clue/python/php_fired.py "city_name=Częstochowa" "lat=50.7651978" "lon=19.1587636"
PHP: RX from Python:
--------
Python: start: 2018-05-12 09:10:49.836750
Python: Python-version: 2.7.9 (default, Aug 13 2016, 16:41:35) [GCC 4.9.2]
Python: RX-DATA:
{u'city_name': u'Cz\u0119stochowa', u'lat': u'50.7651978', u'lon': u'19.1587636'}
Python: stop: 2018-05-12 09:10:49.836967
Python: tested by: http://blog.wiks.eu
--------
PHP: stop: 2018-05-12 09:10:49.838728
PHP: tested by: http://blog.wiks.eu
------------
PHP-TEST: tested by: http://blog.wiks.eu

…a gdyby tak zrobić to z JavaScript?

Ciekawostka, którą potrzebowałem niedawno wykorzystać.

Uruchomienie skryptu Pythona poprzez PHP, czyli np. uruchomienie strony www.

Serwer pracujący pod Apache2, PHP chyba 5.6 (zresztą to nie ma chyba większego znaczenia), Python 2.7 w tym wypadku.

w PHP wygląda to tak:

<?php 
echo '...'.date("Y-m-d H:i:s").'<br>';
$only_path = '/home/wiks/Dokumenty/projects/weather/python2/php_fired.py'; $command = escapeshellcmd($only_path); $output = shell_exec($command); echo $output; echo '<br>'.date("Y-m-d H:i:s").' <-- już :-) ';

* jeśli miast shell_exec() byłoby exec(), otrzymalibyśmy na wyjściu tylko ostatnią wyprintowaną linijkę, a chcielibyśmy przecież całość…

zaś w Pythonie: php_fired.py

#!/usr/bin/env python
# coding=utf-8

[...]

# output poprzez zwykłe:
print('coś tam')  # to działa, ale:
# print(u'coś tam') # powoduje błąd, więc trzeba tak:
print(u'coś tam'.encode('utf-8'))  # to działa

# ja zaś potrzebowałem zamiany tekstu na mowę, i po kilku próbach optymalne jest takie rozwiązanie:
# - przy czym tekst wejściowy jest UNICODE

c = 'espeak -v pl -w ' + fpath + '/' + fname + ' -k5 "%s" ' % text2spaech
execute_unix(c.encode('utf-8'))

W powyższym przypadku tekst z PRINT jest drukowany w przeglądarce.

Oczywiście Python musi być wykonywalny (chmod +x php_fired.py) – w moim wypadku przez www-data, bardzo istotna jest pierwsza linijka w skrypcie Pythona:
#!/usr/bin/env python jeśli używamy virtual-env to czasem nie jest ona potrzebna, w tym wypadku jednak tak.

Kolejnym pytaniem jak przesłać dane do PHP a później przekazać je do Pythona?

Ja testowałem zrobiłem to tak:

Python tworzy dict, zamienia go na JSON i wywołuje request.post z tymi danymi, request.post wywołuje PHP, które przekazuje dane POST/JSON do skryptu Pythona, który testowo zwraca je do PHP poprzez print, zaś PHP poprzez echo do testującego skryptu Python’a 🙂

testujący skrypt Pythona:

– tworzy dane:

post_data = {'arg1': 'tralala',
             'arg2': 123.45,
             'arg3': 'ułnicode str ąśćęłóźć ',
             'arg4': u'ułnicode str ąśćęłóźć ',
             }

i wysyła pod:

url = 'http://localhost/run_python.php'
res = requests.post(url, json.dumps(post_data))

pod powyższym adresem jest PHP, które odbiera poprzez:

$raw_data = file_get_contents( 'php://input' );
// print_r($raw_data); //{"arg1": "tralala", "arg2": 123.45, "arg3": "u\u0142nicode str \u0105\u015b\u0107\u0119\u0142\u00f3\u017a\u0107 ", "arg4": "u\u0142nicode str \u0105\u015b\u0107\u0119\u0142\u00f3\u017a\u0107 "}
$data = json_decode( $raw_data, true );
// print_r($data);
/* Array
   (
   [arg1] => tralala
   [arg2] => 123.45
   [arg3] => ułnicode str ąśćęłóźć
   [arg4] => ułnicode str ąśćęłóźć
   ) */

przetwarza dane do linijki CLI i wywołuje Pythona:

w poniższy sposób:

$cli_data = '';
foreach($data as $key => $value) {
    $cli_data .= '"'.$key.'='.$value.'" ';
}
$command = '/home/wiks/Dokumenty/projects/weather/python2/php_fired.py '.$cli_data;
// echo $command."\n";
// /home/wiks/Dokumenty/projects/weather/python2/php_fired.py "arg1=tralala" "arg2=123.45" "arg3=ułnicode str ąśćęłóźć " "arg4=ułnicode str ąśćęłóźć "
$output = shell_exec($command);
echo $output; // to jest znów zwracane do Pythona

Python:

print 'argumenty:',
for one in sys.argv:
    print one  

otrzymujemy:

argumenty: /home/wiks/Dokumenty/projects/weather/python2/php_fired.py
arg1=tralala
arg2=123.45
arg3=ułnicode str ąśćęłóźć
arg4=ułnicode str ąśćęłóźć

Trochę czasu mi zajęło dociekanie jak (ładniej) przetworzyć dane JSON Python <–> PHP <–> Python, dzięki tej dokumentacji:

http://php.net/manual/en/security.magicquotes.disabling.php

zwróciłem uwagę na przeszkadzające magic_quotes . Zaiste ‚magic’!. Po wyłączeniu – ominięciu -wszystko przestało być magiczne i stało się bardziej logiczne…

 

Gdyby chcieć to przetestować poprzez PHP, mogłoby to wyglądać tak…

źródła:
https://stackoverflow.com/questions/19735250/running-a-python-script-from-php

https://dev.to/dev_nope/catch-and-parse-json-post-data-in-php-1p56

Radości!
WikS

Czym jest? Najkrócej – pośrednikiem w nawiązaniu połączenia internetowego.

Czym może być? -Pewnego rodzaju parawanem, za którym możemy próbować ukryć się przed tym, kogo oglądamy. Wikipedia, także tutaj.

Gdy odwiedzamy stronę internetową (lub robi to np. pracujący dla nas program), przedstawiamy się i przesyłamy dane o sobie …przeglądarce, urządzeniu, adresie IP etc. Pewne z nich możemy zmyślić, zakłamać, zataić, niektórych zaś, zwyczajnie się nie da.

Serwer proxy może wykonać za nas pewne operacje, przedstawiając się swoim adresem i danymi – zamiast naszych. Taka jest w skrócie idea naszego parawanu.

Przesyłamy informację do proxy, on wysyła je do serwera do którego chcemy dotrzeć, zaś odebraną informację zwraca nam. Jest to usługa, można za nią zapłacić, można też spróbować skorzystać z oferty darmowych serwerów proxy, których jest niezliczona ilość… wystarczy zapytać wujka Google, np tutaj: https://free-proxy-list.net/

Możemy także sprawdzić jak działają, czy zakrywają nas ‚od stóp do głowy’ -vide parawan, czy też coś wystaje…

Zaczynając od początku, albo też od strony serwera do której trafiają zapytania z proxy, napisałem krótki PHPowy skrypcik -stronę, która wyświetla to, co strona otrzyma od naszej przeglądarki gdy ją otworzymy.

Zobaczmy tutaj

środek skryptu wygląda tak:

https://github.com/wiks/proxy/blob/master/php/index.php

to co możemy nakłamać sami to HTTP_USER_AGENT – rodzaj systemu, przeglądarka etc, to zaś, co chcemy ukryć to nasz adres IP…

Jak przetestujemy darmowe proxy? Będziemy każdemu z nich zadawać adres naszego skryptu PHP, zaś proxy powinno wysłać do niego zapytanie i to co otrzyma – zwróci, tak więc sprawdzimy, czy w tym co wróciło nie ma naszego adresu IP.

Wykorzystamy także tabele w bazie danych do przechowywania wyniku i kilku ustawień. Głównym narzędziem odpytującym listę proxy będzie Python:

https://github.com/wiks/proxy/blob/master/python/__init__.py

Pośród sprawdzonych 600 darmowych proxy, 94 zwróciły satysfakcjonujący wynik, nie zawierający źródłowego IP …w pozostałych z za ‚parawanu’ widać albo buty i skarpetki, albo połyskującą łysinę… niedobrze.

 

Po uruchomieniu program ostrzega/przypomina, aby uzupełnić dane, tj: jakieś nagłówki user-agent którymi będzie przedstawiał się serwerowi proxy (tak, nakłamie trochę) – jeśli nie podamy będzie używał ‚Mozilla/5.0’. Program potrzebuje także adresu, który zleci proxy do wywołania (u mnie jest to „http://www.wiks.eu/ip” i -jeśli go nie zablokuję to może być ten).

Następnie adres IP, którego będzie wyszukiwał w treści zwróconej przez proxy, jeśli grak to próbuje go ustalić za pomocą „http://www.wiks.eu/ip/myip.php” (też zastrzegam sobie prawo zablokowania).

Log początku programu:

WARNING put some user agents into "webbrowser_headers" table :-)
WARNING set URL for testing content returned by proxy server, e.q. like "http://www.wiks.eu/ip" :-)
WARNING put some proxy`s data IP:port into table "all" columns: "ipaddress" & "port" to check it :-)

…trzecie ostrzeżenie to oczywiście prośba o wpisanie do bazy adresów serwerów proxy do tesów (użyłem tych z: https://free-proxy-list.net/ )

Radości!

WikS.eu

Dane pogodowe, opracowaliśmy – no, są. Co dalej?

Warto je gdzieś przechować, aby można było odczytać i przetwarzać w widgetach, tworząc obrazy albo zamieniając na mowę… w każdym razie do dalszego przetwarzania.

Załóżmy, że uznaliśmy zewnętrzną bazę danych jako dobre miejsce do przechowywania aktualnej pogody i najaktualniejszej prognozy. Z bazy danych łatwo i przyjemnie będzie je nam wydobyć w zasadzie dowolną technologią… o tym później.

 

Opowiem tutaj o sposobie odczytu a później (!) zapisu przy użyciu API.

Zbudujmy małe API (Application Programming Interface), które będziemy używać do zapisu i odczytu danych do bazy.

Bazą będzie MySQL, formatem zapisu i odczytu JSON. API będzie napędzane przez Flask.

Konfiguracją servera zajęliśmy się w innym miejscu, tutaj wspomnimy jedynie o czubaszku naszej lodowej gAPIóry.

Python ‚wapi.py’:

# -*- coding: utf-8 -*-

from flask import Flask, request, jsonify
from flask_restful import Resource, Api

import weather

app = Flask(__name__)
api = Api(app)

DOCS_PATH = u'http://wiks.eu/docs/api'

class Index(Resource):
    """
    powitalne info api,
    rozdziela ruch GET na poszczególne moduły
    """
    def get(self):
        """
        zwykła pusta GET to przywitanie i wskazanie ścieżki do dokumentacji

        :return:
        """

        result = {'message': 'Hey, docs are here :-)',
                  'url': DOCS_PATH,
                  }
        return jsonify(result)

# ogólna info:
api.add_resource(Index, '/')
api.add_resource(weather.Weather, '/weather')

if __name__ == '__main__':

    log_main.debug(u'APP running ' + unicode(datetime.now()) + u' ...')

    app.run(port=8664)

To co zdefiniowane powyżej to:

kodowanie strony, import bibliotek (w tym naszej weather dla API) , zdefiniowana klasa Index – pokaże opis co tu jest, dzięki temu będzie można opublikować ładną (lub taką sobie) dokumentację dla tych co zbłądzili i chcą z tej API skorzystać…

Dodajemy zasoby obsługujące wywołania (to ‚api.add. …’) dla zdefiniowanego ‚Index’ (gdy po adresie głównym nic nie będzie) oraz dla ‚weather’ (gdy po adresie głównym będzie ‚/weather’), oraz -jeśli to plik główny – odpalamy API – tutaj na porcie 8664 .

Teraz wnętrze pogodowej API, czyli wspomniany wcześniej importowany i dodawany zasób ‚weather’ i ‚weather.Weather’

# -*- coding: utf-8 -*-

from flask import request, jsonify
from flask_restful import Resource, Api

from sqlalchemy import create_engine
from sqlalchemy.engine.url import URL
import db_settings_creds

from datetime import datetime

path_for_create_engine_db = str(URL(**db_settings_creds.DATABASES_API))
db_connect = create_engine(path_for_create_engine_db + '?charset=utf8',
                           pool_pre_ping=True)
db_connect.encoding = 'utf-8'


class Weather(Resource):

    def get(self):
        """
        pytanie o pogodę metodą GET, zwraca:
        aktualną pogodę oraz najbliższą - na 12h prognozę,
        a także prognozę na kilka najbliższych dni

        :return: unicode --> dict --> JSON
        """
        conn = db_connect.connect()
        result = {'place': u'Poland, Świnoujście'}
        # część 1 sza:
        query1 = "SELECT "
        query1 += "* "
        query1 += "FROM `wiks_weather`.`openweather_weather` WHERE 1 ORDER BY `lp` DESC LIMIT 1"
        res1 = conn.execute(query1)
        one_row1 = res1.fetchone()
        w = {}
        if one_row1:
            if 'dt' in one_row1:
                result['dt'] = one_row1['dt'].strftime('%Y%m%d %H%M%S')
            if 'teraz' in one_row1:
                w['now'] = one_row1['teraz']
            if 'forecast' in one_row1:
                w['12h'] = one_row1['forecast']
        result['weather'] = w
        # część 2 ga:
        query2 = "SELECT "
        query2 += "`for_date`, `forec_descr` "
        query2 += "FROM `wiks_weather`.`openweather_my_text_3dforecast` " \
                  "WHERE `for_date` > CURDATE() ORDER BY `lp` DESC LIMIT 5"
        res2 = conn.execute(query2)
        f = {}
        if res2:
            for row2 in res2:
                f[row2['for_date'].strftime('%Y%m%d')] = row2['forec_descr']
        result['forecast'] = f
        return jsonify(result)

Z rzeczy powyżej, o których trzeba wspomnieć:

path_for_create_engine_db = str(URL(**db_settings_creds.DATABASES_API))
db_connect = create_engine(path_for_create_engine_db + ‚?charset=utf8’,
pool_pre_ping=True)
db_connect.encoding = ‚utf-8’

Tworzy – używając narzędzia SQLAlchemy (było wcześniej ‚from sqlalchemy import create_engine’ ) – połaczenie do bazy danych, przy użyciu zapisanych w pliku ‚db_settings_creds’ danych takich jak miejsce bazy, nazwa użytkownika i hasło. Zobaczmy:

DATABASES_API = {
    'drivername': 'mysql',
    'host': 'localhost',  
    'port': 3306,
    'username': 'nazwa_uzytkownika',
    'password': 'hasło_użytkownika',
    'database': 'wiks_weather',
}

API będzie działało na komputerze, na którym jest baza danych, dlatego jako host podajemy localhost (czyli baza jest local z punktu widzenia API). Gdyby -np do testów odpalać API z innego komputera – należy podać tutaj pełne IP.

Jako drivername – mysql, to dlatego, że dzięki SQLAlchemy możemy obsługiwać różne typy baz danych, a teraz chcemy właśnie MySQL.

Dodawany zasób ‚weather.Weather’ to: plik który importowaliśmy ‚weather’ oraz występująca w nim klasa ‚Weather’:

class Weather(Resource) i get(self):

To działa tak: wywołanie (lub wpisanie w okno przeglądarki internetowej) adresu naszego API wraz ‚/weather’ przeniesie nas do metody get klasy Weather, którą powyżej zdefiniowaliśmy.

Sprawdźmy: http://api.wiks.eu/weather

Oczywiście inny będzie nasz adres i możemy w nim zdefiniować cokolwiek…

def get i co w niej?

Połączenie z bazą danych (conn), przygotowanie zmiennej-słownika w którym umieścimy i wyślemy w świat wynik:

result = {‚place’: u’Poland, Świnoujście’}  – tak, tak, w formacie JSON będzie,

polecenie dla bazy danych:

"SELECT * FROM `wiks_weather`.`openweather_weather` WHERE 1 ORDER BY `lp` DESC LIMIT 1"

czyli: pobierz (SELECT) wszystko (*) z (FROM) bazy danych o nazwie (`wiks_weather`) i tabeli o nazwie (`openweather_weather`) gdzie (WHERE) 1 (tutaj oznacza to pobranie absolutnie wszystkich rekordów/ wierszy/ zapisów, bo wszystkie pasują do WHERE 1) uporządkowanych według kolumny ‚lp’ (ORDER BY `lp`) – uporządkowanych malejąco (DESC) i ogranicz wynik do jednego – pierwszego rekordu (LIMIT 1).

Właściwie można było napisać: „SELECT * FROM `wiks_weather`.`openweather_weather`” – wynik taki sam (ale miałbym mniej do opisywania, poza tym -wydało by się zbyt proste…) 🙂

Teraz wykonaj polecenie dla bazy danych (zwane query, a tutaj query1) – (conn.execute(query1)) i pobierz jeden wiersz z wyniku (one_row1 = res1.fetchone()):

res1 = conn.execute(query1)
one_row1 = res1.fetchone()

…następnie z pobranego wiersza wyniku , jeśli zawiera pola (czyli nazwy kolumn w naszej tabeli bazy danych) takie jak ‚dt’ itd., to przypisz ich wartości do pól w utworzonej zmiennej/słowniku ‚w’:

w = {}
if one_row1:
    if 'dt' in one_row1:
        result['dt'] = one_row1['dt'].strftime('%Y%m%d %H%M%S')
    if 'teraz' in one_row1:
        w['now'] = one_row1['teraz']
    if 'forecast' in one_row1:
        w['12h'] = one_row1['forecast']
result['weather'] = w

…dla pola ‚dt’ wykonujemy jeszcze zamianę na postać tekstową daty i czasu (.strftime(‚%Y%m%d %H%M%S’)), wynik zapisuję do ‚result’.

Po tym ‚result’ powinno wyglądać jakoś tak:

{
  "dt": "20180424 093000", 
  "place": "Poland, \u015awinouj\u015bcie", 
  "weather": {
    "12h": "w godz.14-... przelotny deszcz, temp. +9...+13\u00b0C, umiarkowany...do\u015b\u0107 silny wiatr (4-5B) pocz\u0105tkowo SW zmieniaj\u0105cy si\u0119 na W, ci\u015bnienie 1024 hPa, zachmurzenie 5-7/8, wilgotno\u015b\u0107 75...84 %", 
    "now": "bezchmurne niebo, temperatura +12\u00b0C, wiatr z kierunku SW 3B (ok.5m/s), ci\u015bnienie 1012hPa, zachmurzenie 0/8 (0%), wilgotno\u015b\u0107 66% "
  }
}

dalej – # część 2 ga pobieramy w podobny sposób prognozę (forecast) i dodajemy do wyniku.

u mnie wygląda to tak, -sprawdźmy: http://api.wiks.eu/weather

 

Mamy część pierwszą – odczyt, ale dane trzeba wpisać aby móc odczytać, a więc:

Zapis aktualnej pogody dla danego miejsca oznacza, że to co było wcześniej należy usunąć.

Polecenie dla bazy danych:

query2 = "DELETE FROM `wiks_weather`.`openweather_weather` WHERE `name` = '" + weather_now['name'] + "' "

czyli: usuń (DELETE) z bazy danych, tabeli gdzie ‚name’ = jakaś zadana wartość.
Ta zadana wartość to wpisana nazwa miejsca, np ‚Swinoujscie’.

Po tym wpisujemy:

query3 = "INSERT INTO `" + db_name + "`.`openweather_weather` ("
query3 += "`dt_python`, "
query3 += "`dt`, "
query3 += "`name`, `lat`, `lon`, "
query3 += "`opis_id`, "
query3 += "`opisEN`, `wiatr_predk`, `wiatr_kier`, "
query3 += "`wilgotnosc`, `cisnienie`, "
query3 += "`zachmurzenie`, `temp`, "
query3 += "`forecast`, "
query3 += "`teraz`"
query3 += ") VALUES ("
query3 += " '" + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "', "
query3 += " '" + weather_now['dt'] + "', "
query3 += " '" + weather_now['name'] + "', '" + str(weather_now['lat']) + "', '" + str(weather_now['lon']) + "', "
query3 += " '" + str(weather_now['opis_id']) + "', "
query3 += " '" + weather_now['opisEN'] + "', '" + str(weather_now['wiatr_predk']) + "', '" + str(weather_now['wiatr_kier']) + "', "
query3 += " '" + str(weather_now['wilgotnosc']) + "', '" + str(weather_now['cisnienie']) + "', "
query3 += " '" + str(weather_now['zachmurzenie']) + "', '" + str(weather_now['temp']) + "', "
query3 += " '" + rx_content['descr_12h'] + "', "
query3 += " '" + rx_content['descr_now'] + "'"
query3 += ");"

…i tu więcej kodu, ale wszystko dość intuicyjne:

wstaw (INSERT) do bazy danych, tabeli , (nazwy pól objęte nawiasem) wartości (VALUES) – lista wartości objętych nawiasem.

Wartości zostaną przesłane do API, i wpisane do tabeli. Teraz odczyt -jak opisano na początku poda te właśnie wartości.

Trochę pisania było… jak by to wyglądało przy (nieco) lepszym wykorzystaniu narzędzia SQLAlchemy?

Otóż opisywana kolenda DELETE:

query2 = "DELETE FROM `wiks_weather`.`openweather_weather` WHERE `name` = '" + weather_now['name'] + "' "

wygląda tak:

dele2 = delete(self.tbl_openweather_weather,
               self.tbl_openweather_weather.c.name == weather_now['name'])

zaś ostatnia komenda INSERT:

ins = self.tbl_openweather_weather.insert(values=dict(
      dt=weather_now['dt'],
      dt_python=datetime.now(),
      name=weather_now['name'],
      lat=weather_now['lat'],
      lon=weather_now['lon'],
      opisEN=weather_now['opisEN'],
      opis_id=weather_now['opis_id'],
      wiatr_predk=weather_now['wiatr_predk'],
      wiatr_kier=weather_now['wiatr_kier'],
      wilgotnosc=weather_now['wilgotnosc'],
      cisnienie=weather_now['cisnienie'],
      zachmurzenie=weather_now['zachmurzenie'],
      temp=weather_now['temp'],
      forecast=rx_content[0]['descr_12h'],
      teraz=rx_content[0]['descr_now'],
                      )
                    )

…o tym i innych – bardziej zaawansowanych, abstrakcyjnych i bardziej niezależnych sposobach wykorzystania SQLAlchemy napiszę nieco później.

Wróćmy teraz do zapisu pogody do API.

będziemy używać PUT (jednej z metod protokołu HTTP) , a więc w naszym Flaskowym API:

def put(self)

zaczniemy od odczytu tego co przyszło:

request_get_json = request.get_json()

a dalej wrzucimy do bazy danych. Zaraz zaraz, a jeśli ktoś inny wyśle złośliwie dane w taki sposób? Nooo to API je również przyjmie i wpisze do bazy… straszność ogromna!

Jak temu zaradzić?

Należałoby sprawdzić, czy użytkownik może i powinien… pierwszym ze sposobów – znane z czasów jaskiniowych (?) ktoś ty i podaj hasło!, czyli nazwa użytkownika i hasło, albo user i pass. Po stornie nadawcy i odbiorcy muszą być identyczne.

Czyli nadając dodatkowo dwa pola:

CREDS_WEATHER_API = {
    'user': 'jakis_user123',
    'pass': 'jakies_haslo123',

odbieramy również je i porównujemy do spodziewanych:

auth = False
if 'user' in request_get_json \
        and 'pass' in request_get_json \
        and 'content' in request_get_json:
    if request_get_json['user'] == CREDS['user'] \
            and request_get_json['pass'] == CREDS['pass']:
        auth = True

Przy czym nasza ‚auth’ będzie posiadała informacje czy użytkownik może (True) czy nie (False) dokonać zapisu…

no i teraz bułka z masłem, odczyt i do bazy…

Jak wysłać dane do API?

def send_weather_to_remote(content_list):
    """
    wyślij do zdalnego
    
    :param content_list: 
    :return: 
    """

    CREDS = db_settings_local.CREDS_WEATHER_API
    data = {
        'content': content_list,
        'user': CREDS['user'],
        'pass': CREDS['pass'],
    }
    headers = {"Content-Type": "application/json"}
    url = "http://---adres pod który wysyłamy ---"
    r = requests.put(url, data=json.dumps(data), headers=headers)

przy czym w pliku ‚db_settings_local’ zdefiniowałem „kto tam i hasło!”:

CREDS_WEATHER_API = {
    'user': 'jakis_user123',
    'pass': 'jakies_haslo123',
}

Aby nie przesyłać za każdym razem user & pass wymyślono coś, co nazywa się ‚OAuth2’ i nawet jest kilka miejsc gdzie to ładnie opisano.  Polecam poszukać!

Radości!

WikS.eu

Zamiana kierunku wiatru wyrażonego w stopniach, na opisanego ‚ładniej’ -w nieco nawigacyjny sposób, mogłaby być w Pythonie wykonana następująco:

def wind_direction2descr_old(wind_dir):
"""
po prostu zmieniam wartość kątową na opisową kierunku wiatru
:param wind_dir: liczbowa wartość w stopniach
:return: unistring 1 lub dwie literki
"""
wind_dir_descr = u''
if wind_dir is not None and isinstance(wind_dir, (int, long, float)):
wind_dir = round(wind_dir % 360, 0)
if wind_dir < 22.5 or wind_dir >= (360 - 22.5):
wind_dir_descr = u'N'
dir_wokolo = 45
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'NE'
dir_wokolo = 90
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'E'
dir_wokolo = 135
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'SE'
dir_wokolo = 180
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'S'
dir_wokolo = 225
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'SW'
dir_wokolo = 270
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'W'
dir_wokolo = 315
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'NW'
return wind_dir_descr

Czyli okrąg-tort – 360 stopni umownie podzielony na 8 równych kawałków, ten na górze N – jak północ, na dole S – południe, po bokach E i W oraz pomiędzy nimi: NE SE SW NW. Osiem kawałków razem, licząc od góry – zgodnie ze wskazówkami zegara da nam: ‚N’ ‚NE’ ‚E’ ‚SE’ ‚S’ ‚SW’ ‚W’ ‚NW’.
Gdy 360 stopni na 8 otrzymamy 45, tak więc dla 0 stopni ( +/- połowa 45 = 22.5 stopnia ), czyli od -22.5 do +22.5 daje N , 45 (+/- 22.5) czyli 22.5 do 67.5 daje NE itd.
Funkcja powyżej w ten właśnie sposób zamienia wartość kąta na jego nawigacyjny opis kierunku.

Wcześniej sprawdzamy, czy wejściowa dana istnieje i jest liczbą – aby uniknąć błędów, jeśli jest poza zakresem 0 – 360 stopni to przywracamy ją do tego zakresu, zaokrąglamy i porównujemy aby określić opis słowny.

Można to też zrobić tak:

def wind_direction2descr(wind_dir):
"""
po prostu zmieniam wartość kątową na opisową kierunku wiatru
:param wind_dir: liczbowa wartość w stopniach
:return: unistring 1 lub dwie literki
"""
wind_dir_descr = u''
if wind_dir is not None and isinstance(wind_dir, (int, long, float)):
index = int(((float(wind_dir) + 22.5) % 360) / 45) % 8
wind_dir_descr = [u'N', u'NE', u'E', u'SE', u'S', u'SW', u'W', u'NW', ][index]
return wind_dir_descr

…czyli przeliczyć wind_dir na liczbę 0…7, która będzie indexem tablicy oznaczeń kierunków i da nam oczekiwany wynik.

Zaraz, zaraz, jak sprawdzić, czy wynik będzie oczekiwany?
Może przy pomocy testu?

wind_dir = 0
wind_dir_descr = wind_direction2descr(wind_dir)
print wind_dir, '-->', wind_dir_descr

…można to sprawdzać na piechotę oczywiście, można też napisać ładny Python’owy test.

Na piechotę:

Test:

# -*- coding: utf-8 -*-

# TODO reload utf-8
import sys
reload(sys)
sys.setdefaultencoding("utf-8")

from datetime import datetime

import describe_weather

try:
    import unittest2 as unittest
except ImportError:
    import unittest


class DescribeWeatherTest(unittest.TestCase):

    def test_wind_direction2descr(self):

        wind_dir_test_list = [
            [0, u'N'],
            [360, u'N'],
            [22.4, u'N'],
            [337.6, u'N'],

            [45 - 22.4, u'NE'],
            [45 + 22.4, u'NE'],
            [90 - 22.4, u'E'],
            [90 + 22.4, u'E'],
            [135 - 22.4, u'SE'],
            [135 + 22.4, u'SE'],
            [180 - 22.4, u'S'],
            [180 + 22.4, u'S'],
            [225 - 22.4, u'SW'],
            [225 + 22.4, u'SW'],
            [270 - 22.4, u'W'],
            [270 + 22.4, u'W'],
            [315 - 22.4, u'NW'],
            [315 + 22.4, u'NW'],

            [None, u''],
        ]

        for test_case in wind_dir_test_list:
            wind_dir = test_case[0]
            expected = test_case[1]
            res = describe_weather.wind_direction2descr(wind_dir)
            self.assertIsInstance(res, unicode, u'nie jest UNICODE')
            self.assertEqual(res, expected, u'oczekiwano innej wartości: ' + unicode(expected)
                             + u' otrzymano: ' + unicode(res) + u' dla kierunku: ' + unicode(wind_dir))
        return None

Od początku:

# -*- coding: utf-8 -*-

– sposób kodowania znaków użyty tutaj, oraz

import sys
reload(sys)
sys.setdefaultencoding("utf-8")

– sposób kodowania użyty w wytworzonym pliku wynikowym testu,

import describe_weather

– pobieram plik, w którym zawarta jest opisana wyżej funkcja,

import unittest

– pobieram pliki wykonujące tworzone testy, tzw testy jednostkowe lub ‚unit-testy’

Na bazie pobranych unit-testów tworzę klasę:

class DescribeWeatherTest(unittest.TestCase):

a w niej metodę / funkcję wykonującą test na opisanej powyżej mojej funkcji zamiany stopni na opis kierunku wiatru:

def test_wind_direction2descr(self):

nazwa klasy tworzona jest w sposób CamelCasing, czyli wielbłądzi – ma garby (wielkie litery) jako rozdzielenie wyrazów i Test w nazwie (np. ‚OpiszMojTest’ albo ‚OpiszJakisDziwnyNieModTest’…), nazwa metody – małymi literami podkreślnik jako rozdzielenie i na końcu ‚_test’.
Taka konwencja pozwala łatwiej zrozumieć …niezrozumiałe.
Co wewnątrz testu?
Mam do przetestowania funkcje, chcę jej zadawać wartości kąta i sprawdzać, czy otrzymany wynik jest zgodny z oczekiwaniami. Np jeśli zadam 0 (zero), chciałbym uzyskać ‚N’.
Zapiszmy to jako jeden przypadeczek testowy:
[0, u’N’]
(literka u oznacza kodowanie jako unicode, choć tutaj nie ma większego znaczenia…)

jeśli zebrać przypadeczki razem i ułożyć z nich listę otrzymamy:

wind_dir_test_list = [
    [0, u'N'],
    [360, u'N'],
    [22.4, u'N'],
    [337.6, u'N'],

    [45 - 22.4, u'NE'],
    [45 + 22.4, u'NE'],
    [90 - 22.4, u'E'],
    [90 + 22.4, u'E'],
    [135 - 22.4, u'SE'],
    [135 + 22.4, u'SE'],
    [180 - 22.4, u'S'],
    [180 + 22.4, u'S'],
    [225 - 22.4, u'SW'],
    [225 + 22.4, u'SW'],
    [270 - 22.4, u'W'],
    [270 + 22.4, u'W'],
    [315 - 22.4, u'NW'],
    [315 + 22.4, u'NW'],

    [None, u''],
]

…gdzie pierwszy element to zadawana wartość, drugi – oczekiwana odpowiedź.

Jak testujemy?

for test_case in wind_dir_test_list:
    wind_dir = test_case[0]
    expected = test_case[1]
    res = describe_weather.wind_direction2descr(wind_dir)
    self.assertIsInstance(res, unicode, u'nie jest UNICODE')
    self.assertEqual(res, expected, u'oczekiwano innej wartości: ' + unicode(expected)
                     + u' otrzymano: ' + unicode(res) + u' dla kierunku: ' + unicode(wind_dir))
return None

 

Pętla pobierająca jeden przypadeczek do ‚test_case’, gdzie ‚test_case[0]’ to zadawana wartość, a test_case[1] – oczekiwany wynik,

… wykonanie funkcji (dwa człony nazwy – pierwszy to nazwa zaimportowanego pliku ‚describe_weather’, drugi – ‚wind_direction2descr’ -nazwa funkcji oraz w nawiasach dana wejściowa )
wynik zwracany do zmiennej ‚res’ – jak resource (zasoby) oraz porównywana -to już narzędzia unit-testów:

assertIsInstance – czy jest typu unicode (bo tylko taki akceptujemy zgodnie z założeniami),
oraz:
assertEqual – czy to co uzyskano ‚res’ jest identyczne (equal -> równe) z oczekiwamym ‚expected’.

W obu ‚assert`ach’ sprawdzających, ostatnią wartością jest komunikat tekstowy, który pojawia się gdy wynik jest inny niż oczekiwano.

 

Na końcu, aby wykonać wszystkie testy:

if __name__ == '__main__':

    # --- jeden sposób:
    # unittest.main()
    # --- drugi sposób (chyba ładniejszy no i dla Jenkinsa):
    import xmlrunner
    unittest.main(testRunner=xmlrunner.XMLTestRunner(output='test-reports'))

…i wykonanie (poprzez run dla PyCharm).

Narzędzie Unittest wygeneruje raport:

Running tests...
----------------------------------------------------------------------
..........................
----------------------------------------------------------------------
Ran 26 tests in 0.003s

OK

Generating XML reports...

…także do pliku:

<?xml version="1.0" ?>
<testsuite errors="0" failures="0" name="DescribeWeatherTest-20180423140707" tests="26" time="0.003">
   <testcase classname="DescribeWeatherTest" name="test_describe_weather_teraz" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_for_effect_id_find_hours_ranges" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_integer_to_effect_description" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_cisnienie" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_cisnienie_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_dodatkowo_efekty_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_dodatkowo_efekty_prognozy" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_prognoze_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_temperature_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_temperature_prognozy" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_wiatr_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_wiatr_prognoze" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_wilgotnosc_popoludnia" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_zachmurzenie_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_zachmurzenie_popoludnia" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opracuj_pobrana_prognoze_max" time="0.001"/>
   <testcase classname="DescribeWeatherTest" name="test_opracuj_pobrana_prognoze_najblizsza" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_semi_zachmurzenie_percent2_octascale" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_special" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_temperatura_ze_znakiem" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_wind_boufort2descr_pl" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_wind_direction2descr" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_wind_speed_mps_2boufort" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_zachmurzenie_percent2_octascale" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_zamien_tylko_interesujace_efekty_atmosferyczne_na_opis" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_zapamietaj_okres_od_do" time="0.000"/>
   <system-out>
<![CDATA[]]>   </system-out>
   <system-err>
<![CDATA[]]>   </system-err>
</testsuite>

Opis unittestów można znaleźć tutaj: https://docs.python.org/2/library/unittest.html

Co do wiatru, mamy jeszcze jego prędkość, można ją wyrażać w stopniach Bouforta, opisowo a także zwyczajnie – w metrach na sekundę…

Zamiana prędkości na stopnie Bouforta – w zależności od zakresu – bułka z masłem,

zaś stopnie Bouforta (z pewnością pamiętacie że zapisujemy inaczej niż stopnie Celsjusza – mianowicie bez znaczka stopni, tylko samo B) na opis, to już kiełbaska na tym maśle 🙂 :

def wind_boufort2descr_pl(wind_boufort):
    """
    zamień stopnie bouforta na opis
    :param wind_boufort: liczba całkowita 0...12
    :return: unistring-opis
    """
    wind_descr = u''
    l_w_d = [u'cisza',
             u'powiew',
             u'słaby wiatr',
             u'łagodny wiatr',
             u'umiarkowany wiatr',
             u'dość silny wiatr',
             u'silny wiatr',
             u'bardzo silny wiatr',
             u'wicher',
             u'silny sztorm',
             u'bardzo silny sztorm',
             u'gwałtowny sztorm',
             u'huragan']
    if wind_boufort is not None and isinstance(wind_boufort, (int, long, float)) and 0 <= wind_boufort <= 12:
        wind_descr = l_w_d[wind_boufort]
    return wind_descr

…oczywiście jakiś test do tej bułki.

http://pogoda.wiks.eu

Radości!

WikS