Before you create a pull request, please create a new issue first to coordinate.
It might be that we are already working on the same or similar feature, but we haven't made our work visible yet.
We usually develop in a virtual environment. To create one, change to the root directory of the repository and invoke:
python -m venv venv
You need to activate it. On nix (Linux, Mac, *etc.):
source venv/bin/activate
and on Windows:
venv\Scripts\activate
Once you activated the virtual environment, you can install the package using pip:
pip3 install --editable .
The --editable option is necessary so that all the changes made to the repository are automatically reflected in the virtual environment (see also this StackOverflow question).
You also have to install the development dependencies, including different development modules:
pip3 install --editable dev
Please make yourself familiar with a general literature on compiler design. For example, Crafting Interpreters is a good book.
Please also read relevant publications related to the aas-core-codegen:
- https://www.researchgate.net/publication/375497058_Empowering_Industry_40_with_Generative_and_Model-Driven_SDK_Development
- https://www.researchgate.net/publication/356039469_Generative_and_Model-driven_SDK_development_for_the_Industrie_40_Digital_Twin,
- https://www.researchgate.net/publication/373325991_Generation_of_Digital_Twins_for_Information_Exchange_Between_Partners_in_the_Industrie_40_Value_Chain,
The repository is organized as follows:
aas-core-codegen/
├── aas_core_codegen/ Main Python package
│ ├── common.py Shared data structures and functions
(Error, Identifier, *etc.*)
│ ├── main.py Entry point (aas-core-codegen console command)
│ ├── run.py Run logic shared across generators
(coupled to main.py, but also used in tests)
│ ├── naming.py Generation of target-specific names based on identifiers
│ ├── string_helper_cases.py Cases of len, string slicing, find and
string comparison (the generated unit tests
follow Python on them)
│ ├── specific_implementations.py Look-up of implementation-specific snippets
│ ├── stringify.py Debug stringification of internal structures
│ ├── parse/ Parsing meta-model source files into an AST
│ ├── intermediate/ Intermediate Representation (IR)
│ ├── infer_for_schema/ Schema-constraint inference from the IR
│ ├── smoke/ Verify a model by running a smoke-test transpilation
│ ├── cpp/ C++ SDK generator
│ ├── csharp/ C# SDK generator
│ ├── golang/ Go SDK generator
│ ├── java/ Java SDK generator
│ ├── jsonschema/ JSON Schema generator
│ ├── python/ Python SDK generator
│ ├── typescript/ TypeScript SDK generator
│ └── xsd/ XSD schema generator
├── dev/ Development tooling (separate installable package)
│ ├── continuous_integration/ Pre-commit and CI scripts
│ ├── dev_scripts/ Developer helper scripts
│ ├── live_tests/ Live end-to-end tests against temporary-generated SDKs
│ ├── tests/ Unit and integration tests
│ └── test_data/ Golden test data (recorded expected output)
├── pyproject.toml Package configuration and entry-point declaration
├── CONTRIBUTING.rst This file
├── README.rst Project overview and usage instructions
└── CHANGELOG.rst Release notes
The main package lives under aas_core_codegen/. The development tooling -- scripts, tests (both integration and unit), pre-commit checks, etc. -- lives under dev/. For example, the script to convert an existing file into a sequence of stripped text blocks lives at dev/dev_scripts/convert_file_into_sequence_of_stripped_blocks.py.
The main entry point is aas_core_codegen/main.py, which corresponds to the aas-core-codegen console command declared in pyproject.toml.
It delegates the bulk of the work to run.py, which contains the run logic shared across generators and is also used directly in tests.
The transpilation pipeline follows three major stages, as described in the background literature: parsing the formalized meta-modelinto an AST, interpreting the AST into an Intermediate Representation (IR), and finally generating code from the IR for each target language.
The parse module takes the source of the formalized AAS meta-model -- written in a strongly-reduced subset of Python -- and translates it into a parsed AST.
The Python source is first tokenised with the asttokens library (_translate.source_to_atok), then walked and translated into our own typed structures (_translate.atok_to_symbol_table).
The result is a SymbolTable holding all the classes, enumerations, constants and constrained primitives found in the meta-model source.
Each of these objects is an instance of one of the types defined in _types.py (AbstractClass, ConcreteClass, Enumeration, etc.).
At this stage all cross-references -- superclasses, property types, method argument types -- are still plain Identifier strings; they are resolved only in the intermediate stage.
parse/tree.py provides our custom AST for constraint expressions.
It is a deliberate simplification of the full Python AST: only the constructs that appear in meta-model constraints are represented.
Nodes carry a reference back to the original ast.AST node for error reporting.
The module exposes Visitor and Transformer base classes for working with the tree.
parse/retree/ provides parsing and manipulation of the regular expressions that appear in meta-model constraints.
Because target languages have incompatible regex flavours -- in particular, C# cannot express character points above the UTF-16 range as a single literal -- we parse regexes into our own AST (retree._types) and re-render them per target platform via retree._render.
The retree._fix module applies the platform-specific fixes; the retree._parse module contains the parser; and retree._visitor provides a visitor base class for the regex AST.
The intermediate module interprets the parsed SymbolTable into a semantically
richer Intermediate Representation.
Whereas the parse module focuses purely on syntax, intermediate resolves cross-references, infers types, and crystallises the semantics needed by the code generators.
The translation is a two-pass process (implemented in _translate.py):
- First pass -- all
OurTypesymbols (classes, enumerations, constrained primitives) are instantiated as placeholder objects and collected into a symbol table. - Second pass -- each placeholder is populated: string-based references in properties, method signatures, inheritance lists, and constraint expressions are resolved to the actual symbol objects created in the first pass.
The types of the IR live in _types.py.
They are mutated during the two-pass translation but are thereafter treated as immutable (see Final and Constant Containers in the Coding Style Guide).
The remaining submodules each handle one aspect of the interpretation:
type_inference.py-- infers a type for every node in aparse.treeconstraint expression. The types roughly follow the annotations in_types.pybut are not identical; for example, aLENGTHpseudo-primitive exists only during type inference.construction.py-- analyses constructor bodies to understand how each class initialises its properties.pattern_verification.py-- analyses pattern-verification functions (functions defined in a meta-model that check a string against a regular expression), and records them in the IR so generators can emit the corresponding checks.revm.py-- transpiles regular expressions to instructions for a non-backtracking virtual machine (based on Ken Thompson's approach). The regular expression engines in many standard libraries (such as Java or C++) have exponential worst-case complexity on certain inputs; the VM provides linear-time matching and is used wherever the target language does not ship a comparable engine.doc.py-- types for resolved references inside docstrings. Documentation is parsed from RST-formatted docstrings and the symbol references within it are resolved to IR symbols._hierarchy.py-- builds an ontology of the class hierarchy (ancestors, descendants, topological order) that generators query when emitting visitor dispatching or inheritance-related code.
Schemas such as JSON Schema and XSD can express only a subset of the constraints found in the IR (lengths, patterns, enumeration membership).
The infer_for_schema module walks the constraint ASTs and matches structural patterns to extract the constraints that a schema can represent.
The result is a set of LenConstraint, PatternConstraint, etc. objects that the schema generators consume directly.
The matching logic lives in infer_for_schema/match.py.
Each supported language has its own generator package under aas_core_codegen/<language>/.
The packages share a consistent internal layout described below.
The entry point for an SDK generator.
It receives the SymbolTable from intermediate and orchestrates calls to the submodules in lib/ to produce the full SDK output on disk.
Each generator contains a set of modules shared across its lib/ submodules:
common.py-- helper functions and constants used by multiple submodules within the same generator. Examples include formatting utilities, primitive-type mappings, or reusable code fragments.naming.py-- derives language-idiomatic names (classes, interfaces, properties, methods, etc.) from theIdentifierstrings in the IR. For example, how to derive an interface name in Go or a property name in TypeScript from the IR identifier.description.py-- renders the documentation from the IR into the language-specific comment syntax (Sphinx docstrings for Python, XML doc-comments for C#, JSDoc for TypeScript, etc.).transpilation.py-- translatesparse.treeconstraint expressions from the IR into executable code in the target language. This covers the bodies of invariant checks and verification functions.
Each submodule in lib/ is responsible for generating one component of the output SDK.
The naming _generate_<component>.py mirrors the component it produces.
Most languages share the following components in the generated SDK:
types-- data structures representing the meta-model classes and enumerations.constants-- global constant values defined in the meta-model (e.g., constant sets of strings).stringification-- functions to convert enumerations to and from their string representations.jsonization-- de/serialization of model instances to and from JSON.xmlization-- de/serialization of model instances to and from XML.verification-- functions that check the invariants defined in the meta-model.common-- helpers shared across the generated SDK components.xmlcommon-- the XML primitives which every reader and writer of XML shares: the whitespace of XSD, the checks of the namespace, the consumption of the tags and the path into the document. It exists so that the de/serialization of the model and thexmlrpcbelow do not each carry a copy of them.
Additional components appear where the language or its community conventions require them:
reporting(C#, Go, Java, Python) -- data structures and utilities for collecting and formatting de/serialization and verification errors, used in languages where exception-based error propagation is not the preferred idiom. In Python it is narrower: it carries only the error paths of the verification, as the de/serialization there reports through exceptions instead.enhancing(C#, C++, Go, Java) -- wrapping and unwrapping model instances with custom user-defined data without modifying the generated types; see the documentation on enhancing in Go.visitation(C#, C++, Java) --VisitorandTransformerbase classes for traversing the model hierarchy; see Visitor pattern.copying(C#, Java) -- functions to produce deep copies of model instances.generation(Java) -- builder classes for constructing model instances incrementally.xmlrpc(C#, C++, Go, Java, Python, TypeScript) -- the subset of XML-RPC --<boolean>,<double>,<string>,<array>and<struct>-- which carries a JSON-able value, since JSON prescribes no XML representation of its own. Generated only for a meta-model which actually uses a JSON-able type.jsonvalueverification(C#, C++, Python) -- the walk which checks that a value is JSON-able at any depth, split out ofverificationwhere the language rewards a module which depends on no class of the meta-model. Generated only for a meta-model which actually uses a JSON-able type.index(TypeScript) -- a barrelindex.tsfile that re-exports all public symbols from the other SDK modules.iteration(C++) -- free functions to descend into and iterate over model instances, serving the same role thatvisitationfills for class-based languages.revm(C++) -- regular expressions, emitted because C++ lacks a cross-platform, linear-time regex engine in its standard library.wstringification(C++) -- wide-string (std::wstring) variants of the stringification functions.
Each submodule in tests/ generates source files for the SDK's own test suite.
The generated tests cover serialization round-trips, verification of valid and invalid instances, visitor traversal, and other SDK-level contracts.
The submodules follow the same naming convention as lib/: one _generate_<component> module per test suite produced.
Some languages require additional inference before code can be emitted. These inferences live in dedicated modules at the top level of the generator package:
golang/pointering.py-- determines which IR nodes correspond to Go pointer (*T) types versus value types. Go distinguishes them at the syntax level, so the generator must resolve nullability before rendering any expression.cpp/optionaling.py-- analogous topointering.pyfor C++: determines which nodes carrystd::optional<T>types.cpp/over.py-- names and collects the functions which the C++ verification and iteration generate over the values of the classes, lists, tuples and named unions, as C++11 has no generic lambdas to compose the lazy iterators with.java/optional.py-- determines which nodes useOptional<T>versus bare types in Java.
The live tests are end-to-end integration scripts that verify the generated SDKs are not only textually correct but also actually compile, type-check, and pass their own unit tests. They live in dev/live_tests/ and are run manually -- they are not part of the pre-commit pipeline because they require language-specific toolchains that are not universally available.
One script exists per supported language:
dev/live_tests/live_test_cpp.py-- C++dev/live_tests/live_test_csharp.py-- C#dev/live_tests/live_test_golang.py-- Godev/live_tests/live_test_java.py-- Javadev/live_tests/live_test_python.py-- Pythondev/live_tests/live_test_typescript.py-- TypeScript
Each script iterates over the golden test cases recorded in dev/test_data/main/<language>/expected/.
For every case it:
- Copies the expected output files (the generated SDK source) into a temporary working directory (or into
--output_dirif given). - Synthesizes the project boilerplate that is not part of the generated SDK itself -- build files, dependency manifests, project configuration -- because the code generator only produces the library source, not a complete project.
- Compiles (or type-checks) the assembled project.
- If a matching sub-directory exists under
dev/test_data/live_tests/<language>/test_data/<case>/, copies that test data into the project and runs the generated SDK's own unit test suite. The tests are first executed in record mode (the*_TEST_RECORD_MODEenvironment variable is set to1) so that any missing golden traces are written out.
The live tests are slow since they install the dependencies, bundle the packages and run the full test suites. To fail fast, run the fast checks first, which prepare the projects exactly like the live tests, but only compile, type-check and lint them:
dev/live_tests/fast_check_cpp.py-- C++ (the compile commands exported by CMake with-fsyntax-only, in parallel)dev/live_tests/fast_check_csharp.py-- C# (dotnet build)dev/live_tests/fast_check_golang.py-- Go (go test -exec true, which compiles the tests without running them)dev/live_tests/fast_check_java.py-- Java (javacdirectly, bypassing Maven)dev/live_tests/fast_check_python.py-- Python (mypyandruff)dev/live_tests/fast_check_typescript.py-- TypeScript (tsc,prettierandeslint)
The fast checks and the live test of the same language can share --output_dir, so that the live test re-uses the prepared projects, installed dependencies and incremental builds.
For TypeScript and Python, both install the dependencies only once, in --output_dir, and share them among all the cases.
The Python live test still installs the generated package of every case with pip install -e, but in a virtual environment of the case which inherits the packages of the shared virtual environment through a .pth file. This way, the cases do not clash and can run in parallel.
All live-test and fast-check scripts accept the same two options (fast_check_cpp.py additionally accepts --jobs):
--output_dir PATH- Write the assembled project into
PATHinstead of a temporary directory. The directory is not deleted after the run, which is useful for inspecting failures or iterating on a fix. If omitted, a temporary directory is created and removed automatically. --select REGEX- Run only the test cases whose directory name matches the given regular expression.
For example,
--select primitive_typesruns only theprimitive_typescase.
Each script requires the corresponding language toolchain to be installed and available on PATH:
- C++
cmakeand a C++ compiler. Dependencies (nlohmann-json,expat,tl-optional,tl-expected, andmpark-variantfor the meta-models with named unions) are managed via vcpkg. TheVCPKG_ROOTenvironment variable must point to the root of a vcpkg installation.The tests need C++17 for
<filesystem>, so the script compiles the libraries and the tests as C++17. Since the generated code must stay C++11-compatible, the script additionally compiles the library as C++11 in a dedicated target,*_cpp11_compatibility_check, which nothing links against.- C#
- The .NET SDK (
dotnetcommand). The script targetsnet6.0. - Go
The
gotoolchain andgoimports. Installgoimportswith:go install golang.org/x/tools/cmd/goimports@latest
The script looks for
goimportsonPATHand falls back to~/go/bin/goimports.- Java
mvn(Maven) and a JDK compatible with Java 17. The script synthesizes apom.xmland runsmvn test-compileandmvn test.- Python
- Python 3.9 or later (the same interpreter that runs the script).
The script creates a dedicated virtual environment inside the temporary project directory and installs
mypyandruffto type-check and lint the generated code before running the tests. - TypeScript
nodeandnpm. The script installs all dependencies vianpm installand then runs lint, build, and jest tests.
The development scripts in dev/dev_scripts/ assist with the development tasks such as authoring code-generation templates and debugging regex handling.
The code generators build their output from Stripped(...) string blocks in which indentation is expressed as {I}, {II}, {III}, etc. placeholders (one I per indentation level) rather than literal spaces or tabs.
Writing these blocks by hand is tedious and error-prone.
This script automates the conversion: given a plain source file it splits the content into top-level blocks (separated by blank lines that are not inside an indented region), escapes backslashes and curly brackets, and replaces leading indentation with the corresponding placeholder.
The result is printed to stdout as a series of Stripped(...) expressions ready to be pasted into a generator module.
Three browser-based (no server required) equivalents of convert_file_into_sequence_of_stripped_blocks.py, one per indentation style:
replace-curly-brackets-backslashes-and-four-space-indent.htmlreplace-curly-brackets-backslashes-and-two-space-indent.htmlreplace-curly-brackets-backslashes-and-tab-indent.html
Open the appropriate file in a browser, paste a code snippet into the upper text area, and the lower text area immediately shows the escaped and placeholder-substituted result. Checkboxes allow suppressing backslash or curly-bracket escaping when they are not needed. A Copy button copies the output to the clipboard. These pages are most convenient for converting small ad-hoc snippets interactively, whereas the Python script is better suited for processing entire files.
The parse/retree/ module re-renders regular expressions into platform-specific forms.
For each test case under dev/test_data/parse_retree/expected/ the test data contains a source.py file (the original Python-literal regex) and a rendered_regex.txt file (the expected re-rendered form).
This script walks all those pairs and prints the cases where the repr of rendered_regex.txt no longer matches source.py, making it easy to spot regressions or inconsistencies after changes to the regex renderer.
When large blocks of generated code are reordered rather than changed, diff output becomes noisy and hard to read.
This script helps by treating each line as a node and printing, for every line in --ours that also appears in --theirs, the pair of line numbers.
The resulting bipartite mapping shows which blocks have simply moved, making it easier to identify a minimal reordering and to verify that no content was accidentally lost.
Almost all tests in dev/tests/ are golden tests: the test runs the production code, serialises the result to text, and compares it against a pre-recorded expected file.
If the AAS_CORE_CODEGEN_TESTS_RERECORD environment variable is set, the test writes it (record mode); on subsequent runs without this environment variable the test fails if the output differs from the recorded file.
This makes it easy to review changes: git diff dev/test_data/ shows exactly what the output of the generators changed.
The golden files live in dev/test_data/.
The sub-directory structure mirrors the test-module structure: a test in dev/tests/cpp looks for its golden data under dev/test_data/cpp/.
Each top-level sub-directory of dev/test_data/ serves a distinct purpose:
common_meta_models/- Meta-model source files that are reused by multiple test suites.
For example,
common_meta_models/aas_core_meta.v3.pyis the full AAS V3.0 meta-model and is referenced by the intermediate and end-to-end main tests. parse/- Golden data for
dev/tests/parse/, which tests theaas_core_codegen.parsemodule. Cases are split intoexpected/(valid meta-models that must parse successfully) andunexpected/(meta-models that must fail with a specific error). Each leaf directory contains ameta_model.py(the input) and either anexpected_symbol_table.txt(the serialised parse result) or anexpected_error.txt(the expected error message). parse_retree/- Golden data for
dev/tests/parse_retree/, which tests theaas_core_codegen.parse.retreeregex parser and renderer. Each leaf directory underexpected/containssource.py(the input regex string literal),expected_parsed_regex.txt(the serialised regex AST), andrendered_regex.txt(the re-rendered platform-neutral form). Leaf directories underunexpected/containsource.pyandexpected_error.txt. intermediate/- Golden data for
dev/tests/intermediate/, which tests theaas_core_codegen.intermediatemodule. The layout mirrorsparse/:expected/cases containmeta_model.pyandexpected_symbol_table.txt;unexpected/cases containmeta_model.pyandexpected_error.txt. intermediate_revm/- Golden data for the REVM (regular-expression virtual machine) tests in
dev/tests/intermediate/. Each case hasexpected/andunexpected/sub-trees, following the same convention asintermediate/. smoke/- Golden data for
dev/tests/smoke/, which runs the smoke test over every meta-model undersmoke/test_main/expected/andcommon_meta_models/, and verifies that it either succeeds or fails with a known error. The smoke test runs the full generation of every target with dummy snippets into a temporary directory, so thataas-core-metacan check a meta-model against all the targets at once.unexpected/cases each contain ameta_model.pyand anexpected_stderr.txt. main/The most important test data directory: end-to-end golden tests that run the full transpilation pipeline for every target language. The structure is
main/<language>/expected/<case>/. Each case directory contains:meta_model.py-- the input meta-model.input/snippets/-- implementation-specific snippets supplied to the generator.expected_output/-- the expected SDK source tree produced by the generator, stored file-by-file.
These expected outputs serve a dual purpose: they are compared against during the test run and they are copied into the temporary project by the live tests (see Live Tests).
csharp/- Unit-test golden data for individual C# generator modules, e.g.,
csharp/test_types/for_generate_types.pyandcsharp/test_verification/for_generate_verification.py. Each leaf directory containsmeta_model.pyand an expected.cssnippet file that covers only the module under test, not a full SDK output. live_tests/- Test data used by the generated SDK's own unit tests when running under a live test.
The structure is
live_tests/<language>/test_data/<case>/. Each case directory contains serialised AAS instances (organised asJson/andXml/sub-directories) that the generated test suite reads to verify de/serialisation round-trips and constraint checking. See Live Tests for how this data is consumed.
Always write explicit types in function arguments.
If you really expect any type, mark that explicitly with Any.
Also always mark your local variables with a type if it can not be deduced.
For example:
lst = [] # type: List[str]For files, use typing.IO.
We prefer to put types in comments if they are short for readability. However, put them in code when they are multi-line:
some_map: Optional[
Dict[
SomeType,
AnotherType
]
] = NonePut _set for sets.
Prefer to designate the key with _by_ suffix.
For example, our_types_by_name is a mapping string (a name) 🠒 OurType.
Do not put get_ in method names.
If you want to make sure that the reader understands that some method is going to take longer than just a simple getter, prefix it with a meaningful verb such as compute_..., collect_... or retrieve_....
Do not duplicate module (package) or class names in the property names.
For example, if you have a class called Features, and want to add property to hold feature names, call the property simply names and not feature_names.
The caller code would otherwise redundantly read Features.feature_names or features.feature_names.
Do not call your modules, classes or functions ..._helpers or ..._utils.
A general name is most probably an indicator that there is either a flaw in the design (e.g., tight coupling which should be decoupled) or that there needs to be more thought spent on the naming.
If you have shared functionality in a module used by all or most of the submodules, put it in common submodule.
- Prefer functional programming to object-oriented programming.
- Better be explicit about the data flow than implicit.
- Prefer namespaced functions in a (sub)module instead of class methods.
- Side effects are difficult to trace.
- Context of a function is immediately visible when you look at arguments. A function is much easier to isolate and unit test than a class method.
- Use inheritance only when you need polymorphism.
- Do not use inheritance to share implementation; use namespaced functions for that.
- Prefer simplicity with a small number of classes; see http://thedailywtf.com/articles/Enterprise-Dependency-The-Next-Generation
- Use stateful objects in moderation.
- Some thoughts: https://medium.com/@cscalfani/goodbye-object-oriented-programming-a59cda4c0e53
Do not split script-like parts of the code into small chunks of one-time usage functions.
Use comments or regions to give overview.
It's ok to have long scripts that are usually more readable than a patchwork of small functions. Jumping around a file while keeping the context in head is difficult and error-prone.
Do not ever use stateful singletons. Pass objects down the calling stack even if it seems tedious at first.
Very common symbols such as Error or Identifier can be imported without prefix.
Usually, these symbols reside in aas_core_codegen.common.
In addition, do not prefix typing symbols such as List or Mapping, and the assertion functions from icontract design-by-contract library (see below).
Otherwise, the code would be completely unreadable.
All other symbols should be imported with an aliased prefix corresponding to the module. For example:
The indention constants (I, II etc.) are the only aliases allowed for symbols.
No other symbol should be aliased.
Use pathlib, not os.path.
Use design-by-contract as much as possible. We use icontract library.
Prefer immutable to mutable objects and structures.
Distinguish between internally and externally mutable structures. Annotate for immutability if the structures are only internally mutable.
For example, aas_core_codegen.intermediate._types are all marked as immutable since they should not be mutated after the intermediate translation phase.
They are, however, mutated within aas_core_codegen.intermediate._translate.
Double-asterisks are unpredictable for the reader, as all the keys need to be kept in mind, and overridden keys are simply ignored.
Please do not use ** operator unless it is utterly necessary, and explain in the comment why it is necessary.
Check for overwriting keys where appropriate.
Always use classes in the code.
Use TypedDict only if you have to deal with serialization (e.g., to JSON).
We intensively use PyCharms # region ... and # endregion to structure code into regions.
Mark notes with # NOTE ({github username}):.
Do not date the notes. The notes need to be adapted as the code around them changes, so the date quickly becomes misleading.
No # TODO in the code, please.
Comment only where the comments really add information. Do not write self-evident comments.
Comments should be in proper English. Write in simple present tense; avoid imperative mood.
Be careful about the capitals. Start the sentence with a capital. If you list bullet points, start with a capital, and do not forget conjectures:
# * We ...,
# * Then, ..., and finally
# * We ...The abbreviations are to be written properly in capitals (e.g., JSON and not json).
No code is allowed in the comments since it always rots.
You can write full-blown Sphinx docstrings, if you wish.
In many cases, a short docstring is enough.
We are not religious about :param ...: and :return fields.
Follow PEP 287. Use imperative mood in the docstrings.
Write unit tests for everything that can be obviously tested at the function/class level.
For many inter-dependent code regions, writing unit tests is too tedious or nigh impossible to later maintain.
For such parts of the system, prefer integration tests with comparisons against initially recorded and reviewed golden data.
See, for example, dev/tests/csharp/test_main.py or dev/tests/intermediate/test_translate.py.
The golden test data resides in dev/test_data/.
The structure of the test data directory follows in general the test module structure.
We provide a battery of pre-commit checks to make the code uniform and consistent across the code base.
We use black to format the code and use the default maximum line length of 88 characters.
To run all pre-commit checks, run from the root directory:
python continuous_integration/precommit.py
You can automatically re-format the code and fix certain files automatically with:
python continuous_integration/precommit.py --overwrite
The pre-commit script also runs as part of our continuous integration pipeline.
We follow Chris Beams' guidelines on commit messages:
- Separate subject from body with a blank line
- Limit the subject line to 50 characters
- Capitalize the subject line
- Do not end the subject line with a period
- Use the imperative mood in the subject line, full sentences in the body
- Wrap the body at 72 characters
- Use the body to explain what and why vs. how
If you are merging in a pull request, please squash before merging. We want to keep the Git history as simple as possible, and the commits during the development are rarely insightful later.