0

I'm new to Flask/Marshmallow/SQLAlchemy and starting to implement a REST API.

Here is a minimal example. Ideally it uses "name":"value" pairs without specifying "name" in a schema. Note: My application isn't really about saving phone numbers and email addresses.

This is the JSON request for POST of an object called Person:

{ 
  "name": "John Doe",
  "public_contact_info": {
    "office": "111-1111",
  },  
  "private_contact_info": {
    "home": "333-3333",
    "mobile": "444-4444"
  }   
}

Here is another valid request:

{ 
  "name": "Jane Doe",
  "private_contact_info": {
    "home": "555-5555",
    "email": "jane@gmail.com"
  }   
}

The database tables look like this:

sqlite> .schema person
CREATE TABLE person (
    id INTEGER NOT NULL, 
    name VARCHAR(255), 
    public_contact_info_id INTEGER, 
    private_contact_info_id INTEGER, 
    PRIMARY KEY (id), 
    FOREIGN KEY(public_contact_info_id) REFERENCES contact_info (id), 
    FOREIGN KEY(private_contact_info_id) REFERENCES contact_info (id)
);
sqlite> .schema contact_info
CREATE TABLE contact_info (
    id INTEGER NOT NULL, 
    person_id INTEGER, 
    contact_type VARCHAR(255), 
    name VARCHAR(255), 
    value VARCHAR(255), 
    PRIMARY KEY (id), 
    FOREIGN KEY(person_id) REFERENCES person (id)
);

The Person schema will have private_contact_info and public_contact_info that end up in the ContactInfo schema as contact_type. The first JSON request show above will create one person db row and three contact_info rows. The second JSON request will create one person db row and one contact_info db row.

models.py:

# models.py

from datetime import datetime
from marshmallow import Schema, fields

from config import db, ma

class ContactInfo(db.Model):
    __tablename__ = 'contact_info'
    id = db.Column(db.Integer, primary_key=True)
    person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
    contact_type = db.Column(db.String(255))
    name = db.Column(db.String(255))
    value = db.Column(db.String(255))

#    def __init__(self, contact_type, name, value):
#        self.contact_type = contact_type
#        self.name = name
#        self.value = value

class Person(db.Model):
    __tablename__ = 'person'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255))

    public_contact_info_id = db.Column(db.Integer, db.ForeignKey('contact_info.id'))
    private_contact_info_id = db.Column(db.Integer, db.ForeignKey('contact_info.id'))

    public_contact_info = db.relationship(
        'ContactInfo',
        #backref='person', 
        #cascade='all, delete-orphan',
        foreign_keys=[public_contact_info_id],
    )   
    private_contact_info = db.relationship(
        'ContactInfo',
        #backref='person', 
        #cascade='all, delete-orphan',
        foreign_keys=[private_contact_info_id],
    )   

class ContactInfoSchema(ma.SQLAlchemyAutoSchema):
    class Meta:
        model = ContactInfo
        unknown = EXCLUDE
        load_instance = True

class PersonSchema(ma.SQLAlchemyAutoSchema):
    class Meta:
        model = Person
        include_relationships = True
        unknown = EXCLUDE
        load_instance = True

    public_contact_info = fields.Nested('ContactInfoSchema')
    private_contact_info = fields.Nested('ContactInfoSchema')

contact_info_schema = ContactInfoSchema()
person_schema = PersonSchema()
people_schema = PersonSchema(many=True)

Here is a snippet from people.py. (In this case I am using a hard-coded request- that is not the cause of my issue):

example = { 
  "name": "John Doe",
  "public_contact_info": {
    "office": "111-1111",
  },  
  "private_contact_info": {
    "home": "333-3333",
    "mobile": "444-4444"
  }   
}

def create():
    json_data = example

    if not json_data:
        return abort(400, f'No JSON data provided')
    try:
        new_person = person_schema.load(json_data, session=db.session)
        person_schema.dump(new_person)
        db.session.add(new_person)
        db.session.commit()

    except Exception as e:
        return abort(500, 'Error creating person,' + ' Error:' + str(e))

    return person_schema.dump(new_person)

When I make a request to create(), this is the result, with empty fields:

{
  "id": 2,
  "name": "John Doe",
  "private_contact_info": {
    "contact_type": "",
    "id": 4,
    "name": "",
    "value": ""
  },
  "public_contact_info": {
    "contact_type": "",
    "id": 3,
    "name": "",
    "value": ""
  }
}

I guess my JSON representation and internal representation are different. Presumably the internal representation would be something like:

{
  "id": 2,
  "name": "John Doe",
  "contact_info": [
    {
      "contact_type": "public",
      "id": 5,
      "name": "office",
      "value": "111-1111"
    },
    {
      "contact_type": "private",
      "id": 3,
      "name": "home",
      "value": "333-3333"
    },
    {
      "contact_type": "private",
      "id": 4,
      "name": "mobile",
      "value": "444-4444"
    }
  ]
}

How do I define this intermediate representation? How can I fill in the fields contact_type, name and value?

Have not been able to find good examples anywhere. There are examples for some combination of one or two of those frameworks, but not with all three. Looked at SO, other places, documentation, even asked ChatGPT (which keeps forgetting I am using all three- 'Apologies for the oversight. You are correct').

Is this possible?

Update

I modified the request JSON and used two identical schemas, one for public_contact_info and one for private_contact_info. It's unsatisfying to change the public API to match the internal implementation.

What I was trying may be possible, but it looks that it needs more advanced skills.

RobP
  • 45
  • 4

0 Answers0