graphene-sqlalchemy icon indicating copy to clipboard operation
graphene-sqlalchemy copied to clipboard

geoalchemy2 support

Open wahello opened this issue 7 years ago • 6 comments

Help! geoalchemy2 support!


class CoordinateMixin():
    location = db.Column('location', Geography(geometry_type='POINT' ,srid=4326, spatial_index=True, dimension=2), doc='gps coordinate')


Exception: Don't know how to convert the SQLAlchemy field user.location (<class 'sqlalchemy.sql.schema.Column'>)

I'v already add the following code in schema file.


from geoalchemy2 import Geography
from graphene_sqlalchemy.converter import get_column_doc, convert_sqlalchemy_type
from app.graphql.extesions import CoordinateJSON

@convert_sqlalchemy_type.register(Geography)
def convert_column_to_coordinatejson(type, column, registry=None):
    return graphene.Field(CoordinateJSON, description=get_column_doc(column))

schema = graphene.Schema(query=RootQuery, types=types, mutation=MyMutation)


wahello avatar Jul 04 '18 12:07 wahello

Please let me know how to import CoordinateJSON, also can anyone provide solution for how it works.

neebj avatar Jan 11 '19 05:01 neebj

@keyeMyria What you have won't work, because you haven't told GraphQL how the underlying type should be represented.

Here's a way to do it, with geometries instead of geographies, but it should be applicable to geography type with minor changes. This implementation assumes that you want to represent the geometry or geography as its WKT; you can sub the CoordinateJSON output instead, as you prefer.

I imported graphene_sqlalchemy like so, for easier typing:

import graphene_sqlalchemy as gsqa

First, you need to define a scalar type that will represent the geometry:

class Geometry_WKT(graphene.Scalar):
     '''Geometry WKT custom type.'''

     @staticmethod
     def serialize(geom):
         return engine.scalar(geom.ST_AsText())

     @staticmethod
     def parse_literal(node):
         if isinstance(node, graphql.language.ast.StringValue):
             return engine.scalar(geoalchemy2.func.ST_GeomFromText(node.value))

     @staticmethod
     def parse_value(value):
         return engine.scalar(geoalchemy2.func.ST_GeomFromText(value))

Then, you define the conversion using the convert_sqlalchemy_type decorator:

@gsqa.converter.convert_sqlalchemy_type.register(geoalchemy2.Geometry)
def _convert_geometry(thetype, column, registry=None):
    return Geometry_WKT(description=gsqa.converter.get_column_doc(column),
            required=not(gsqa.converter.is_column_nullable(column))

That's it! Your Geometry outputs will now be represented as WKT. You can do the same with the Geography type, instead. And, if you wish to use CoordinateJSON, just set up the CoordinateJSON conversion in the scalar type's methods, instead of using ST_AsText() or ST_GeomFromText().

flewellyn avatar Feb 08 '19 22:02 flewellyn

Strange, I have a piece of code that used to work but doesn't anymore, I wonder what changed here (I also used Geography). I never wrote any code to do conversion.

cglacet avatar Apr 07 '21 17:04 cglacet

Strange, I have a piece of code that used to work but doesn't anymore, I wonder what changed here (I also used Geography). I never wrote any code to do conversion.

I just landed here today with the same issue: something that was working before doesn't work any more. In my case, all this mess raises when I install a 3rd party package sqlalchemy-utils. And I mean when I install, even when not using it at all in the codebase, it blows up with this graphene-sqlalchemy errors.

jose-lpa avatar Jun 15 '21 15:06 jose-lpa

I am trying the code suggested by @flewellyn and am getting a long exception traceback ending with

File ".../.tox/py37/lib/python3.7/site-packages/graphql/type/typemap.py", line 87, in reducer
    if type_.name in map_:
AttributeError: 'Geometry_WKT' object has no attribute 'name'

If I try adding a name attribute to the Geometry_WKT class, I get a similarly long error traceback ending with

  File ".../.tox/py37/lib/python3.7/site-packages/graphql/type/typemap.py", line 127, in reducer
    type_, field_name, field.type
AssertionError: LocationObject.location field type must be Output Type but got: <Geometry_WKT object at 0x7ff6829a7390>.

I am not familiar enough with the inner workings of Graphene/GraphQL to know what to do about either of these tracebacks. Can anyone confirm if this approach still works for them? Also, what is engine supposed to be?

dpitch40 avatar Jun 23 '21 19:06 dpitch40

If anyone still interested I figured out a way to get the geometry content using shapely-geojson (0.0.1) and graphene-sqlalchemy (2.3.0)


import graphene_sqlalchemy as gsqa
from shapely_geojson import dumps
from geoalchemy2.shape import to_shape


class Geometry(graphene.ObjectType):
    geojson = graphene.Field(graphene.String)
    srid = graphene.Int(required=True)

    def resolve_srid(self, info):
        return self.srid

    def resolve_geojson(self, info):
        return dumps(to_shape(self))


@gsqa.converter.convert_sqlalchemy_type.register(geoalchemy2.Geometry)
def _convert_geometry(thetype, column, registry=None):
    return Geometry


class Address(SQLAlchemyObjectType):
    class Meta:
        interfaces = (relay.Node, )
        model = schemas.Address # a schema with geometry = Column(Geometry(srid=4326), nullable=True)

Result is like:


            "geometry": {
              "geojson": "{\"type\": \"Point\", \"coordinates\": [5.605069, 47.440551]}",
              "srid": 4326
            }

(as far as I can tell the geojson string is matching posgraphile output).

fvallee-bnx avatar Jan 13 '22 16:01 fvallee-bnx