Restful API mit Eve - Fehler bei der Type Validation

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
Tholo
User
Beiträge: 177
Registriert: Sonntag 7. Januar 2018, 20:36

Hej an Alle,

Ich verdinge mich gerade an einem API Script und schaue mir dazu das Eve Api Framework an. Dieses basiert auf Flask.
In der Docu und dem Webinar von TalkPython wird folgendes beschrieben.

Code: Alles auswählen

from eve import Eve
import re
from eve.io.mongo import Validator

class MyValidator(Validator):
    """ You can extend or override the built-in validation rules easily
    by inheriting from the eve.io.mongo.Validator class
    """
    def _validate_type_email(self, email, field, value):
        """ Extend types by adding a new 'email' type """
        if not re.match(r"[^@]+@[^@]+\.[^@]+", value):
            if email:
                self._error(field, 'Value is not a valid email address')
Die Idee eine Post Request vom Client zb

Code: Alles auswählen

{"lastname": "basasdasdasdls","email": "das@tholo.de"}
trägt das ganze in die DB ein. Überprüft voher via regex ob es eine valide Mailadresse ist.

nur Leider kann ich den folgenden Fehler kaum nachvollziehen.

Code: Alles auswählen

{
    "_status": "ERR",
    "_issues": {
        "exception": "_validate_type_email() missing 2 required positional arguments: 'field' and 'value'"
    },
    "_error": {
        "code": 422,
        "message": "Insertion failure: 1 document(s) contain(s) error(s)"
    }
}
in der settings.py hab ich folgendes Schema gesetz ( wie auch im Repo von Github)

Code: Alles auswählen

