|
| 1 | +# Getting Started |
| 2 | + |
| 3 | +To install dependencies to build a Smithy-Dafny project in Python, first run |
| 4 | + |
| 5 | +``` |
| 6 | +make setup_python |
| 7 | +``` |
| 8 | + |
| 9 | +# Makefile Variables |
| 10 | + |
| 11 | +To build a Smithy-Dafny Python project, you will need to add the following variables to your Makefile. |
| 12 | + |
| 13 | +- `PYTHON_MODULE_NAME`: The name of the Python module for generated code. |
| 14 | +- `TRANSLATION_RECORD_PYTHON` : For each dependency (including StandardLibrary), the path to the generated `.dtr` file for that dependency’s generated Dafny code. |
| 15 | +- `PYTHON_DEPENDENCY_MODULE_NAMES`: For each dependency, this is a map from a Smithy namespace to the `PYTHON_MODULE_NAME` variable for that dependency. |
| 16 | + |
| 17 | +Examples: |
| 18 | + |
| 19 | +- [Crypto Tools’ MPL](https://github.com/aws/aws-cryptographic-material-providers-library/blob/main/AwsCryptographicMaterialProviders/Makefile). This project sets all of these variables with multiple dependencies. |
| 20 | + |
| 21 | +### Smithy-Dafny Python Project Structure |
| 22 | + |
| 23 | +(Assuming you have made the Makefile updates specified above, then:) |
| 24 | + |
| 25 | +These commands will set up the base file structure for a Python project: |
| 26 | + |
| 27 | +1. `make polymorph_dafny` |
| 28 | +2. `make polymorph_python` |
| 29 | +3. `make transpile_python` |
| 30 | + |
| 31 | +This structure follows: |
| 32 | + |
| 33 | +``` |
| 34 | +MyProject/runtimes/python/ |
| 35 | +├── src/ |
| 36 | +│ └── my_project/ |
| 37 | +│ ├── __init__.py |
| 38 | +│ ├── internaldafny/ |
| 39 | +│ │ ├── generated/ |
| 40 | +│ │ │ ├── [all Dafny-generated source code from `make transpile_python`] |
| 41 | +│ │ │ └── dafny_src-py.dtr |
| 42 | +│ │ └── extern/ |
| 43 | +│ │ ├── __init__.py |
| 44 | +│ │ └── [all manually written externs] |
| 45 | +│ └── smithygenerated/ |
| 46 | +│ └── my_project/ |
| 47 | +│ └── [all Smithy-Dafny-generated source code from `make polymorph_python`] |
| 48 | +├── test/ |
| 49 | +│ ├── __init__.py # empty |
| 50 | +│ └── internaldafny/ |
| 51 | +│ ├── __init__.py # empty |
| 52 | +│ ├── test_dafny_wrapper.py |
| 53 | +│ ├── generated/ |
| 54 | +│ │ ├── [all Dafny-generated test code from `make transpile_python`] |
| 55 | +│ └── extern/ |
| 56 | +│ ├── __init__.py |
| 57 | +│ └── [all manually test written externs] |
| 58 | +├── pyproject.toml |
| 59 | +├── tox.ini |
| 60 | +└── .gitignore |
| 61 | +``` |
| 62 | + |
| 63 | +- `src/` : Project source code. |
| 64 | + - `my_project/` : Python module that will be distributed to end users. This is the top-level name of the package that is imported by users. (ex. `import my_project`). The top-level name is defined by the Smithy-Dafny project’s Makefile’s `PYTHON_MODULE_NAME` variable; see [Makefile Updates](https://quip-amazon.com/SahBABLOQkya#temp:C:KIXe7b270945e2b419180867c04e). |
| 65 | + - `__init__.py`: Initializes generated Dafny code and performs optional project setup. See Appendix. |
| 66 | + - `internaldafny/`: Dafny-generated code and externs. |
| 67 | + - `generated/`: Dafny-generated code. |
| 68 | + - `dafny_src-py.dtr`: [Dafny translation record](https://dafny.org/dafny/DafnyRef/DafnyRef#sec-dtr-files) for this project’s generated code. This is critical to let other projects use this project as a dependency. Other projects will read this file to determine how to refer to this project’s generated Dafny code. |
| 69 | + - `extern/`: Holds manually-written externs. You don’t need this if you don’t have any source externs. |
| 70 | + - `__init__.py` : Initializes externs. See Appendix. |
| 71 | + - `smithygenerated/`: Smithy-Dafny generated code. |
| 72 | + - `my_project/`: Smithy-generated code for a LocalService’s namespace. |
| 73 | + - Note: If a Smithy-Dafny project has multiple LocalServices, there will be multiple folders in this directory. Each folder will named be the LocalService’s Smithy namespace converted to snakecase. For an example, see [Crypto Tools’ MPL](https://github.com/aws/aws-cryptographic-material-providers-library/tree/main/AwsCryptographicMaterialProviders/runtimes/python/src/aws_cryptographic_materialproviders/smithygenerated). |
| 74 | +- `test/`: Project test code. |
| 75 | + - `internaldafny/`: Dafny tests. |
| 76 | + - `test_dafny_wrapper.py`: See Appendix. |
| 77 | + - `generated/` : Dafny-generated test code. |
| 78 | + - `extern/`: Holds manually-written test externs. You don’t need this if you don’t have any test externs. |
| 79 | + - Any other test groupings, ex. `functional/`: Other tests for the project that aren’t Dafny-generated. For an example, See [Crypto Tools’ AwsCryptographyPrimitives](https://github.com/aws/aws-cryptographic-material-providers-library/tree/main/AwsCryptographyPrimitives/runtimes/python/test). |
| 80 | +- `pyproject.toml` : Project configuration and dependencies file. See Appendix. |
| 81 | +- `tox.ini` : Test configuration file. See Appendix. |
| 82 | + |
| 83 | +# Externs |
| 84 | + |
| 85 | +In Python, Dafny externs are not loaded at compile time because "compile time" doesn't exist in Python. Python code is interpreted, not compiled. |
| 86 | +Smithy-Dafny Python projects use this pattern to link extern code when the Smithy-Dafny module is loaded. |
| 87 | + |
| 88 | +Smithy-Dafny Python extern implementations must: |
| 89 | + |
| 90 | +1. Extend the generated class, if there is one |
| 91 | +2. “Export” itself to the generated class |
| 92 | + 1. **Why?** The Dafny-generated code expects that generated code behaves like it has extern code. The extern class that extends the generated class has this behavior. Overwriting the generated class with the extern class matches Dafny’s expectation. |
| 93 | + |
| 94 | +Annotated sample implementation: |
| 95 | + |
| 96 | +``` |
| 97 | +# If this is generated by `transpile_python` |
| 98 | +import my_project.internaldafny.generated.ModuleWithExtern.SomeExternCLass |
| 99 | +# Your extern might need code from the generated module |
| 100 | +from my_project.internaldafny.generated.ModuleWithExtern import * |
| 101 | +
|
| 102 | +# Extern should extend generated class, if one is generated |
| 103 | +class SomeExternClass(my_project.internaldafny.generated.SomeExternClass): |
| 104 | + ... |
| 105 | +
|
| 106 | +# Export extern class to generated class |
| 107 | +my_project.internaldafny.generated.ModuleWithExtern.SomeExternClass = SomeExternClass |
| 108 | +``` |
| 109 | + |
| 110 | +This extern file should be placed at `my_project/internaldafny/extern`. |
| 111 | + |
| 112 | +Every project that has externs must also have an `__init__.py` located at `my_project/internaldafny/extern` with the code at [TODO]. |
| 113 | + |
| 114 | +Examples: |
| 115 | + |
| 116 | +- [Extern TestModel](https://github.com/smithy-lang/smithy-dafny/tree/main-1.x/TestModels/Extern/runtimes/python/src/simple_dafnyextern/internaldafny/extern). |
| 117 | + |
| 118 | +# Appendix |
| 119 | + |
| 120 | +## Code Samples |
| 121 | + |
| 122 | +### Project Initialization Files |
| 123 | + |
| 124 | +Every Smithy-Dafny project should have a file at `src/your_project/__init__.py` with the following content: |
| 125 | + |
| 126 | +``` |
| 127 | +# Initialize generated Dafny |
| 128 | +from .internaldafny.generated import module_ |
| 129 | +
|
| 130 | +# Initialize externs |
| 131 | +# (If you don't have externs, omit this import) |
| 132 | +from .internaldafny import extern |
| 133 | +``` |
| 134 | + |
| 135 | +This file is executed when your project is imported. |
| 136 | + |
| 137 | +This file primarily exists to initialize a project’s Dafny code. |
| 138 | + |
| 139 | +First, this file initializes generated Dafny by importing the generated `module_.py`. |
| 140 | +`module_.py` is a Dafny-generated file that imports a project’s generated Dafny topologically (i.e. avoiding circular dependencies in import dependencies). |
| 141 | + |
| 142 | +Second, this file initializes externs. |
| 143 | +(This order is important; externs rely on generated code, and the generated code must be imported first to avoid circular dependencies.) |
| 144 | + |
| 145 | +To use externs in source code, you must add a file at `my_project/internaldafny/extern/__init__.py`. |
| 146 | +This file is executed by the root `__init__.py` when the project is imported. |
| 147 | +This file will import externs. |
| 148 | + |
| 149 | +``` |
| 150 | +from . import ( |
| 151 | + MyExtern |
| 152 | +) |
| 153 | +``` |
| 154 | + |
| 155 | +Examples: |
| 156 | + |
| 157 | +- [Constraints TestModel](https://github.com/smithy-lang/smithy-dafny/blob/main-1.x/TestModels/Constraints/runtimes/python/src/simple_constraints/__init__.py). This has no externs, so the `import extern` line is omitted. |
| 158 | +- [Extern TestModel](https://github.com/smithy-lang/smithy-dafny/tree/main-1.x/TestModels/Extern/runtimes/python/src/simple_dafnyextern). This has both generated and extern code. |
0 commit comments