JSON 扩展属性

GINO provides additional support to leverage native JSON type in the database as flexible GINO model fields.

Quick Start

  1. from gino import Gino
  2. from sqlalchemy.dialects.postgresql import JSONB
  3. db = Gino()
  4. class User(db.Model):
  5. __tablename__ = "users"
  6. id = db.Column(db.Integer, primary_key=True)
  7. name = db.Column(db.String)
  8. profile = db.Column(JSONB, nullable=False, server_default="{}")
  9. age = db.IntegerProperty()
  10. birthday = db.DateTimeProperty()

The age and birthday are JSON properties stored in the profile column. You may use them the same way as a normal GINO model field:

  1. u = await User.create(name="daisy", age=18)
  2. print(u.name, u.age) # daisy 18

注解

profile is the default column name for all JSON properties in a model. If you need a different column name for some JSON properties, you’ll need to specify explicitly:

  1. audit_profile = db.Column(JSON, nullable=False, server_default="{}")
  2. access_log = db.ArrayProperty(prop_name="audit_profile")
  3. abnormal_detected = db.BooleanProperty(prop_name="audit_profile")

Using JSON properties in queries is supported:

  1. await User.query.where(User.age > 16).gino.all()

This is simply translated into a native JSON query like this:

  1. SELECT users.id, users.name, users.profile
  2. FROM users
  3. WHERE CAST((users.profile ->> $1) AS INTEGER) > $2; -- ('age', 16)

Datetime type is very much the same:

  1. from datetime import datetime
  2. await User.query.where(User.birthday > datetime(1990, 1, 1)).gino.all()

And the generated SQL:

  1. SELECT users.id, users.name, users.profile
  2. FROM users
  3. WHERE CAST((users.profile ->> $1) AS TIMESTAMP WITHOUT TIME ZONE) > $2
  4. -- ('birthday', datetime.datetime(1990, 1, 1, 0, 0))

Here’s a list of all the supported JSON properties:

JSON 扩展属性

Python type

JSON type

Database Type

StringProperty

str

string

text

IntegerProperty

int

number

int

BooleanProperty

bool

boolean

boolean

DateTimeProperty

datetime

string

text

ObjectProperty

dict

object

JSON

ArrayProperty

list

array

JSON

Hooks

JSON property provides 2 instance-level hooks to customize the data:

  1. class User(db.Model):
  2. __tablename__ = "users"
  3. id = db.Column(db.Integer, primary_key=True)
  4. profile = db.Column(JSONB, nullable=False, server_default="{}")
  5. age = db.IntegerProperty()
  6. @age.before_set
  7. def age(self, val):
  8. return val - 1
  9. @age.after_get
  10. def age(self, val):
  11. return val + 1
  12. u = await User.create(name="daisy", age=18)
  13. print(u.name, u.profile, u.age) # daisy {'age': 17} 18

And 1 class-level hook to customize the SQLAlchemy expression of the property:

  1. class User(db.Model):
  2. __tablename__ = "users"
  3. id = db.Column(db.Integer, primary_key=True)
  4. profile = db.Column(JSONB, nullable=False, server_default="{}")
  5. height = db.JSONProperty()
  6. @height.expression
  7. def height(cls, exp):
  8. return exp.cast(db.Float) # CAST(profile -> 'height' AS FLOAT)

Create Index on JSON Properties

We’ll need to use declared_attr() to wait until the model class is initialized. The rest is very much the same as defining a usual index:

  1. class User(db.Model):
  2. __tablename__ = "users"
  3. id = db.Column(db.Integer, primary_key=True)
  4. profile = db.Column(JSONB, nullable=False, server_default="{}")
  5. age = db.IntegerProperty()
  6. @db.declared_attr
  7. def age_idx(cls):
  8. return db.Index("age_idx", cls.age)

This will lead to the SQL below executed if you run db.gino.create_all():

  1. CREATE INDEX age_idx ON users (CAST(profile ->> 'age' AS INTEGER));

警告

Alembic doesn’t support auto-generating revisions for functional indexes yet. You’ll need to manually edit the revision. Please follow this issue for updates.