people = {
    # A class with the authorization logic for the endpoint. If not provided
    # the eventual general purpose auth class will be used.
    # 'authentication': MyBasicAuth,
    # When False, this option disables HATEOAS for the resource.
    # Defaults to True.
    #'hateoas': False,
    # A dict defining the actual data structure being handled by the resource.
    # Enables data validation.
    'schema': {
        'firstname': {
            'type': 'string',
            'minlength': 1,
            'maxlength': 30,
        },
        'lastname': {
            'type': 'string',
            'maxlength': 50,
            'required': True,
            'unique': True,
         }
        # dieses Regex funtioniert!
        #'email': {'type': 'string',
         #         'regex': '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
         #------------
         # this only works when app.MyValidator is used, as it brings support
        # for validating a field of type 'email'
        #Error in Api! "exception": "_validate_type_email() missing 2 required positional arguments: 'field' and 'value'"
        'email': {'type': 'email'} # Irgendwas fehlt hier...
    }
Ich habe das Script mit dem Git Repo verglichen
https://github.com/talkpython/eve-build ... pis-course erkenne aber keinen Unterschied. Auch die Doco von http://python-eve.org/] Eve und Cerebus haben mich nicht weiter gebracht.
Mir ist bewusst das die Klasse MyValidator und die Methode _validate_type_emai 2 Argumente braucht. "Field" und "Value" Field ist "Mail" aber wie soll ich den String aus dem Post Eintrag als "Value" übertragen?
Mein Request (Autogenerated) aus Postman sieht ungefähr so aus

Code: Alles auswählen

import requests

url = "http://127.0.0.1:5000/people"

payload = "{\n\t\"lastname\": \"basasdasdasdls\",\n\t\"email\": \"das@tholo.de\"\n\t\n}"
headers = {
    'Content-Type': "application/json",
    'Cache-Control': "no-cache",
    'Postman-Token': "ec72d098-42e7-42ec-bd62-716eb672e34d"
    }

response = requests.request("POST", url, data=payload, headers=headers)

print(response.text)

Könnt ihr mir verraten wo mein Hänger ist? Ich gehe von aus das ich in der Settings.py (in Github die __init__.py unter Domain) das Value im Dict irgendwie beschreiben muss. Oder?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Dir fehlt nichts im Schema sondern das `type` in `_validate_type_email()` ist das Problem. Die bekommt vom Rahmenwerk nur ein Argument und dieses Argument darfst/sollst Du auf den *Typ* testen, *nicht* auf den *Wert*. Du hast aber eigentlich gar keinen neuen Typ, sondern einfach eine Zeichenkette, deren Wert Du auf „enthält eine E-Mail-Adresse“ testen möchtest. Vergleiche einfach mal die Methoden in den beiden Beispielen Custom Validation Rules und Custom Data Types.

Der reguläre Ausdruck verweigert gültige E-Mail-Adressen. Ich finde so etwas ja immer ein bisschen doof. Entweder testet man nur das mindestens ein @ vorkommt, oder man macht es richtig, mit der entsprechenden Monster-Regex.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Tholo
User
Beiträge: 177
Registriert: Sonntag 7. Januar 2018, 20:36

Das die Emai Prüfung mittels Regex nicht die beste Wahl ist, ist mir bewusst.
Aber ich würde den Teil des Scriptes gerne zum laufen bekommen, da ich versuche das zu verstehen.

Ich hatte gestern einen Fehler gemacht und den falschen Body aus dem Request und die falsche Funktion gepostet. Da ich hier das Attribute "Mail" testen wollte. Entschuldigt.. War wohl doch zu spät..

Code: Alles auswählen

from eve import Eve
import re
from eve.io.mongo import Validator

class MyValidator(Validator):
    """ You can extend or override the built-in validation rules easily
    by inheriting from the eve.io.mongo.Validator class
    """
    def _validate_isodd(self, isodd, field, value):
        """ Define a brand new 'isodd' rule """
        if isodd and not bool(value & 1):
            self._error(field, 'Value must be an odd number')

    def _validate_type_email(self, field, value):
        """ Extend types by adding a new 'email' type """
        if not re.match(r"[^@]+@[^@]+\.[^@]+", value):
            self._error(field, 'Value is not a valid email address')

 
app = Eve(validator=MyValidator)

if __name__ == '__main__':
    app.run(port=5000, debug=True)
Die Settings.py mit stark vereinfachten Schema

Code: Alles auswählen

people = {
    # A dict defining the actual data structure being handled by the resource.
    # Enables data validation.
    'schema': {
        'firstname': {
            'type': 'string',
            'minlength': 1,
            'maxlength': 30,
        },
        'lastname': {
            'type': 'string',
            'maxlength': 50,
            'required': True,
            'unique': True,
        },
        'age': {
            'type': 'integer',
            # Make sure clients don't try to write to it.
            # 'readonly': True,
            'isodd': True,
            
        # Regex ohne app.MyValidator funktioniert!
        # 'email': {'type': 'string',
        #         'regex': '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'}
        
        # this only works when app.MyValidator is used, as it brings support
        # for validating a field of type 'email'.
        ## email ist Field und Type:Email ist type
        'email': {'type': 'email'}
    }
}
Der Error im Body des Requests

Code: Alles auswählen

{
    "_status": "ERR",
    "_issues": {
        "exception": "_validate_type_email() missing 1 required positional argument: 'value'"
    },
    "_error": {
        "code": 422,
        "message": "Insertion failure: 1 document(s) contain(s) error(s)"
    }
}
Das Log von Py

Code: Alles auswählen

FLASK_APP = serv/app.py
FLASK_ENV = development
FLASK_DEBUG = 1
In folder /home/tholo/pyproj/SB2k/Serverside
/home/tholo/.local/share/virtualenvs/Serverside-cxjHtsUU/bin/python -m flask run
 * Serving Flask app "serv/app.py" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
/home/tholo/.local/share/virtualenvs/Serverside-cxjHtsUU/lib/python3.7/site-packages/cerberus/validator.py:1402: UserWarning: No validation schema is defined for the arguments of rule 'isodd'
  "'%s'" % method_name.split('_', 2)[-1])
 * Debugger is active!
 * Debugger PIN: 171-155-902
/home/tholo/.local/share/virtualenvs/Serverside-cxjHtsUU/lib/python3.7/site-packages/cerberus/validator.py:1402: UserWarning: No validation schema is defined for the arguments of rule 'isodd'
  "'%s'" % method_name.split('_', 2)[-1])
[2018-08-13 08:44:46,491] ERROR in post: _validate_type_email() missing 1 required positional argument: 'value'
Traceback (most recent call last):
  File "/home/tholo/.local/share/virtualenvs/Serverside-cxjHtsUU/lib/python3.7/site-packages/eve/methods/post.py", line 193, in post_internal
    validation = validator.validate(document)
  File "/home/tholo/.local/share/virtualenvs/Serverside-cxjHtsUU/lib/python3.7/site-packages/cerberus/validator.py", line 877, in validate
    self.__validate_definitions(definitions, field)
  File "/home/tholo/.local/share/virtualenvs/Serverside-cxjHtsUU/lib/python3.7/site-packages/cerberus/validator.py", line 940, in __validate_definitions
    result = validate_rule(rule)
  File "/home/tholo/.local/share/virtualenvs/Serverside-cxjHtsUU/lib/python3.7/site-packages/cerberus/validator.py", line 922, in validate_rule
    return validator(definitions.get(rule, None), field, value)
  File "/home/tholo/.local/share/virtualenvs/Serverside-cxjHtsUU/lib/python3.7/site-packages/cerberus/validator.py", line 1282, in _validate_type
    matched = type_handler(value)
TypeError: _validate_type_email() missing 1 required positional argument: 'value'
127.0.0.1 - - [13/Aug/2018 08:44:46] "POST /people HTTP/1.1" 422 -
Hängt es gar mit dem Isodd UserWanr zusammen?Wobei diese Methode keine Fehler macht, nach dem Start.
Ich gehe davon aus das in der Methode

