question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Creating a custom field that automatically converts between db types and python types

See original GitHub issue

I want to use Peewee to manage a collection of GIS data. I’ve defined some custom fields to handle the PostGIS geometry type:

class GeomField(Field):
    db_field = 'geometry'

    def __init__(self, geom_type=None, geom_srid=None, **kwargs):
        self.geom_srid = geom_srid
        self.geom_type = geom_type

        super(GeomField, self).__init__(**kwargs)
        if geom_type:
            if geom_srid:
                self.db_field = 'geometry(%s, %s)' % (geom_type, geom_srid)
            else:
                self.db_field = 'geometry(%s)' % (geom_type, )


class PointField(GeomField):
    def __init__(self, geom_srid=None, **kwargs):
        super(PointField, self).__init__(
            geom_type='Point',
            geom_srid=geom_srid,
            **kwargs)

This works fine so far. I would like to be able to set a PointField to a Python tuple and have it converted automatically to a geometry value (and I want the inverse as well). I started with this:

def db_value(self, value):
    return fn.st_setsrid(
      fn.st_makepoint(value[0],value[1],self.geom_srid))

But I guess that Peewee doesn’t interpolate these functions at the right point, because that gets me:

ValueError: only bytes values expected, got Func

So instead I’ve done this:

def python_value(self, value):
    res = database.execute_sql('select st_x(%s), st_y(%s)',
                               (value, value))
    return res.fetchone()

def db_value(self, value):
    res = database.execute_sql(
        'select st_setsrid(st_makepoint(%s,%s), %s)',
        (value[0], value[1], self.geom_srid))
    return res.fetchone()

This works, but it seems less than elegant and I am curious if I am missing a simpler solution.

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Comments:12 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
whalesaladcommented, Nov 30, 2017

@coleifer is there a way to override the fn called when performing a read?

IE, instead of

select foo, bar, baz from table

You could specify a wrapper fn for a field, so you could so something like

select foo, bar, ST_AsText(baz) from table

which would alleviate a lot of the trouble here (I too am trying to figure out how to read/write a simple geometry(Point) field with postgis.

1reaction
davidbernatcommented, Jul 26, 2019

This appears to create the same outcome as @nbolten , and allows the containment to be on the field itself. It is unclear to me whether _returning is only those fields selected by the select() method, and how this will affect performance in more complex queries. Posting here, though.

class PointField(pw.Field):
    """Geometry field type Point, conforming to MySQL 8.0+, format is WKT: 'POINT(18 23)'"""

    # Field type in MySQL is Point with a capital P.
    field_type = 'Point'

    def python_value(self, value):
        return value

    def db_value(self, ewkt):
        """Converts EWKT format to MySQL database requirements, e.g., SRID=3857;POINT(10,10)"""

        # Does the ewkt start with "SRID=XXXX;...".
        # If so, remove the value from the geometry and parse/extract the srid
        s = ewkt.split(";")
        if s[0].startswith("SRID="):
            geo = ";".join(s[1:])
            srid = s[0].split("=")[1]
            return pw.fn.ST_GeomFromText(geo, srid)
        else:
            return pw.fn.ST_GeomFromText(ewkt)

    def _db_wrangler(self, field):
        return pw.fn.ST_AsText(field).alias(field.name)
query = PadsLocations.select(PadsLocations)
for r_i, r in enumerate(query._returning):
    if hasattr(r, '_db_wrangler'):
        query._returning[r_i] = r._db_wrangler(r)
Read more comments on GitHub >

github_iconTop Results From Across the Web

How to create custom model fields - Django documentation
Writing a custom field can be a tricky process, particularly if you're doing complex conversions between your Python types and your database and...
Read more >
Adapting and Converting SQLite Data Types for Python
This tutorial will explain all the methods the sqlite3 module provides for converting and adapting Python data types into SQLite types, ...
Read more >
Custom Types - SQLAlchemy 1.4 Documentation
The TypeDecorator allows the creation of custom types which add bind-parameter and result-processing behavior to an existing type object. It is ...
Read more >
Models and Fields — peewee 3.15.4 documentation
It's easy to create custom field types and use them with your models. ... a value coming from the database and converts it...
Read more >
The database abstraction layer - Web2py
Web2py's auto-execution of Python code inside the model directory does ... If a field has changed type but not name, it will try...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found