Skip to content

Commit 59644c4

Browse files
committed
Renamed "Using Generator Code" section to "Builtin Generators".
- Improved Python Guide section.
1 parent 05c1a3e commit 59644c4

File tree

5 files changed

+292
-248
lines changed

5 files changed

+292
-248
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internally: Swift, C#, Java, Go, JavaScript, and HTML documentation.
2828
* Motivation_
2929
* Installation_
3030
* `Language Reference <doc/lang_ref.rst>`_
31-
* `Using Generated Code <doc/using_generator.rst>`_
31+
* `Builtin Generators <doc/builtin_generators.rst>`_
3232
* `Managing Specs <doc/managing_specs.rst>`_
3333
* `Evolving a Spec <doc/evolve_spec.rst>`_
3434
* `Writing a Generator <doc/generator_ref.rst>`_

doc/builtin_generators.rst

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
******************
2+
Builtin Generators
3+
******************
4+
5+
Using a generator, you can convert the data types and routes in your spec into
6+
objects in your programming language of choice.
7+
8+
Currently, the only generator included with Stone is for `Python
9+
<#python-guide>`_. Dropbox has written generators for an assortment of
10+
languages, which we intend to release, including:
11+
12+
* C#
13+
* Go
14+
* Java
15+
* Javascript
16+
* Swift
17+
18+
If you're looking to make your own generator, see `Writing a Generator
19+
<generator_ref.rst>`_. We would love to see a contribution of a PHP or Ruby
20+
generator.
21+
22+
Compile with the CLI
23+
====================
24+
25+
Compiling a spec and generating code is done using the ``stone``
26+
command-line interface (CLI)::
27+
28+
$ stone -h
29+
usage: stone [-h] [-v] [--clean-build] [-f FILTER_BY_ROUTE_ATTR]
30+
[-w WHITELIST_NAMESPACE_ROUTES | -b BLACKLIST_NAMESPACE_ROUTES]
31+
generator output [spec [spec ...]]
32+
33+
StoneAPI
34+
35+
positional arguments:
36+
generator Either the name of a built-in generator or the path to
37+
a generator module. Paths to generator modules must
38+
end with a .stoneg.py extension. The following
39+
generators are built-in: js_client, python_types,
40+
python_client, swift_client
41+
output The folder to save generated files to.
42+
spec Path to API specifications. Each must have a .stone
43+
extension. If omitted or set to "-", the spec is read
44+
from stdin. Multiple namespaces can be provided over
45+
stdin by concatenating multiple specs together.
46+
47+
optional arguments:
48+
-h, --help show this help message and exit
49+
-v, --verbose Print debugging statements.
50+
--clean-build The path to the template SDK for the target language.
51+
-f FILTER_BY_ROUTE_ATTR, --filter-by-route-attr FILTER_BY_ROUTE_ATTR
52+
Removes routes that do not match the expression. The
53+
expression must specify a route attribute on the left-
54+
hand side and a value on the right-hand side. Use
55+
quotes for strings and bytes. The only supported
56+
operators are "=" and "!=". For example, if "hide" is
57+
a route attribute, we can use this filter:
58+
"hide!=true". You can combine multiple expressions
59+
with "and"/"or" and use parentheses to enforce
60+
precedence.
61+
-w WHITELIST_NAMESPACE_ROUTES, --whitelist-namespace-routes WHITELIST_NAMESPACE_ROUTES
62+
If set, generators will only see the specified
63+
namespaces as having routes.
64+
-b BLACKLIST_NAMESPACE_ROUTES, --blacklist-namespace-routes BLACKLIST_NAMESPACE_ROUTES
65+
If set, generators will not see any routes for the
66+
specified namespaces.
67+
68+
We'll generate code based on an ``calc.stone`` spec with the following
69+
contents::
70+
71+
namespace calc
72+
73+
route eval(Expression, Result, CalcError)
74+
75+
struct Expression
76+
"This expression is limited to a binary operation."
77+
op Operator = add
78+
left Int64
79+
right Int64
80+
81+
union Operator
82+
add
83+
sub
84+
mult
85+
div Boolean
86+
"If value is true, rounds up. Otherwise, rounds down."
87+
88+
struct Result
89+
answer Int64
90+
91+
union EvalError
92+
overflow
93+
94+
Python Guide
95+
============
96+
97+
This section explains how to use the pre-packaged Python generators and work
98+
with the Python classes that have been generated from a spec.
99+
100+
There are two different Python generators: ``python_types`` and
101+
``python_client``. The former generates Python classes for the data types
102+
defined in your spec. The latter generates a single Python class with a method
103+
per route, which is useful for building SDKs.
104+
105+
We'll use the ``python_types`` generator::
106+
107+
$ stone python_types . calc.stone
108+
109+
This runs the generator on the ``calc.stone`` spec. Its output target is
110+
``.`` which is the current directory. A Python module is created for
111+
each declared namespace, so in this case only ``calc.py`` is created.
112+
113+
Three additional modules are copied into the target directory. The first,
114+
``stone_validators.py``, contains classes for validating Python values against
115+
their expected Stone types. You will not need to explicitly import this module,
116+
but the auto-generated Python classes depend on it. The second,
117+
``stone_serializers.py``, contains a pair of ``json_encode()`` and
118+
`json_decode()`` functions. You will need to import this module to serialize
119+
your objects. The last is ``stone_base.py`` which shouldn't be used directly.
120+
121+
In the following sections, we'll interact with the classes generated in
122+
``calc.py``. For simplicity, we'll assume we've opened a Python interpreter
123+
with the following shell command::
124+
125+
$ python -i calc.py
126+
127+
For non-test projects, we recommend that you set the generation target to a
128+
path within a Python package, and use Python's import facility.
129+
130+
Primitive Types
131+
---------------
132+
133+
The following table shows the mapping between a Stone `primitive type
134+
<lang_ref.rst#primitive-types>`_ and its corresponding type in Python.
135+
136+
========================== ============== =====================================
137+
Primitive Python 2.x / 3 Notes
138+
========================== ============== =====================================
139+
Bytes bytes
140+
Boolean bool
141+
Float{32,64} float long type within range is converted.
142+
Int{32,64}, UInt{32,64} long
143+
List list
144+
String unicode / str str type is converted to unicode.
145+
Timestamp datetime
146+
========================== ============== =====================================
147+
148+
Struct
149+
------
150+
151+
For each struct in your spec, you will see a corresponding Python class of the
152+
same name.
153+
154+
In our example, ``Expression``, ``Operator``, ``Answer``, ``EvalError``, and
155+
are Python classes. They have an attribute (getter/setter/deleter property) for
156+
each field defined in the spec. You can instantiate these classes and specify
157+
field values either in the constructor or by assigning to an attribute::
158+
159+
>>> expr = Expression(op=Operator.add, left=1, right=1)
160+
161+
If you assign a value that fails validation, an exception is raised::
162+
163+
>>> expr.op = '+'
164+
Traceback (most recent call last)
165+
...
166+
ValidationError: expected type Operator or subtype, got string
167+
168+
Accessing a required field (non-optional with no default) that has not been set
169+
raises an error::
170+
171+
>>> res = Result()
172+
>>> res.answer
173+
Traceback (most recent call last):
174+
File "<stdin>", line 1, in <module>
175+
File "calc.py", line 221, in answer
176+
raise AttributeError("missing required field 'answer'")
177+
AttributeError: missing required field 'answer'
178+
179+
Other characteristics:
180+
181+
1. Inheritance in Stone is represented as inheritance in Python.
182+
2. If a field is nullable and was never set, ``None`` is returned.
183+
3. If a field has a default but was never set, the default is returned.
184+
185+
Union
186+
-----
187+
188+
For each union in your spec, you will see a corresponding Python class of the
189+
same name.
190+
191+
You do not use a union class's constructor directly. To select a tag with a
192+
void type, use the class attribute of the same name::
193+
194+
>>> EvalError.overflow
195+
EvalError('overflow', None)
196+
197+
To select a tag with a value, use the class method of the same name and pass
198+
in an argument to serve as the value::
199+
200+
>>> Operator.div(False)
201+
Operator('div', False)
202+
203+
To write code that handles the union options, use the ``is_[tag]()`` methods.
204+
We recommend you exhaustively check all tags, or include an else clause to
205+
ensure that all possibilities are accounted for. For tags that have values,
206+
use the ``get_[tag]()`` method to access the value::
207+
208+
>>> # assume that op is an instance of Operator
209+
>>> if op.is_add():
210+
... # handle addition
211+
... elif op.is_sub():
212+
... # handle subtraction
213+
... elif op.is_mult():
214+
... # handle multiplication
215+
... elif op.is_div():
216+
... round_up = op.get_div()
217+
... # handle division
218+
219+
Struct Polymorphism
220+
-------------------
221+
222+
As with regular structs, structs that enumerate subtypes have corresponding
223+
Python classes that behave identically to regular structs.
224+
225+
The difference is apparent when a field has a data type that is a struct with
226+
enumerated subtypes. Expanding on our example from the language reference,
227+
assume the following spec::
228+
229+
struct Resource
230+
union*
231+
file File
232+
folder Folder
233+
234+
path String
235+
236+
struct File extends Resource:
237+
size UInt64
238+
239+
struct Folder extends Resource:
240+
"No new fields."
241+
242+
struct Response
243+
rsrc Resource
244+
245+
If we instantiate ``Response``, the ``rsrc`` field can only be assigned a
246+
``File`` or ``Folder`` object. It should not be assigned a ``Resource`` object.
247+
248+
An exception to this is on deserialization. Because ``Resource`` is specified
249+
as a catch-all, it's possible when deserializing a ``Response`` to get a
250+
``Resource`` object in the ``rsrc`` field. This indicates that the returned
251+
subtype was unknown because the recipient has an older spec than the sender.
252+
To handle catch-alls, you should use an else clause::
253+
254+
>>> print resp.rsrc.path # Guaranteed to work regardless of subtype
255+
>>> if isinstance(resp, File):
256+
... # handle File
257+
... elif isinstance(resp, Folder):
258+
... # handle Folder
259+
... else:
260+
... # unknown subtype of Resource
261+
262+
Route
263+
-----
264+
265+
Routes are represented as instances of a ``Route`` object. The generated Python
266+
module for the namespace will have a module-level variable for each route::
267+
268+
>>> eval
269+
Route('eval', False, ...)
270+
271+
Serialization
272+
-------------
273+
274+
We can use ``stone_serializers.json_encode()`` to serialize our objects to
275+
JSON::
276+
277+
>>> import stone_serializers
278+
>>> stone_serializers.json_encode(eval.result_type, Result(answer=10))
279+
'{"answer": 10}'
280+
281+
To deserialize, we can use ``json_decode``::
282+
283+
>>> stone_serializers.json_decode(eval.result_type, '{"answer": 10}')
284+
Result(answer=10)
285+
286+
There's also ``json_compat_obj_encode`` and ``json_compat_obj_decode`` for
287+
converting to and from Python primitive types rather than JSON strings.
288+

doc/lang_ref.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ Mapping to Target Languages
9797
---------------------------
9898

9999
Code generators map the primitive types of Stone to types in a target language.
100-
For more information, consult the appropriate guide in `Using Generated Code
101-
<using_generator.rst>`_.
100+
For more information, consult the appropriate guide in `Builtin Generators
101+
<builtin_generators.rst>`_.
102102

103103
Alias
104104
=====

0 commit comments

Comments
 (0)