Funktion/Code läuft nicht durch/Syntax error

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Patricia
User
Beiträge: 14
Registriert: Mittwoch 25. Mai 2022, 08:37

Hallo zusammen

Ich habe einen Code, den ich in Einzelschritten in Jupyter getestet habe und der klappt.

Nun möchte ich das Ganze zu einer Funktion zusammenfassen und mit xlwings dann in Excel laufen lassen.

Jedoch erhalte ich hier nun einen SyntaxError Fehler in Zeile 12 "invalid syntax" (bei "zählerstand.drop...)

Wenn ich zB diesen Schritt mit #glatt stelle, dann kommt der gleiche Fehler in Zeile 13, dann in 14 etc.
Habe das Gefühl, als ob der Code hier in der Funktion zusammengefasst die Einzelschritte nicht ausgibt.
Oder an was könnte es sonst liegen? Hätte evtl. jemand eine Idee?

Code: Alles auswählen


import requests
import pandas as pd
import xlwings as xw

def values_in_past_multiple(deviceId, startdate, enddate):
      
    interval = int(1000)
    base_url = 'https://smart-me.com:443/api/ValuesinPastMultiple'
    auth_data = ('xx', 'xx')
    response = requests.get(f'{base_url}/{deviceId}?startDate={startdate}, &endDate={enddate}&interval={interval}', auth=auth_data)
    response.raise_for_status()
    zählerstand = response.json()
    zählerstand = pd.DataFrame(zählerstand)
    zählerstand['Date'] = pd.to_datetime(zählerstand['Date'].dt.tz_localize(False)
    
    zählerstand.drop('Values', axis=1, inplace=True)
    zählerstand['Values'].apply(pd.Series)[0].apply(pd.Series).drop(columns='Obis')
    zählerstand3 = pd.concat([zählerstand1, zählerstand2], axis=1)
    zählerstand3['Value']=zählerstand['Value'].astype(int)
    zählerstand3 = zählerstand2.pivot(index=['Date'], columns=['DeviceId'])
    
    return zählerstand3
Danke
Patricia
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Zähl mal in der Zeile darüber, wieviele öffnende und schließende Klammern da stehen. Tipp: Beide Zahlen müssen gleich sein.
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patricia: Anmerkungen zum Quelltext: ``int(1000)`` macht keinen Sinn. 1000 ist bereits eine ganze Zahl, die wird durch das `int()` nicht irgendwie ”ganzzahliger”.

Die Parameter würde ich `requests` zusammenbasteln lassen. Das ist übersichtlicher und robuster. Das Komma und das Leerzeichen dürften auch falsch sein.

Namen sollten innerhalb des gleichen Namensraums nicht an Werte mit unterschiedlichen (Duck-)Typen gebunden werden. Also nicht den gleichen Namen an JSON-Daten und `DataFrame`-Objekte. Man muss auch nicht jedes Zwischenergebnis an einen Namen binden.

Man sollte keine Namen nummerieren und `zählerstand1` und `zählerstand2` werden auch gar nicht definiert.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Patricia
User
Beiträge: 14
Registriert: Mittwoch 25. Mai 2022, 08:37

@pillmuncher: Danke - copy paste Fehler!

@blackack:
Was meinst du mit "requests zusammenbasteln"?
Ich habe den code nun erstellt, und er läuft soweit, ausser dass er die Spalte, welche einen dictionary enthält, nicht mehr anpasst.
Was ich versuchen wollte, und im Jupityer Einzelschritt funktioniert das auch, ist folgendes:

Ich habe die Spalte "Values". Diese sieht so aus - enthält also einen Dictionary:
[{'Obis': '1-0.1.8....', 'Value": "3954..."{]
Nun möchte ich von dieser Spalte lediglichen den Wert aus dem Value auslesen, also im Beispiel hier zB 3954...

Im Jupyter mit Einzelschritten hat das mit diesem Code funktioniert:

Code: Alles auswählen

    zählerstand['Values'].apply(pd.Series)[0].apply(pd.Series).drop(columns='Obis')
Aber in der Funktion geht das nicht, ich erhalte als Ausgabe immer noch die Spalte mit den Dictionary Werten.

Hier nochmals mein abgeänderter Code:

Code: Alles auswählen

@xw.func
#@xw.arg("deviceId", expand = "right")
@xw.ret(index = True, header = True)
def values_in_past_multiple(deviceId, startdate, enddate):
    interval = 1000
    base_url = 'https://smart-me.com:443/api/ValuesinPastMultiple'
    auth_data = ('xx', 'xx')
    response = requests.get(f'{base_url}/{deviceId}?startDate={startdate}&endDate={enddate}&interval={interval}',auth=auth_data)
    response.raise_for_status()
    zählerstand = response.json()
    zählerstand = pd.DataFrame(zählerstand)
    zählerstand['Date'] = pd.to_datetime(zählerstand['Date'])
    zählerstand['Date'] = zählerstand['Date'].dt.tz_localize(None)
    zählerstand['Values'].apply(pd.Series)[0].apply(pd.Series).drop(columns='Obis')
    #zählerstand.drop('Values', axis=1, inplace=True)
    #zählerstand3 = pd.concat([zählerstand1, zählerstand2], axis=1)
    #zählerstand['Value']=zählerstand['Value'].astype(int)
    zählerstand = zählerstand.pivot(index=['Date'], columns=['DeviceId'])
    
    return zählerstand
An was könnte das nun wieder liegen?
Danke für deine Hilfe!
Patricia
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das Modul requests erlaubt dir, Parameter als Woerterbuch zu uebergeben, und sorgt dann dafuer, dass die HTTP-konform verschickt werden. Du machst das hingegen selbst, und das auch noch mit Fehlern darin, wie zB dem Leerzeichen vor &endDate.
einfachTobi
User
Beiträge: 491
Registriert: Mittwoch 13. November 2019, 08:38

Ich vermute, dass du die Umformung komplizierter machst als es sein müsste. Kannst du mal als Beispiel eine vollständige Antwort der API (JSON) posten und wie das Endergebnis aussehen soll?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ich würde die Umformung einfach in Python vornehmen. Ungetestet:

Code: Alles auswählen

@xw.func
# @xw.arg("device_id", expand="right")
@xw.ret(index=True, header=True)
def values_in_past_multiple(device_id, start_date, end_date):
    url = "https://smart-me.com/api/ValuesinPastMultiple"
    response = requests.get(
        f"{url}/{device_id}",
        params={
            "startDate": start_date,
            "endDate": end_date,
            "interval": "1000",
        },
        auth=("xx", "xx"),
        timeout=None,
    )
    response.raise_for_status()

    return pd.DataFrame.from_records(
        [
            {
                "Date": pd.to_datetime(record["Date"]).tz_localize(None),
                **{item["Obis"]: item["Value"] for item in record["Values"]},
            }
            for record in response.json()
        ]
    )
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Patricia
User
Beiträge: 14
Registriert: Mittwoch 25. Mai 2022, 08:37

@deets: es ist eine API Abfrage und ich muss doch im request genau angeben, was ich abfragen möchte? Die Leerzeichen habe ich nun entfernt, hatte aber keinen Einfluss.

https://smart-me.com/swagger/ui/index#! ... ltiple_Get


@Tobi
Ich hoffe das klappt so mit der Anzeige des Bildes?
So sieht die Tabelle aus mit der aktuellen Abfrage:
Bildhttps://i.imgur.com/LZVvK5H.png

So sieht die Tabelle aus wenn ich es in Einzelschritten in Jupyter habe laufen lassen - und das wäre auch mein gewünschter output.
Bildhttps://i.imgur.com/MQjFRvB.png

Wenn ich den Code umschreibe so wie blackjack ihn mir eben gesandt hat, sieht das Resultat so aus:

Code: Alles auswählen

@xw.func
# @xw.arg("device_id", expand="right")
@xw.ret(index=True, header=True)
def values_in_past_multiple(device_id, start_date, end_date):
    url = "https://smart-me.com/api/ValuesinPastMultiple"
    response = requests.get(
        f"{url}/{device_id}",
        params={
            "startDate": start_date,
            "endDate": end_date,
            "interval": "1000",
        },
        auth=(email.ch", " passwott"),
        timeout=None,
    )
    response.raise_for_status()

    return pd.DataFrame.from_records(
        [
            {
                "Date": pd.to_datetime(record["Date"]).tz_localize(None),
                **{item["Obis"]: item["Value"] for item in record["Values"]},
            }
            for record in response.json()
        ]
    )
      
Bildhttps://i.imgur.com/9pQWOL4.png

..bin etwas verloren Leute..
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich habe mal sehr schnell die Auth-Daten geändert. Die hattest du im
Klartext da stehen. Besser ändern.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patricia: Welchen von den Obis-Werten hast Du denn in Deinem Wunschergebnis genommen? Oder sind die etwa alle in einer Spalte ohne die Information was die Zahl denn nun eigentlich bedeutet? Oder hast Du irgendwo in Deinem Code einen einzigen Obis-Wert heraus gepickt? Anhand eines Indexwertes? Sicher, dass der garantiert ist? Denn die Werte haben ja nicht ohne Grund eine Obis-ID um sie zu idenzifizieren. *Die* sollte dann auch beim Selektieren angewendet werden.

Eine Spalte in der für jeden Wert immer die gleiche Geräte-ID steht, kann man bei meinem Code leicht ergänzen, aber warum? Die ist doch für alle Werte gleich‽
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Patricia
User
Beiträge: 14
Registriert: Mittwoch 25. Mai 2022, 08:37

Ich möchte jeweils nur den ersten Obis Wert..
Bildhttps://i.imgur.com/XGk03UP.png

Ich verstehe einfach nicht, weshalb das im Einzelschritt funktioniert und mit der Funktion nicht..
Wenn ich es in Excel auslesen mit den Einzelschritten, erhalte ich auch das richtige Resultat!
Bildhttps://i.imgur.com/7IQGrhs.png

Ich möchte dann am Ende eine Liste, wo viele verschiedene Geräte ID's abgefragt werden.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Dann mußt Du doch nur nach dem richtigen Obis-Wert filtern:

Code: Alles auswählen

@xw.func
# @xw.arg("device_id", expand="right")
@xw.ret(index=True, header=True)
def values_in_past_multiple(device_id, start_date, end_date):
    url = "https://smart-me.com/api/ValuesinPastMultiple"
    response = requests.get(
        f"{url}/{device_id}",
        params={
            "startDate": start_date,
            "endDate": end_date,
            "interval": "1000",
        },
        auth=("xx", "xx"),
        timeout=None,
    )
    response.raise_for_status()

    return pd.DataFrame.from_records(
        [
            (
                pd.to_datetime(record["Date"]).tz_localize(None),
                next(item["Value"] for item in record["Values"] if item["Obis"] == "1-0:1.8.0*255"),
            ) for record in response.json()
        ], columns=['date', value']
    )
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patricia: Wenn Du den ersten Obis-Wert aus der Übersichtstabelle willst, dann ist Dein bisheriges Ergebnis nur zufällig richtig, denn Du nimmst den zweiten Obis-Wert, der offenbar immer den gleichen Wert wie der erste Obis-Wert hat.

Ungetestet:

Code: Alles auswählen

@xw.func
# @xw.arg("device_id", expand="right")
@xw.ret(index=True, header=True)
def values_in_past_multiple(device_id, start_date, end_date):
    url = "https://smart-me.com/api/ValuesinPastMultiple"
    response = requests.get(
        f"{url}/{device_id}",
        params={
            "startDate": start_date,
            "endDate": end_date,
            "interval": "1000",
        },
        auth=("xxx", "xxx"),
        timeout=None,
    )
    response.raise_for_status()

    return pd.DataFrame.from_records(
        [
            {
                "DeviceId": device_id,
                "Date": pd.to_datetime(record["Date"]).tz_localize(None),
                "Value": next(
                    item["Value"]
                    for item in record["Values"]
                    if item["Obis"] == "1-0:1.8.0*255"
                ),
            }
            for record in response.json()
        ]
    )
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Patricia
User
Beiträge: 14
Registriert: Mittwoch 25. Mai 2022, 08:37

@Sirius3
Bingo!! Nun klappt es!!
Puh aber das hätte ich alleine nie rausgekriegt!!
Bildhttps://i.imgur.com/i47JoPH.png
Schade nur dass ich keine Abfrage mit kürzeren Intervallen machen kann. Die API erlaubt das nicht. Denn die Werte werden eigentlich alle 15 Min abgelesen.
VIELEN DANK!!!!
einfachTobi
User
Beiträge: 491
Registriert: Mittwoch 13. November 2019, 08:38

Die API erlaubt auch kürzere Intervalle. Die von dir gepostete Doku sagt: The interval in minutes betwenn the values. 0 means as fast as possible. Only 1000 values can be get in one call.. Du musst dann lediglich den Zeitraum kleiner wählen, damit die 1000 Werte mit dem gewünschten Intervall da rein passen.
Patricia
User
Beiträge: 14
Registriert: Mittwoch 25. Mai 2022, 08:37

@Tobi
Danke dir auch für diese Rückmeldung!
Ich brauche diese Werte für die Abrechnung der Energiebezüge und diese sind jeweils 6 Monate und ich würde es gerne auch grafisch darstellen. D.h. ich muss da wohl mehr als eine Abfrage machen und diese dann zusammenfügen... bleibt mir noch einiges zu tun:-)!
Beste Grüsse
Patricia
Antworten