python generator

civilx64 edited this page Jul 18, 2017 · 30 revisions

python Generator

This wiki page gives a few hints to get started with the SCL python generator, to understand the design choices/implementation that have been made so far, to introduce the semantic mappings between an EXPRESS schema and the related python module, and at last to present a TODO list. Please report any issue, suggestion, patch, idea etc. For instance:

1. Purpose of the python generator

So far, SCL is known to provide a program named fedex_plus. fedex_plus parses an EXPRESS schema, and generates C++ code that needs to be compiled in order to provide a C++ API to handle the schema. The python generator aims at providing the same functionality for the python programming language: parsing any EXPRESS schema, and generating a python module giving access to all entities defined in the schema. This development branch then provides a binary named fedex_python, as well as a SCL python package, both enabling access to EXPRESS data represented in a schema (e.g. entities, local or/and global rules, db population etc).

2. Design Guidelines

ISO 10303 Part 22 defines a STEP API, named Standard Data Access Interface (SDAI), for C++ (Part 23), C (Part 24) and java (Part 27) programming languages. This gives a strong reference frame for the development of fedex_plus. Regarding other programming languages (e.g. ruby, scheme, lisp, python etc.), the ISO did not standardize any implementation. The following rules lead to the first public release of the python generator:

  • 100% pure python: output of fedex_python should be 100% pure python code, that is to say that only a basic python installation is required to use the generated python module, without any additional dependency to SCL C or C++ code;
  • an EXPRESS interpreter: the syntax and semantics of the python code must be as close as possible to the original schema semantics, as well as to the EXPRESS syntax. As a result, the python code should work as a kind of ''EXPRESS interpreter'';
  • strongly typed: EXPRESS is strongly typed, whereas python is not. It appears necessary to implemented strong type checking of the arguments and parameters passed to the entity constructors, in order to check the quality of a set of instance or db population.

3. How to get it

The python generator was previously in a development stage, and had to be cloned from a separate branch from main. It has been renamed from fedex_python to exp2python.

Related code is available from the src/exp2python directory.

exp2python

-----> CMakeLists.txt: the cmake base file in order to build/install SCL

-----> README.md: README file in the md syntax

-----> examples: some examples of what exp2python should produce and how to deal with that

-----> python: python code of the SCL python package

-----> src: C/C++ code of the exp2python program

4. How to build

4.1. Build exp2python

By default, the build system will compile both fedex_plus and fedex_python. First create a directory named cmake-build at the root StepClassLibrary directory:

$ cd StepClassLibrary
$ mkdir cmake-build
$ cd cmake-build

Then run cmake:

$ cmake ..

The Makefile is created and available in the ./cmake-build/bin directory. Compile with the usual make command: the exp2python program should be locate into ./cmake-build/bin.

$ make
$ cd bin
$ ls -l
-rwxr-xr-x  1 thomas  staff   16136 10 jan 06:02 check-express
-rwxr-xr-x  1 thomas  staff  252128 10 jan 06:02 fedex_plus
-rwxr-xr-x  1 thomas  staff  256032 10 jan 06:42 fedex_python

You can check exp2python by just running:

$ ./exp2python -v
Build info for ./exp2_python: git commit id v0.8, build timestamp 21:44:08 on Jul  17 2017
http://github.com/stepcode/stepcode and scl-dev on google groups

4.2. Install the SCL python package

The generated modules need the SCL package to be installed. cd to the exp2python/python directory: $ cd src/exp2python/python And then run the install script with: $ python setup.py install To check that everything was fine, try to import the SCL package from a python prompt:

$ python
>>> from SCL.SimpleDataTypes import *
>>> dir()
['BINARY', 'BOOLEAN', 'INTEGER', 'LOGICAL', 'NUMBER', 'REAL', 'STRING', 'Unknown', '__builtins__',
'__doc__', '__name__', '__package__']
>>> 

Everything is now ready for a first test.

5. Getting started

The following experiments are conducted with one simple example: the test_simple_geometry schema, defining three entities (point, vector and circle). Let's consider the file name for this schema is a_simple_schema.exp (if you want to run the following, copy/paste the content below and save it as simple_schema.exp.

SCHEMA test_simple_geometry;

ENTITY point;
  x : REAL;
  y : REAL;
  z : REAL;
  name : OPTIONAL STRING;
END_ENTITY;

ENTITY vector;
  vx : REAL;
  vy : REAL;
  name : OPTIONAL STRING;
END_ENTITY;

ENTITY circle;
  centre : point;
  radius : REAL;
  axis : vector;
DERIVE
  area : REAL := PI*radius**2;
  perimeter : REAL := 2.0*PI*radius;

END_ENTITY;
END_SCHEMA;

This schema was chosen because of its simplicity. The purpose is now to create data, from this definition, using the SCL python generator.

5.1. Process exp2python over the EXPRESS schema

This is simply done by running:

$ exp2python simple_schema.exp
 Running exp2python
Resolution successful.
Writing python module...Done.

exp2python parsed the express file and generated a python module named test_simple_geometry.py (pattern is the_schema_name.py):

$ ls -l
$ ls -l
total 24
-rw-r--r--@ 1 thomas  staff   366 10 jan 10:01 simple_schema.exp
-rw-r--r--  1 thomas  staff  4703 10 jan 10:01 test_simple_geometry.py

We'll see later the contents of this file and the way exp2python mapped EXPRESS to python semantics. But now, let's play with the generated module:

$ python
>>> from test_simple_geometry import *
>>> dir()
['ABS', 'ACOS', 'ARRAY', 'ASIN', 'ATAN', 'Aggregate', 'Algorithm', 'BAG', 'BINARY', 'BLENGTH', 'BOOLEAN', 'BaseAggregate', 'BaseEntityClass', 'BaseType', 'CONST_E', 'COS', 'ENUMERATION', 'EXISTS', 'EXP', 'EnumerationId', 'FALSE', 'FORMAT', 'FUNCTION', 'HIBOUND', 'HIINDEX', 'INTEGER', 'LENGTH', 'LIST', 'LOBOUND', 'LOG', 'LOG10', 'LOG2', 'LOGICAL', 'LOINDEX', 'NUMBER', 'NVL', 'ODD', 'PI', 'PROCEDURE', 'REAL', 'ROLESOF', 'Rule', 'SCL_float_epsilon', 'SELECT', 'SET', 'SIN', 'SIZEOF', 'STRING', 'TAN', 'TRUE', 'TYPEOF', 'USEDIN', 'Unknown', 'VALUE', 'VALUE_IN', 'VALUE_UNIQUE', '__builtins__', '__doc__', '__name__', '__package__', 'check_type', 'circle', 'math', 'point', 'schema_name', 'schema_scope', 'sys', 'vector']
>>>

5.2. Let's create a point instance

In order to create an instance, you must pass all arguments, including optional arguments. point_A is the point of coordinates (1,2.5,3), with name "A":

>>> point_A = point(REAL(1),REAL(2.5),REAL(3),STRING("A"))

Instance attributes can be listed with the usual dir python commant:

>>> dir(point_A)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_name', '_x', '_y', '_z', 'name', 'x', 'y', 'z']

The attributes values name, x, y and z are python properties:

>>> point_A.x
1.0
>>> point A.y
2.5
>>> point_A.z
3
>>> point_A.name
'A'

As python properties, they can easily be changed, for instance:

>>> point_A.x = REAL(1.5)
>>> point_A.x
1.5

If ever you try to pass the wrong expected type, or if you set to None the value of an attribute that can't be OPTIONAL, exceptions will be raised:

>>> point_A.x = INTEGER(2)
Traceback (most recent call last):
  File "", line 1, in 
  File "test_simple_geometry.py", line 177, in fset
    check_type(value,REAL)
  File "/Library/Python/2.6/site-packages/SCL/TypeChecker.py", line 49, in check_type
    raise TypeError('Type of argument number_of_sides must be %s (you passed %s)'%(expected_type,type(instance)))
TypeError: Type of argument number_of_sides must be  (you passed )
>>> point_A.x
1.5
>>> point_A.x = None
Traceback (most recent call last):
  File "", line 1, in 
  File "test_simple_geometry.py", line 176, in fset
    raise AssertionError('Argument x is mantatory and can not be set to None')
AssertionError: Argument x is mantatory and can not be set to None
>>> point_A.x
1.5
>>> point_A.name = None
>>> point_A

Note that you can inspect attributes values with a simple print:

>>> print point_A
#  class description:
Entity point definition.

	:param x
	:type x:REAL

	:param y
	:type y:REAL

	:param z
	:type z:REAL

	:param name
	:type name:STRING
	
# Instance attributes:
	name:None
	x:1.5
	y:2.5
	z:3.0

5.3 Create a vector

>>> vector_v = vector(REAL(1),REAL(0),STRING("V"))

5.4 Create a circle

It's now possible to create circle, of radius 3.

  • create a circle
>>> c = circle(point_A,REAL(3),vector_v)

5.5 Playing with DERIVED attributes

area and perimeter are known to be DERIVED attributes, that means they can't be set or initialized, but they are inferred.

>>> c.area = REAL(5)
Traceback (most recent call last):
  File "", line 1, in 
  File "test_simple_geometry.py", line 133, in fset
    raise AssertionError('Argument area is DERIVED. It is computed and can not be set to any value')
AssertionError: Argument area is DERIVED. It is computed and can not be set to any value
>>> c.area
28.274333882308138
>>> c.perimeter
18.849555921538759

area and perimeter were computed according to the mathematical expression defined in the EXPRESS schema. You can change the radius to check that the new values of area and parameter are up-to-date with this change (note that these values are computed when the attribute is called, according to a kind of 'pull' pipe):

    >>> c.radius = REAL(4)
    >>> c.area
    >>> c.area
    50.26548245743669
    >>> c.perimeter
    25.132741228718345

6. Implemented features

  • entities are mapped to python classes
  • OPTIONAL argument
  • single and multiple inheritance
  • aggregates
  • enums and selects

Note: these features still need to be unit tested to check specific use cases that should not been supported.

7. TODO LIST

7.1. On going work

  • use the python super() type to implement multiple inheritance. Python's mro is similar to EXPRESS
  • move the current implementation of expression evaluation from eval() (which is known to be unsafe) to python functions
  • Part21 importer: a draft for a Part21 reader is available in the SCL package. It has not been tested for a while and has to be modified.
  • enums take strings parameters. They should rather take instances.
  • run exp2python on STEP IS schema files (ap203, ap214 etc.)

7.2. Planned

  • extend the set of supported unitary schemas
  • Part28 importer
  • Part21/28 exporter
  • local rules checking
  • port to Python 3
  • [Please add your suggestions here]