Code: Alles auswählen

_validate_type_email(self, field, value)
der Wurm drin ist. Die Frage is nur Wieso...
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Tholo: Mein Antwort bleibt die gleiche: Wenn da `_type_` im Methodennamen steht dann werden nicht zwei Argumente erwartet, sondern nur eines. Schau Dir die beiden genannten Beispiele an. Und der einen übergebene Wert muss dann auf den Daten*typ* geprüft werden und nicht auf den Wert. Mit `isinstance()`. Das willst Du aber gar nicht, denn Du hast keinen neuen Datentyp, das ist einfach eine Zeichenkette die bestimmten Regeln folgen soll. Also nicht 'type': 'email', sondern 'type': 'string'. Und eine eigene Methode brauchst Du dann auch nicht schreiben, denn 'regex' kannst Du auch schon im Schema angeben. Das Beispiel für 'regex' in der Cerberus-Dokumentation ist sogar eine E-Mail.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du hast immer noch nicht BlackJacks Antwort umgesetzt: du erwartest 2 Argumente, obwohl du nur eines bekommst. Darum das "missing 1 required positional argument". Und du benutzt die falsche Validierungsart: du behauptest du hast einen Datentyp Email. Hast du aber nicht. Du hast einen Datentyp String, dessen WERT einer bestimmten Vorschrift gehorchen soll. Du musst bei der Email genauso vorgehen, wie bei isodd: du musst "isemail" definieren, und dann eine entsprechende Validierung (egal wie die dann im inneren aussieht, fuer die erste Implementung ein einfaches '@' in reicht{.
Tholo
User
Beiträge: 177
Registriert: Sonntag 7. Januar 2018, 20:36

Ich versteh is immer noch nicht.
In der Doku von Eve steht doch explizit drin
Custom Data Types
You can also add new data types by simply adding _validate_type_<typename> methods to your subclass. Consider the following snippet from the Eve source code.
Im Schema hab ich nun das stehen:

Code: Alles auswählen

'email': {'type': 'string', 'ismail': True,}
und die validation.py sieht so aus

Code: Alles auswählen

class MyValidator(Validator):
    """ You can extend or override the built-in validation rules easily
    by inheriting from the eve.io.mongo.Validator class
    """

    def _validate_isodd(self, isodd, field, value):
        """ Define a brand new 'isodd' rule """
        if isodd and not bool(value & 1):
            self._error(field, 'Value must be an odd number')

    def _validate_ismail(self, ismail, field, value):
        """ Extend types by adding a new 'email' type """
        if ismail and not re.match(r"[^@]+@[^@]+\.[^@]+", value):
            self._error(field, 'Value is not a valid email address')
Und ja es Funktioniert. Ich war zu sehr auf die Doku versessen. Selbst der Code von Github wo ich das her hab, hat das ja so drin gehabt. In seinem Video gings ja auch mit _type_mail.
Und das ist der Coder von Eve und Cerberus selbst.Siehe hier

Ich gehe mal von aus das die Eve,Cere oder Py Version da eine Änderung gemacht haben.

Danke euch!
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Tholo: Ich weiss nicht was man da noch sagen soll — ja in der Doku steht der von Dir zitierte Abschnitt. Damit kann man dann auf eigene Datentypen testen. Die Methode mit dem Muster `_validate_type_<typename>` erwartet dann *ein* Argument (neben `self`), und genau das hast Du nicht gemacht, Du hast da zwei erwartet und das ist laut Dokumentation falsch. Und noch wichtiger: das ist auch laut Laufzeitfehler falsch — die Dokumentation stimmt an der Stelle also. Zudem hast Du gar keinen eigenen Datentyp. Du hast eine Zeichenkette. Im Beispiel bei `_validate_type_<typename>` gibt es dagegen den Datentyp `ObjectId`. Du hast aber keinen Typ `EMail` oder etwas ähnliches.

Edit: Und noch eine Wiederholung: Du brauchst diese Methode nicht, denn Cerberus kennt ja schon 'regex' als Regel.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Tholo
User
Beiträge: 177
Registriert: Sonntag 7. Januar 2018, 20:36

Danke BJ!

Ich glaube jetzt ist der Groschen gefallen! "ObjectId" kommt ja von MongoDB! und nicht aus Eve oder Cerberus oder meinem Script. Deswegen macht das mit dem Type: "Email" keinerlei Sinn.
Das hab ich die ganze Zeit nicht bedacht.

Zum Edit:
Ich brauche keines der beiden Funktionen. Mir war es aber wichtig zu verstehen, wieso das Script an dieser Stelle nicht so wollte, wie es vorgestellt wurde.
Antworten