Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more descriptive pydeck types classes #4897

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

kylebarron
Copy link
Collaborator

Background

Currently pydeck does a lot of "magic" behind the scenes to coerce user input into the representation sent to @deck.gl/json.

  • Strings are always treated as functions unless it includes an extra pair of quotes. So "properties.valuePerSqm" becomes the function datum => datum.properties.valuePerSqm. To pass a string literal, you need to use '"literal string"'.
  • When a literal string is passed, it removes all internal occurrences of that string. So '"literal "" string"' in Python becomes 'literal string' in JS.
  • Lists of strings are always treated as functions. In order to reference a literal string, you need to include another pair of quotes on that item.

Change List

These changes are designed to minimize backwards incompatibility.

  • Add helper classes that aim to be a 1:1 equivalent of what is possible in the JSON spec. These are named Enum, Literal, Function and Identifier.
  • Enum takes string input and prefixes @@# before serialization
  • Literal takes any JSON-encodable object and passes it unchanged to JS
  • Function takes varied input.
    • For string input it prefixes @@= for serialization.

    • For number input it stringifies the number and prefixes @@=.

    • For Literal input it prefixes @@= and surrounds the input with double quotes. I.e. Literal('foo') becomes @@="foo". This is necessary because the @deck.gl/json docs say:

      Interpret the rest of the string as a function, parsing unquoted character strings as identifiers

    • The Identifier class is included as a more-explicit form of string input. It includes its string unquoted.

    • Each of these should also work within array input. So you should be able to pass Function([Identifier('x'), Identifier('y'), Literal(0)]) to render data at ground level.

  • When a literal string is passed in double quotes, remove only the first and last characters and leave the rest of the string intact. So '"literal "" string"' in Python becomes 'literal "" string' in JS. This is a backwards incompatible change, but the existing behavior is arguably broken.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 80.595% when pulling 538b223 on kyle/pydeck-type-classes into 0e9109a on master.

try:
json.dumps(value)
except TypeError as e:
raise TypeError("Literal value must be JSON serializable\n" + e)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're going to get a TypeError in your TypeError message, since the string and error don't concatenate. Maybe

raise TypeError("pydeck Literal value must be JSON serializable and input {} is not".format(value))

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you're right. I think I'm looking for e.message.



class Type(metaclass=abc.ABCMeta):
"""Base class for Pydeck defined type"""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: pydeck is lower-cased, like pandas or folium.

Copy link
Contributor

@isaacbrodsky isaacbrodsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of having typed objects for these and not automagically deduce the encoding

ENUM_IDENTIFIER = "@@#"


class Type(metaclass=abc.ABCMeta):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest naming this something more descriptive than "Type", which seems a little generic.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's a base class, BaseType might be more apt?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Naming base classes with Base... is one my pet peeves. I feel that it is a design cop-out. Let's ideate a bit more first.

SerializableType, TransportType, TransportableType, ...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. This base class isn't public so I'm not too concerned with what the name should be, and welcome those suggestions. I'd also consider prefixing with _ to make clear it's private.

@@ -82,28 +83,29 @@ def __init__(self, type, data, id=None, use_binary_transport=None, **kwargs):
self._kwargs = kwargs.copy()
if kwargs:
for k, v in kwargs.items():
if isinstance(v, Type):
continue

# We assume strings and arrays of strings are identifiers
# ["lng", "lat"] would be converted to '[lng, lat]'
# TODO given that data here is usually a list of records,
# we could probably check that the identifier is in the row
# Errors on case like get_position='-', however

if isinstance(v, str) and v[0] in QUOTE_CHARS and v[0] == v[-1]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also fail for "a" + b + "c" (results in a" + b + "c)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the question here is what is the desired behavior? Is "a" + b + "c" -> a + b + c more desired?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect "a" + b + "c" to be "a" + b + "c" to match the behavior of "a" + "c" + b. The type result changing so dramatically from changing the order of operators is surprising.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right I'd also prefer that but it's a breaking change.

@ibgreen
Copy link
Collaborator

ibgreen commented Aug 27, 2020

Side note: don't want to make this more complex but I am thinking about how these design choices translate to the bigger cross-language picture.

E.g. if similar constructs would make sense in C++, Java, Swift Proxy bindings...

I really want these various API to develop in the same general direction.

Could be worth sketching out how things would look in those APIs...

@kylebarron
Copy link
Collaborator Author

I am thinking about how these design choices translate to the bigger cross-language picture.

I think it's good to have that discussion before merging a PR such as this one into Pydeck. I could envision a similar API for other languages. A good precursor might be to minimize the differences between pydeck and the JSON spec?

@ibgreen
Copy link
Collaborator

ibgreen commented Aug 31, 2020

I think it's good to have that discussion before merging a PR such as this one into Pydeck. I could envision a similar API for other languages. A good precursor might be to minimize the differences between pydeck and the JSON spec?

Agreed.

About Proxy API definition:

At the moment we don't have a reference Proxy API for deck.gl JSON that other languages can just follow. The most "natural" proxy API would be implemented in JavaScript - but it is not really needed for obvious reasons (though it could potentially be useful for e.g. for rendering deck.gl from Node.js, and certainly for testing proxying setups).

We do have pydeck and experimental Java and Swift proxy APIs. We may also want to start sketching on a C++ proxy API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants