diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a5c8f2..6867496 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,22 @@ name: Project Tests -on: [push] +on: + push: + pull_request: + branches: + - main jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python 3.8 - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: 3.8 - name: Install dependencies run: | - cd $GITHUB_WORKSPACE python -m pip install --upgrade pip - pip install -r requirements.txt - - name: Run unittests - run: python -m unittest + pip install . + - name: Run unit tests + run: python -m unittest discover -s src diff --git a/README.md b/README.md index 71b4bda..31a5d5a 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,84 @@ # RapidPro Flow Toolkit -Toolkit for using spreadsheets to create and modify RapidPro flows -This is a clean up of https://github.com/IDEMSInternational/conversation-parser +Toolkit for using spreadsheets to create and modify RapidPro flows. -In the future this should also include a rewrite of https://github.com/geoo89/rapidpro_abtesting +# Quickstart -## Setup -1. Install python `>=3.6` -2. Run `pip install -r requirements.txt` - -## Console tool -``` -main.py {create_flows,flow_to_sheet} input1 input2 ... -o output --format {csv,xlsx,google_sheets} [--datamodels DATAMODELS] +```sh +pip install rpft +rpft --help ``` -Example: -``` -main.py create_flows tests/input/example1/content_index.csv -o out.json --format=csv --datamodels=tests.input.example1.nestedmodel +# Command Line Interface (CLI) + +The CLI allows spreadsheets in various formats to be converted to RapidPro flows in JSON format. Full details of the available options can be found via the help feature: + +```sh +rpft --help ``` -`main.py -h` for more details. +Below is a concrete example of a valid execution of the command line tool. The line breaks are merely for improving readability; the command would also be valid on a single line. -## Processing Google sheets +```sh +rpft create_flows \ + --output flows.json \ + --datamodels=tests.input.example1.nestedmodel \ + --format=csv \ + src/rpft/tests/input/example1/content_index.csv +``` -Follow the steps _Enable the API_ and _Authorize credentials for a desktop application_ from -https://developers.google.com/sheets/api/quickstart/python +# Using the toolkit in other Python projects -Note: Google sheets need to be in native Google sheets format, -not `XLSX`, `XLS`, `ODS`, etc +1. Add the package `rpft` as a dependency of your project e.g. in requirements.txt or pyproject.toml +1. Import the `create_flows` function +1. Call `create_flows` to convert spreadsheets to flows -## Running Tests -1. Run `python -m unittest` +```python +from rpft.converters import create_flows -# Components +sheets = ["sheet1.csv", "sheet2.csv"] +create_flows( + sheets, "flows.json", "csv", data_models="your_project.models" +) +``` -## Generic parsers from spreadsheets to data models +_It should be noted that this project is still considered beta software that may change significantly at any time._ -### Cell parser +# RapidPro flow spreadsheet format -See `./parsers/common/cellparser.py`. Parser to convert a spreadsheet cell -into a nested list. (Currently no nesting as only `;` is supported as an -element separator.) +The expected contents of the input spreadsheets is [documented separately][3]. -### Row parser +# Processing Google Sheets -See `./parsers/common/rowparser.py`. Parser to turn rows of a sheet -into a specified data model. Column headers determine which field of the -model the column contains data for, and different ways to address fields -in the data models are supported. See `./parsers/common/tests/test_full_rows.py` -and `./parsers/common/tests/test_differentways.py` for examples. +It is possible to read in spreadsheets via the Google Sheets API by specifying `--format=google_sheets` on the command line. Spreadsheets must be in the Google Sheets format rather than XLSX, CSV, etc. -The reverse operation is also supported, but only to a limited extent: -All models are spread out into a flat dict of fields, each becoming the -header of a column. +Instead of specifying paths to individual spreadsheets on your local filesystem, you must supply the IDs of the Sheets you want to process. The ID can be extracted from the URL of the Sheet i.e. docs.google.com/spreadsheets/d/**ID**/edit. -### Sheet parser +The toolkit will need to authenticate with the Google Sheets API and be authorized to access your spreadsheets. Two methods for doing this are supported. -See `./parsers/common/sheetparser.py`. +- **OAuth 2.0 for installed applications**: for cases where human interaction is possible e.g. when using the CLI +- **Service accounts**: for cases where interaction is not possible or desired e.g. in automated pipelines -## RapidPro tools +## Installed applications -### RapidPro models +Follow the steps in the [setup your environment section][1] of the Google Sheets quickstart for Python. -See `./rapidpro/models`. Models for flows, nodes, etc, with convenience -functions to assemble RapidPro flows. Each model has a `render` method -to render the model into a dictionary, that can be exported to a json -file whose fields are consistent with the format used by RapidPro. +Once you have a `credentials.json` file in your current working directory, the toolkit will automatically use it to authenticate whenever you use the toolkit. The refresh token (`token.json`) will be saved automatically in the current working directory so that it is not necessary to go through the full authentication process every time. -### Standard format flow parser +## Service accounts -See `./parsers/creation/flowparser.py`. Parser to turn sheets in -the standard format (Documentation TBD) into RapidPro flows. -See `./tests/input` and `./tests/output` for some examples. +Follow the steps in the [creating a service account section][2] to obtain a service account key. The toolkit will accept the key as an environment variable called `CREDENTIALS`. -Examples: -- `./tests/test_flowparser.py` -- `./parsers/creation/tests/test_flowparser.py` +```sh +export CREDENTIALS=$(cat service-account-key.json) +rpft ... +``` -### Parsing collections of flows (with templating) +# Development -See `./parsers/creation/contentindexparser.py`, `parse_all_flows`. -Examples: -- `./tests/test_contentindexparser.py` -- `./parsers/creation/tests/test_contentindexparser.py` +For instructions on how to set up your development environment for developing the toolkit, see the [development][4] page. -Documentation (request access): https://docs.google.com/document/d/1Onx2RhNoWKW9BQvFrgTc5R5hcwDy1OMsLKnNB7YxQH0/edit?pli=1# +[1]: https://developers.google.com/sheets/api/quickstart/python#set_up_your_environment +[2]: https://developers.google.com/identity/protocols/oauth2/service-account#creatinganaccount +[3]: https://docs.google.com/document/d/1Onx2RhNoWKW9BQvFrgTc5R5hcwDy1OMsLKnNB7YxQH0/edit?pli=1# +[4]: https://github.com/IDEMSInternational/rapidpro-flow-toolkit/docs/development.md diff --git a/coverage.sh b/coverage.sh old mode 100644 new mode 100755 index 4800315..79d1de6 --- a/coverage.sh +++ b/coverage.sh @@ -1,3 +1,3 @@ #!/bin/sh -python3 -m coverage run --source . --omit="*/test*" -m unittest +python3 -m coverage run --source src --omit="*/test*" -m unittest discover -s src python3 -m coverage html diff --git a/docs/components.md b/docs/components.md new file mode 100644 index 0000000..97e959a --- /dev/null +++ b/docs/components.md @@ -0,0 +1,49 @@ +# Generic parsers from spreadsheets to data models + +## Cell parser + +See `./parsers/common/cellparser.py`. Parser to convert a spreadsheet cell +into a nested list. (Currently no nesting as only `;` is supported as an +element separator.) + +## Row parser + +See `./parsers/common/rowparser.py`. Parser to turn rows of a sheet +into a specified data model. Column headers determine which field of the +model the column contains data for, and different ways to address fields +in the data models are supported. See `./parsers/common/tests/test_full_rows.py` +and `./parsers/common/tests/test_differentways.py` for examples. + +The reverse operation is also supported, but only to a limited extent: +All models are spread out into a flat dict of fields, each becoming the +header of a column. + +## Sheet parser + +See `./parsers/common/sheetparser.py`. + +# RapidPro tools + +## RapidPro models + +See `./rapidpro/models`. Models for flows, nodes, etc, with convenience +functions to assemble RapidPro flows. Each model has a `render` method +to render the model into a dictionary, that can be exported to a json +file whose fields are consistent with the format used by RapidPro. + +## Standard format flow parser + +See `./parsers/creation/flowparser.py`. Parser to turn sheets in +the standard format (Documentation TBD) into RapidPro flows. +See `./tests/input` and `./tests/output` for some examples. + +Examples: +- `./tests/test_flowparser.py` +- `./parsers/creation/tests/test_flowparser.py` + +## Parsing collections of flows (with templating) + +See `./parsers/creation/contentindexparser.py`, `parse_all_flows`. +Examples: +- `./tests/test_contentindexparser.py` +- `./parsers/creation/tests/test_contentindexparser.py` diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..2d9f7b9 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,73 @@ +# Setup + +1. Install Python >= 3.7 +1. Clone the source code repository: `git clone https://github.com/IDEMSInternational/rapidpro-flow-toolkit` +1. Change to the project root directory: `cd rapidpro_flow_toolkit` +1. Create a virtual environment: `python -m venv .venv` +1. Activate venv: `source .venv/bin/activate` +1. Upgrade pip: `pip install --upgrade pip` +1. Install the project in dev mode: `pip install --editable .` + +# Running tests + +```sh +python -m unittest discover -s src +``` + +# Build + +1. Install the build tool: `pip install --upgrade build` +1. Build the project: `python -m build` +1. Results of the build should be found in the `dist` directory + +To verify that the build produced a valid and working Python package, install the package in a clean virtual environment. + +```sh +python -m venv build_verification +source build_verification/bin/activate +pip install dist/rpft-x.y.z-py3-none-any.whl +rpft --help +deactivate +rm -rf venv_verification +``` + +# Release + +You will need: + +- sufficient access to the Github repo to create Releases +- the project in a tested and fully-working state + +Once ready: + +1. Create a release in Github +1. Decide what the next version number should be +1. Edit the release notes +1. Publish the release + +Upon publishing, the project should be tagged automatically with the release version number. This can be used later to build specific versions of the project. + +# Upload to PyPI + +## TestPyPI + +It is recommended to get comfortable with uploading packages to PyPI by first experimenting on the test index. See [Using TestPyPI] for details. + +## PyPI + +You will need: + +- an account on PyPI +- membership of the `rapidpro-flow-tools` project in PyPI +- the `twine` package installed + +Once ready: + +1. Check out the project at the relevant release tag +1. Build the project as per the [Build](#build) section +1. Upload to TestPyPI: `twine upload -r testpypi dist/*` +1. Check everything looks ok +1. Upload to PyPI: `twine upload dist/*` + + +[1]: https://packaging.python.org/en/latest/guides/using-testpypi/ diff --git a/main.py b/main.py deleted file mode 100644 index 10ab199..0000000 --- a/main.py +++ /dev/null @@ -1,58 +0,0 @@ -import json -import argparse - -from parsers.creation.contentindexparser import ContentIndexParser -from parsers.creation.tagmatcher import TagMatcher -from parsers.sheets.csv_sheet_reader import CSVSheetReader -from parsers.sheets.xlsx_sheet_reader import XLSXSheetReader -from parsers.sheets.google_sheet_reader import GoogleSheetReader -from rapidpro.models.containers import RapidProContainer -from logger.logger import initialize_main_logger - -LOGGER = initialize_main_logger() - -def main(): - description = 'Generate RapidPro JSON from Spreadsheet(s).\n\n'\ - 'Example usage: \n'\ - 'create_flows tests/input/example1/content_index.csv --output=out.json --format=csv --datamodels=tests.input.example1.nestedmodel' - parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument('command', - choices=["create_flows", "flow_to_sheet"], - help='create_flows: Create flows as defined in the input content index sheet.\n'\ - ' input: Content index sheet(s) defining flows to be created.\n'\ - ' output: RapidPro JSON file for writing output flows.\n'\ - 'flow_to_sheet: Convert input file into a set of sheets encoding the flows.\n' - ' input: RapidPro JSON file to read the flows from.\n'\ - ' output: File to write the output sheets to.\n') - parser.add_argument('input', nargs='+', help='Filename, or sheet_id for google sheets (https://docs.google.com/spreadsheets/d/[spreadsheet_id]/edit)') - parser.add_argument('-o', '--output', required=True, help='Filename') - parser.add_argument('-f', '--format', required=True, choices=["csv", "xlsx", "google_sheets"], help='Sheet format for reading/writing.') - parser.add_argument('--datamodels', help='Module defining models for data sheets. E.g. if the definitions reside in ./myfolder/mysubfolder/mymodelsfile.py, then this argument should be myfolder.mysubfolder.mymodelsfile') - parser.add_argument('--tags', nargs='*', help='Tags to filter the content index sheet. A sequence of lists, with each list starting with an integer (tag position) followed by tags to include for this position. Example: 1 foo bar 2 baz means: only include rows if tags:1 is empty, foo or bar, and tags:2 is empty or baz.') - args = parser.parse_args() - - if args.command != 'create_flows': - print(f"Command {args.command} currently unsupported.") - return - - tag_matcher = TagMatcher(args.tags) - for index, infile in enumerate(args.input): - if args.format == 'csv': - sheet_reader = CSVSheetReader(infile) - elif args.format == 'xlsx': - sheet_reader = XLSXSheetReader(infile) - elif args.format == 'google_sheets': - sheet_reader = GoogleSheetReader(infile) - else: - print(f"Format {args.format} currently unsupported.") - return - if index == 0: - ci_parser = ContentIndexParser(sheet_reader, args.datamodels, tag_matcher=tag_matcher) - else: - ci_parser.add_content_index(sheet_reader) - output = ci_parser.parse_all() - json.dump(output.render(), open(args.output, 'w'), indent=4) - - -if __name__ == '__main__': - main() diff --git a/parsers/creation/datarowmodel.py b/parsers/creation/datarowmodel.py deleted file mode 100644 index e9b4b5b..0000000 --- a/parsers/creation/datarowmodel.py +++ /dev/null @@ -1,4 +0,0 @@ -from parsers.common.rowparser import ParserModel - -class DataRowModel(ParserModel): - ID : str = '' diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2d33f76 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,48 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "rpft" +version = "1.0.0" +description = "Toolkit for using spreadsheets to create and modify RapidPro flows" +authors = [ + {name = "IDEMS International", email = "communications@idems.international"}, +] +readme = "README.md" +requires-python = ">=3.7" +keywords = ["rapidpro", "flow", "tools", "toolkit"] +license = {text = "LGPL-2.1-or-later"} +classifiers = [ + "Development Status :: 4 - Beta", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Text Processing :: General", + "Topic :: Utilities", +] +dependencies = [ + "Jinja2~=3.0.3", + "google-api-python-client~=2.6.0", + "google-auth-oauthlib~=0.4.4", + "networkx~=2.5.1", + "openpyxl~=3.0.7", + "pydantic~=1.8.2", + "tablib[ods]>=3.1.0", +] + +[project.urls] +Homepage = "https://github.com/IDEMSInternational/rapidpro-flow-toolkit" +Repository = "https://github.com/IDEMSInternational/rapidpro-flow-toolkit" + +[project.scripts] +rpft = "rpft.cli:main" diff --git a/logger/__init__.py b/src/rpft/__init__.py similarity index 100% rename from logger/__init__.py rename to src/rpft/__init__.py diff --git a/src/rpft/cli.py b/src/rpft/cli.py new file mode 100644 index 0000000..39f9d99 --- /dev/null +++ b/src/rpft/cli.py @@ -0,0 +1,77 @@ +import argparse + +from rpft.converters import create_flows +from rpft.logger.logger import initialize_main_logger + +LOGGER = initialize_main_logger() + + +def main(): + args = create_parser().parse_args() + create_flows( + args.input, + args.output, + args.format, + data_models=args.datamodels, + tags=args.tags, + ) + + +def create_parser(): + parser = argparse.ArgumentParser( + description=( + "Generate RapidPro flows JSON from spreadsheets\n" + "\n" + "Example usage:\n" + "create_flows --output=flows.json --format=csv " + "--datamodels=example.models sheet1.csv sheet2.csv" + ), + formatter_class=argparse.RawTextHelpFormatter, + ) + parser.add_argument( + "command", + choices=["create_flows"], + help=( + "create_flows: create flows from spreadsheets\n" + "flow_to_sheet: create spreadsheets from flows (not implemented)" + ), + ) + parser.add_argument( + "input", + nargs="+", + help=( + "CSV/XLSX: path to files on local file system\n" + "Google Sheets: sheet ID i.e. https://docs.google.com/spreadsheets/d/[ID]/edit" + ), + ) + parser.add_argument("-o", "--output", required=True, help="Output JSON filename") + parser.add_argument( + "-f", + "--format", + required=True, + choices=["csv", "xlsx", "google_sheets"], + help="Input sheet format", + ) + parser.add_argument( + "--datamodels", + help=( + "Module defining models for data sheets e.g. if the definitions reside in " + "./myfolder/mysubfolder/mymodelsfile.py, then this argument should be " + "myfolder.mysubfolder.mymodelsfile" + ), + ) + parser.add_argument( + "--tags", + nargs="*", + help=( + "Tags to filter the content index sheet. A sequence of lists, with each " + "list starting with an integer (tag position) followed by tags to include " + "for this position. Example: 1 foo bar 2 baz means: only include rows if " + "tags:1 is empty, foo or bar, and tags:2 is empty or baz" + ), + ) + return parser + + +if __name__ == "__main__": + main() diff --git a/src/rpft/converters.py b/src/rpft/converters.py new file mode 100644 index 0000000..f6d28d7 --- /dev/null +++ b/src/rpft/converters.py @@ -0,0 +1,32 @@ +import json + +from rpft.parsers.creation.contentindexparser import ContentIndexParser +from rpft.parsers.creation.tagmatcher import TagMatcher +from rpft.parsers.sheets.csv_sheet_reader import CSVSheetReader +from rpft.parsers.sheets.xlsx_sheet_reader import XLSXSheetReader +from rpft.parsers.sheets.google_sheet_reader import GoogleSheetReader + + +def create_flows(input_files, output_file, sheet_format, data_models=None, tags=[]): + parser = ContentIndexParser( + user_data_model_module_name=data_models, tag_matcher=TagMatcher(tags) + ) + + for input_file in input_files: + reader = create_sheet_reader(sheet_format, input_file) + parser.add_content_index(reader) + + json.dump(parser.parse_all().render(), open(output_file, "w"), indent=4) + + +def create_sheet_reader(sheet_format, input_file, credentials=None): + if sheet_format == "csv": + sheet_reader = CSVSheetReader(input_file) + elif sheet_format == "xlsx": + sheet_reader = XLSXSheetReader(input_file) + elif sheet_format == "google_sheets": + sheet_reader = GoogleSheetReader(input_file) + else: + raise Exception(f"Format {sheet_format} currently unsupported.") + + return sheet_reader diff --git a/parsers/__init__.py b/src/rpft/logger/__init__.py similarity index 100% rename from parsers/__init__.py rename to src/rpft/logger/__init__.py diff --git a/logger/logger.py b/src/rpft/logger/logger.py similarity index 100% rename from logger/logger.py rename to src/rpft/logger/logger.py diff --git a/parsers/common/__init__.py b/src/rpft/parsers/__init__.py similarity index 100% rename from parsers/common/__init__.py rename to src/rpft/parsers/__init__.py diff --git a/parsers/common/tests/__init__.py b/src/rpft/parsers/common/__init__.py similarity index 100% rename from parsers/common/tests/__init__.py rename to src/rpft/parsers/common/__init__.py diff --git a/parsers/common/cellparser.py b/src/rpft/parsers/common/cellparser.py similarity index 98% rename from parsers/common/cellparser.py rename to src/rpft/parsers/common/cellparser.py index ccfc1a9..1e41e27 100644 --- a/parsers/common/cellparser.py +++ b/src/rpft/parsers/common/cellparser.py @@ -1,7 +1,7 @@ from jinja2 import Environment from jinja2.nativetypes import NativeEnvironment from jinja2 import contextfilter -from logger.logger import get_logger, logging_context +from rpft.logger.logger import get_logger, logging_context LOGGER = get_logger() diff --git a/parsers/common/rowdatasheet.py b/src/rpft/parsers/common/rowdatasheet.py similarity index 100% rename from parsers/common/rowdatasheet.py rename to src/rpft/parsers/common/rowdatasheet.py diff --git a/parsers/common/rowparser.py b/src/rpft/parsers/common/rowparser.py similarity index 100% rename from parsers/common/rowparser.py rename to src/rpft/parsers/common/rowparser.py diff --git a/parsers/common/sheetparser.py b/src/rpft/parsers/common/sheetparser.py similarity index 94% rename from parsers/common/sheetparser.py rename to src/rpft/parsers/common/sheetparser.py index e1e376e..b6f5035 100644 --- a/parsers/common/sheetparser.py +++ b/src/rpft/parsers/common/sheetparser.py @@ -1,7 +1,6 @@ import copy -from .rowdatasheet import RowDataSheet -from logger.logger import get_logger, logging_context -import pydantic +from rpft.parsers.common.rowdatasheet import RowDataSheet +from rpft.logger.logger import get_logger, logging_context LOGGER = get_logger() diff --git a/parsers/creation/__init__.py b/src/rpft/parsers/common/tests/__init__.py similarity index 100% rename from parsers/creation/__init__.py rename to src/rpft/parsers/common/tests/__init__.py diff --git a/parsers/common/tests/mock_cell_parser.py b/src/rpft/parsers/common/tests/mock_cell_parser.py similarity index 100% rename from parsers/common/tests/mock_cell_parser.py rename to src/rpft/parsers/common/tests/mock_cell_parser.py diff --git a/parsers/common/tests/mock_row_parser.py b/src/rpft/parsers/common/tests/mock_row_parser.py similarity index 100% rename from parsers/common/tests/mock_row_parser.py rename to src/rpft/parsers/common/tests/mock_row_parser.py diff --git a/parsers/common/tests/mock_sheetparser.py b/src/rpft/parsers/common/tests/mock_sheetparser.py similarity index 93% rename from parsers/common/tests/mock_sheetparser.py rename to src/rpft/parsers/common/tests/mock_sheetparser.py index 01d7b3c..8c95007 100644 --- a/parsers/common/tests/mock_sheetparser.py +++ b/src/rpft/parsers/common/tests/mock_sheetparser.py @@ -1,5 +1,5 @@ import copy -from parsers.common.sheetparser import SheetParser +from rpft.parsers.common.sheetparser import SheetParser class MockSheetParser(SheetParser): diff --git a/parsers/common/tests/models.py b/src/rpft/parsers/common/tests/models.py similarity index 90% rename from parsers/common/tests/models.py rename to src/rpft/parsers/common/tests/models.py index aa790f8..1f8b521 100644 --- a/parsers/common/tests/models.py +++ b/src/rpft/parsers/common/tests/models.py @@ -1,5 +1,5 @@ from typing import List -from parsers.common.rowparser import ParserModel +from rpft.parsers.common.rowparser import ParserModel class Condition(ParserModel): diff --git a/parsers/common/tests/test_cellparser.py b/src/rpft/parsers/common/tests/test_cellparser.py similarity index 98% rename from parsers/common/tests/test_cellparser.py rename to src/rpft/parsers/common/tests/test_cellparser.py index 1821a12..96b67f0 100644 --- a/parsers/common/tests/test_cellparser.py +++ b/src/rpft/parsers/common/tests/test_cellparser.py @@ -1,8 +1,8 @@ import unittest from typing import List -from parsers.common.cellparser import CellParser -from parsers.common.rowparser import ParserModel +from rpft.parsers.common.cellparser import CellParser +from rpft.parsers.common.rowparser import ParserModel class InnerModel(ParserModel): diff --git a/parsers/common/tests/test_differentways.py b/src/rpft/parsers/common/tests/test_differentways.py similarity index 94% rename from parsers/common/tests/test_differentways.py rename to src/rpft/parsers/common/tests/test_differentways.py index 7187985..0261400 100644 --- a/parsers/common/tests/test_differentways.py +++ b/src/rpft/parsers/common/tests/test_differentways.py @@ -1,9 +1,9 @@ import unittest import json -from parsers.common.rowparser import RowParser -from .mock_cell_parser import MockCellParser -from .models import FromWrong +from rpft.parsers.common.rowparser import RowParser +from rpft.parsers.common.tests.mock_cell_parser import MockCellParser +from rpft.parsers.common.tests.models import FromWrong output_instance = { diff --git a/parsers/common/tests/test_full_rows.py b/src/rpft/parsers/common/tests/test_full_rows.py similarity index 96% rename from parsers/common/tests/test_full_rows.py rename to src/rpft/parsers/common/tests/test_full_rows.py index 903e6b4..c25c0cb 100644 --- a/parsers/common/tests/test_full_rows.py +++ b/src/rpft/parsers/common/tests/test_full_rows.py @@ -1,9 +1,9 @@ import unittest import json -from parsers.common.rowparser import RowParser -from parsers.creation.flowrowmodel import FlowRowModel -from .mock_cell_parser import MockCellParser +from rpft.parsers.common.rowparser import RowParser +from rpft.parsers.creation.flowrowmodel import FlowRowModel +from rpft.parsers.common.tests.mock_cell_parser import MockCellParser input1 = { diff --git a/parsers/common/tests/test_rowdatasheet.py b/src/rpft/parsers/common/tests/test_rowdatasheet.py similarity index 95% rename from parsers/common/tests/test_rowdatasheet.py rename to src/rpft/parsers/common/tests/test_rowdatasheet.py index 27cf003..e127dfd 100644 --- a/parsers/common/tests/test_rowdatasheet.py +++ b/src/rpft/parsers/common/tests/test_rowdatasheet.py @@ -3,8 +3,8 @@ from typing import List, Dict, Optional from collections import OrderedDict -from .mock_row_parser import MockRowParser -from parsers.common.rowdatasheet import RowDataSheet +from rpft.parsers.common.tests.mock_row_parser import MockRowParser +from rpft.parsers.common.rowdatasheet import RowDataSheet rowA = OrderedDict([ diff --git a/parsers/common/tests/test_sheetparser.py b/src/rpft/parsers/common/tests/test_sheetparser.py similarity index 87% rename from parsers/common/tests/test_sheetparser.py rename to src/rpft/parsers/common/tests/test_sheetparser.py index 10e9b1d..9a82fe6 100644 --- a/parsers/common/tests/test_sheetparser.py +++ b/src/rpft/parsers/common/tests/test_sheetparser.py @@ -4,10 +4,10 @@ from collections import OrderedDict import tablib -from parsers.common.rowparser import RowParser, ParserModel -from .mock_row_parser import MockRowParser -from parsers.common.rowdatasheet import RowDataSheet -from parsers.common.sheetparser import SheetParser +from rpft.parsers.common.rowparser import RowParser, ParserModel +from rpft.parsers.common.tests.mock_row_parser import MockRowParser +from rpft.parsers.common.rowdatasheet import RowDataSheet +from rpft.parsers.common.sheetparser import SheetParser class MainModel(ParserModel): field1: str = '' diff --git a/parsers/common/tests/test_unparse.py b/src/rpft/parsers/common/tests/test_unparse.py similarity index 97% rename from parsers/common/tests/test_unparse.py rename to src/rpft/parsers/common/tests/test_unparse.py index e92de22..9cf1d71 100644 --- a/parsers/common/tests/test_unparse.py +++ b/src/rpft/parsers/common/tests/test_unparse.py @@ -3,8 +3,8 @@ from typing import List, Dict, Optional from collections import OrderedDict -from parsers.common.rowparser import RowParser, ParserModel -from .mock_cell_parser import MockCellParser +from rpft.parsers.common.rowparser import RowParser, ParserModel +from rpft.parsers.common.tests.mock_cell_parser import MockCellParser diff --git a/parsers/creation/tests/__init__.py b/src/rpft/parsers/creation/__init__.py similarity index 100% rename from parsers/creation/tests/__init__.py rename to src/rpft/parsers/creation/__init__.py diff --git a/parsers/creation/campaigneventrowmodel.py b/src/rpft/parsers/creation/campaigneventrowmodel.py similarity index 92% rename from parsers/creation/campaigneventrowmodel.py rename to src/rpft/parsers/creation/campaigneventrowmodel.py index 36e1ec9..10bd4de 100644 --- a/parsers/creation/campaigneventrowmodel.py +++ b/src/rpft/parsers/creation/campaigneventrowmodel.py @@ -1,6 +1,6 @@ -from parsers.common.rowparser import ParserModel +from rpft.parsers.common.rowparser import ParserModel from pydantic import validator -from typing import List + class CampaignEventRowModel(ParserModel): uuid: str = '' diff --git a/parsers/creation/campaignparser.py b/src/rpft/parsers/creation/campaignparser.py similarity index 79% rename from parsers/creation/campaignparser.py rename to src/rpft/parsers/creation/campaignparser.py index 6896814..6b7856c 100644 --- a/parsers/creation/campaignparser.py +++ b/src/rpft/parsers/creation/campaignparser.py @@ -1,9 +1,5 @@ -import importlib -from collections import OrderedDict -from .campaigneventrowmodel import CampaignEventRowModel -from rapidpro.models.campaigns import Campaign, CampaignEvent - -from logger.logger import get_logger, logging_context +from rpft.rapidpro.models.campaigns import Campaign, CampaignEvent +from rpft.logger.logger import get_logger, logging_context LOGGER = get_logger() diff --git a/parsers/creation/constants.py b/src/rpft/parsers/creation/constants.py similarity index 100% rename from parsers/creation/constants.py rename to src/rpft/parsers/creation/constants.py diff --git a/parsers/creation/contentindexparser.py b/src/rpft/parsers/creation/contentindexparser.py similarity index 85% rename from parsers/creation/contentindexparser.py rename to src/rpft/parsers/creation/contentindexparser.py index e970f8c..8171507 100644 --- a/parsers/creation/contentindexparser.py +++ b/src/rpft/parsers/creation/contentindexparser.py @@ -1,15 +1,16 @@ import importlib from collections import OrderedDict -from .contentindexrowmodel import ContentIndexRowModel -from parsers.common.cellparser import CellParser -from parsers.common.sheetparser import SheetParser -from parsers.common.rowparser import RowParser -from parsers.creation.tagmatcher import TagMatcher -from rapidpro.models.containers import RapidProContainer -from parsers.creation.flowparser import FlowParser -from parsers.creation.campaignparser import CampaignParser -from parsers.creation.campaigneventrowmodel import CampaignEventRowModel -from logger.logger import get_logger, logging_context + +from rpft.parsers.creation.contentindexrowmodel import ContentIndexRowModel +from rpft.parsers.common.cellparser import CellParser +from rpft.parsers.common.sheetparser import SheetParser +from rpft.parsers.common.rowparser import RowParser +from rpft.rapidpro.models.containers import RapidProContainer +from rpft.parsers.creation.flowparser import FlowParser +from rpft.parsers.creation.campaignparser import CampaignParser +from rpft.parsers.creation.campaigneventrowmodel import CampaignEventRowModel +from rpft.parsers.creation.tagmatcher import TagMatcher +from rpft.logger.logger import get_logger, logging_context LOGGER = get_logger() @@ -22,7 +23,7 @@ def __init__(self, table, argument_definitions): class ContentIndexParser: - def __init__(self, sheet_reader, user_data_model_module_name=None, tag_matcher=TagMatcher()): + def __init__(self, sheet_reader=None, user_data_model_module_name=None, tag_matcher=TagMatcher()): self.tag_matcher = tag_matcher self.template_sheets = {} # values: tablib tables self.data_sheets = {} # values: OrderedDicts of RowModels @@ -30,8 +31,8 @@ def __init__(self, sheet_reader, user_data_model_module_name=None, tag_matcher= self.campaign_parsers = [] # list of CampaignParser if user_data_model_module_name: self.user_models_module = importlib.import_module(user_data_model_module_name) - main_sheet = sheet_reader.get_main_sheet() - self.process_content_index_table(sheet_reader, main_sheet, "content_index") + if sheet_reader: + self.add_content_index(sheet_reader) def add_content_index(self, sheet_reader): main_sheet = sheet_reader.get_main_sheet() @@ -79,13 +80,13 @@ def process_content_index_table(self, sheet_reader, content_index_table, content def process_data_sheet(self, sheet_reader, sheet_names, new_name, data_model_name): if not hasattr(self, 'user_models_module'): - LOGGER.critical(f'If there are data sheets, a user_data_model_module_name has to be provided (as commandline argument)') + LOGGER.critical('If there are data sheets, a user_data_model_module_name has to be provided (as commandline argument)') return if not data_model_name: - LOGGER.critical(f'No data_model_name provided for data sheet.') + LOGGER.critical('No data_model_name provided for data sheet.') return if len(sheet_names) > 1 and not new_name: - LOGGER.critical(f'If multiple sheets are concatenated, a new_name has to be provided') + LOGGER.critical('If multiple sheets are concatenated, a new_name has to be provided') return if not new_name: new_name = sheet_names[0] @@ -120,7 +121,7 @@ def get_node_group(self, template_name, data_sheet, data_row_id, template_argume with logging_context(f'{template_name}'): return self.parse_flow(template_name, data_sheet, data_row_id, template_arguments, RapidProContainer(), parse_as_block=True) else: - LOGGER.critical(f'For insert_as_block, either both data_sheet and data_row_id or neither have to be provided.') + LOGGER.critical('For insert_as_block, either both data_sheet and data_row_id or neither have to be provided.') def parse_all(self): rapidpro_container = RapidProContainer() @@ -152,7 +153,7 @@ def parse_all_flows(self, rapidpro_container): with logging_context(f'with data_row_id "{data_row_id}"'): self.parse_flow(row.sheet_name[0], row.data_sheet, data_row_id, row.template_arguments, rapidpro_container, row.new_name) elif not row.data_sheet and row.data_row_id: - LOGGER.critical(f'For create_flow, if data_row_id is provided, data_sheet must also be provided.') + LOGGER.critical('For create_flow, if data_row_id is provided, data_sheet must also be provided.') else: self.parse_flow(row.sheet_name[0], row.data_sheet, row.data_row_id, row.template_arguments, rapidpro_container, row.new_name) @@ -163,7 +164,7 @@ def parse_flow(self, sheet_name, data_sheet, data_row_id, template_arguments, ra context = self.get_data_model_instance(data_sheet, data_row_id) else: if data_sheet or data_row_id: - LOGGER.warn(f'For create_flow, if no data_sheet is provided, data_row_id should be blank as well.') + LOGGER.warn('For create_flow, if no data_sheet is provided, data_row_id should be blank as well.') flow_name = base_name context = {} template_sheet = self.get_template_sheet(sheet_name) diff --git a/parsers/creation/contentindexrowmodel.py b/src/rpft/parsers/creation/contentindexrowmodel.py similarity index 94% rename from parsers/creation/contentindexrowmodel.py rename to src/rpft/parsers/creation/contentindexrowmodel.py index 0152f81..e8f132c 100644 --- a/parsers/creation/contentindexrowmodel.py +++ b/src/rpft/parsers/creation/contentindexrowmodel.py @@ -1,4 +1,4 @@ -from parsers.common.rowparser import ParserModel +from rpft.parsers.common.rowparser import ParserModel from typing import List class TemplateArgument(ParserModel): diff --git a/src/rpft/parsers/creation/datarowmodel.py b/src/rpft/parsers/creation/datarowmodel.py new file mode 100644 index 0000000..f581ebd --- /dev/null +++ b/src/rpft/parsers/creation/datarowmodel.py @@ -0,0 +1,4 @@ +from rpft.parsers.common.rowparser import ParserModel + +class DataRowModel(ParserModel): + ID : str = '' diff --git a/parsers/creation/flowparser.py b/src/rpft/parsers/creation/flowparser.py similarity index 96% rename from parsers/creation/flowparser.py rename to src/rpft/parsers/creation/flowparser.py index 07628d2..2763290 100644 --- a/parsers/creation/flowparser.py +++ b/src/rpft/parsers/creation/flowparser.py @@ -1,20 +1,20 @@ -import re -import json from collections import defaultdict -from rapidpro.models.actions import SendMessageAction, SetContactFieldAction, AddContactGroupAction, \ - RemoveContactGroupAction, SetRunResultAction, SetContactPropertyAction, Group, WhatsAppMessageTemplating -from rapidpro.models.containers import FlowContainer -from rapidpro.models.nodes import BaseNode, BasicNode, SwitchRouterNode, RandomRouterNode, EnterFlowNode -from rapidpro.models.routers import SwitchRouter, RandomRouter -from rapidpro.models.exceptions import RapidProActionError -from parsers.common.cellparser import CellParser -from parsers.common.sheetparser import SheetParser -from parsers.common.rowparser import RowParser -from parsers.creation.flowrowmodel import FlowRowModel -from .flowrowmodel import Condition - -from logger.logger import get_logger, logging_context +from rpft.rapidpro.models.actions import ( + SendMessageAction, SetContactFieldAction, AddContactGroupAction, + RemoveContactGroupAction, SetRunResultAction, SetContactPropertyAction, + Group, WhatsAppMessageTemplating) +from rpft.rapidpro.models.containers import FlowContainer +from rpft.rapidpro.models.exceptions import RapidProActionError +from rpft.rapidpro.models.nodes import BasicNode, SwitchRouterNode, RandomRouterNode, EnterFlowNode +from rpft.rapidpro.models.routers import SwitchRouter +from rpft.parsers.common.cellparser import CellParser +from rpft.parsers.common.sheetparser import SheetParser +from rpft.parsers.common.rowparser import RowParser +from rpft.parsers.creation.flowrowmodel import FlowRowModel +from rpft.parsers.creation.flowrowmodel import Condition + +from rpft.logger.logger import get_logger, logging_context LOGGER = get_logger() @@ -301,7 +301,7 @@ def append_node_group(self, new_node_group, row_id): def parse_as_block(self): self._parse_block() if not len(self.node_group_stack) == 1: - LOGGER.critical(f'Unexpected end of flow. Did you forget end_for/end_block?') + LOGGER.critical('Unexpected end of flow. Did you forget end_for/end_block?') return self.current_node_group() def parse(self): @@ -594,6 +594,6 @@ def _compile_flow(self): # Caveat/TODO: Need to ensure starting node comes first. flow_container = FlowContainer(flow_name=self.flow_name, uuid=self.flow_uuid) if not len(self.node_group_stack) == 1: - LOGGER.critical(f'Unexpected end of flow. Did you forget end_for/end_block?') + LOGGER.critical('Unexpected end of flow. Did you forget end_for/end_block?') self.current_node_group().add_nodes_to_flow(flow_container) return flow_container diff --git a/parsers/creation/flowrowmodel.py b/src/rpft/parsers/creation/flowrowmodel.py similarity index 98% rename from parsers/creation/flowrowmodel.py rename to src/rpft/parsers/creation/flowrowmodel.py index a476406..03cdb91 100644 --- a/parsers/creation/flowrowmodel.py +++ b/src/rpft/parsers/creation/flowrowmodel.py @@ -1,5 +1,5 @@ from typing import List -from parsers.common.rowparser import ParserModel +from rpft.parsers.common.rowparser import ParserModel from pydantic import Field diff --git a/parsers/creation/tagmatcher.py b/src/rpft/parsers/creation/tagmatcher.py similarity index 100% rename from parsers/creation/tagmatcher.py rename to src/rpft/parsers/creation/tagmatcher.py diff --git a/parsers/creation/template_sheet_parser.py b/src/rpft/parsers/creation/template_sheet_parser.py similarity index 82% rename from parsers/creation/template_sheet_parser.py rename to src/rpft/parsers/creation/template_sheet_parser.py index d7af812..92a4214 100644 --- a/parsers/creation/template_sheet_parser.py +++ b/src/rpft/parsers/creation/template_sheet_parser.py @@ -1,6 +1,6 @@ -from parsers.creation.flowparser import FlowParser -from rapidpro.models.containers import RapidProContainer -from parsers.common.sheetparser import SheetParser +from rpft.parsers.creation.flowparser import FlowParser +from rpft.rapidpro.models.containers import RapidProContainer +from rpft.parsers.common.sheetparser import SheetParser class TemplateSheetParser: diff --git a/parsers/sheets/__init__.py b/src/rpft/parsers/creation/tests/__init__.py similarity index 100% rename from parsers/sheets/__init__.py rename to src/rpft/parsers/creation/tests/__init__.py diff --git a/parsers/creation/tests/datarowmodels/evalmodels.py b/src/rpft/parsers/creation/tests/datarowmodels/evalmodels.py similarity index 66% rename from parsers/creation/tests/datarowmodels/evalmodels.py rename to src/rpft/parsers/creation/tests/datarowmodels/evalmodels.py index 715e13c..1ff29ee 100644 --- a/parsers/creation/tests/datarowmodels/evalmodels.py +++ b/src/rpft/parsers/creation/tests/datarowmodels/evalmodels.py @@ -1,4 +1,4 @@ -from parsers.creation.datarowmodel import DataRowModel +from rpft.parsers.creation.datarowmodel import DataRowModel class EvalMetadataModel(DataRowModel): include_if: str = '' diff --git a/parsers/creation/tests/datarowmodels/listmodel.py b/src/rpft/parsers/creation/tests/datarowmodels/listmodel.py similarity index 86% rename from parsers/creation/tests/datarowmodels/listmodel.py rename to src/rpft/parsers/creation/tests/datarowmodels/listmodel.py index 91fcafa..7b11229 100644 --- a/parsers/creation/tests/datarowmodels/listmodel.py +++ b/src/rpft/parsers/creation/tests/datarowmodels/listmodel.py @@ -1,4 +1,4 @@ -from parsers.creation.datarowmodel import DataRowModel +from rpft.parsers.creation.datarowmodel import DataRowModel from typing import List class ListRowModel(DataRowModel): diff --git a/tests/input/example1/nestedmodel.py b/src/rpft/parsers/creation/tests/datarowmodels/nestedmodel.py similarity index 79% rename from tests/input/example1/nestedmodel.py rename to src/rpft/parsers/creation/tests/datarowmodels/nestedmodel.py index 4fb8a74..db0e6c0 100644 --- a/tests/input/example1/nestedmodel.py +++ b/src/rpft/parsers/creation/tests/datarowmodels/nestedmodel.py @@ -1,5 +1,5 @@ -from parsers.creation.datarowmodel import DataRowModel -from parsers.common.rowparser import ParserModel +from rpft.parsers.creation.datarowmodel import DataRowModel +from rpft.parsers.common.rowparser import ParserModel class CustomModel(ParserModel): # Because this does not directly define the content of a datasheet, diff --git a/parsers/creation/tests/datarowmodels/simplemodel.py b/src/rpft/parsers/creation/tests/datarowmodels/simplemodel.py similarity index 54% rename from parsers/creation/tests/datarowmodels/simplemodel.py rename to src/rpft/parsers/creation/tests/datarowmodels/simplemodel.py index 76ae5cd..744233c 100644 --- a/parsers/creation/tests/datarowmodels/simplemodel.py +++ b/src/rpft/parsers/creation/tests/datarowmodels/simplemodel.py @@ -1,4 +1,4 @@ -from parsers.creation.datarowmodel import DataRowModel +from rpft.parsers.creation.datarowmodel import DataRowModel class SimpleRowModel(DataRowModel): value1: str = '' diff --git a/parsers/creation/tests/mock_sheetreader.py b/src/rpft/parsers/creation/tests/mock_sheetreader.py similarity index 100% rename from parsers/creation/tests/mock_sheetreader.py rename to src/rpft/parsers/creation/tests/mock_sheetreader.py diff --git a/parsers/creation/tests/row_data.py b/src/rpft/parsers/creation/tests/row_data.py similarity index 95% rename from parsers/creation/tests/row_data.py rename to src/rpft/parsers/creation/tests/row_data.py index c8b176b..b9478ab 100644 --- a/parsers/creation/tests/row_data.py +++ b/src/rpft/parsers/creation/tests/row_data.py @@ -1,4 +1,4 @@ -from parsers.creation.flowrowmodel import FlowRowModel +from rpft.parsers.creation.flowrowmodel import FlowRowModel def get_start_row(): return FlowRowModel(**{ diff --git a/parsers/creation/tests/test_contentindexparser.py b/src/rpft/parsers/creation/tests/test_contentindexparser.py similarity index 92% rename from parsers/creation/tests/test_contentindexparser.py rename to src/rpft/parsers/creation/tests/test_contentindexparser.py index 254d4de..962323d 100644 --- a/parsers/creation/tests/test_contentindexparser.py +++ b/src/rpft/parsers/creation/tests/test_contentindexparser.py @@ -1,10 +1,10 @@ import unittest -import json -from .mock_sheetreader import MockSheetReader -from parsers.creation.contentindexparser import ContentIndexParser -from parsers.creation.tagmatcher import TagMatcher -from tests.utils import traverse_flow, Context +from rpft.parsers.creation.tests.mock_sheetreader import MockSheetReader +from rpft.parsers.creation.contentindexparser import ContentIndexParser +from rpft.parsers.creation.tagmatcher import TagMatcher +from rpft.tests.utils import traverse_flow, Context + class TestParsing(unittest.TestCase): @@ -84,7 +84,7 @@ def test_basic_user_model(self): ) sheet_reader = MockSheetReader(ci_sheet, {'simpledata' : simpledata}) - ci_parser = ContentIndexParser(sheet_reader, 'parsers.creation.tests.datarowmodels.simplemodel') + ci_parser = ContentIndexParser(sheet_reader, 'rpft.parsers.creation.tests.datarowmodels.simplemodel') datamodelA = ci_parser.get_data_model_instance('simpledata', 'rowA') datamodelB = ci_parser.get_data_model_instance('simpledata', 'rowB') self.assertEqual(datamodelA.value1, '1A') @@ -111,7 +111,7 @@ def test_concat(self): } sheet_reader = MockSheetReader(ci_sheet, sheet_dict) - ci_parser = ContentIndexParser(sheet_reader, 'parsers.creation.tests.datarowmodels.simplemodel') + ci_parser = ContentIndexParser(sheet_reader, 'rpft.parsers.creation.tests.datarowmodels.simplemodel') datamodelA = ci_parser.get_data_model_instance('simpledata', 'rowA') datamodelB = ci_parser.get_data_model_instance('simpledata', 'rowB') self.assertEqual(datamodelA.value1, '1A') @@ -156,7 +156,7 @@ def test_generate_flows(self): } sheet_reader = MockSheetReader(ci_sheet, sheet_dict) - ci_parser = ContentIndexParser(sheet_reader, 'parsers.creation.tests.datarowmodels.nestedmodel') + ci_parser = ContentIndexParser(sheet_reader, 'rpft.parsers.creation.tests.datarowmodels.nestedmodel') container = ci_parser.parse_all() render_output = container.render() self.compare_messages(render_output, 'my_basic_flow', ['Some text']) @@ -164,7 +164,7 @@ def test_generate_flows(self): self.compare_messages(render_output, 'my_template - row2', ['Value2', 'Happy2 and Sad2']) sheet_reader = MockSheetReader(ci_sheet_alt, sheet_dict) - ci_parser = ContentIndexParser(sheet_reader, 'parsers.creation.tests.datarowmodels.nestedmodel') + ci_parser = ContentIndexParser(sheet_reader, 'rpft.parsers.creation.tests.datarowmodels.nestedmodel') container = ci_parser.parse_all() render_output = container.render() self.compare_messages(render_output, 'my_basic_flow', ['Some text']) @@ -195,7 +195,7 @@ def test_bulk_flows_with_args(self): } sheet_reader = MockSheetReader(ci_sheet, sheet_dict) - ci_parser = ContentIndexParser(sheet_reader, 'parsers.creation.tests.datarowmodels.nestedmodel') + ci_parser = ContentIndexParser(sheet_reader, 'rpft.parsers.creation.tests.datarowmodels.nestedmodel') container = ci_parser.parse_all() render_output = container.render() self.compare_messages(render_output, 'my_renamed_template - row1', ['Value1 ARG1 ARG2', 'Happy1 and Sad1']) @@ -238,7 +238,7 @@ def test_insert_as_block(self): } sheet_reader = MockSheetReader(ci_sheet, sheet_dict) - ci_parser = ContentIndexParser(sheet_reader, 'parsers.creation.tests.datarowmodels.nestedmodel') + ci_parser = ContentIndexParser(sheet_reader, 'rpft.parsers.creation.tests.datarowmodels.nestedmodel') container = ci_parser.parse_all() render_output = container.render() messages_exp = [ @@ -297,7 +297,7 @@ def test_insert_as_block_with_sheet_arguments(self): } sheet_reader = MockSheetReader(ci_sheet, sheet_dict) - ci_parser = ContentIndexParser(sheet_reader, 'parsers.creation.tests.datarowmodels.listmodel') + ci_parser = ContentIndexParser(sheet_reader, 'rpft.parsers.creation.tests.datarowmodels.listmodel') container = ci_parser.parse_all() render_output = container.render() messages_exp = [ @@ -344,7 +344,7 @@ def test_insert_as_block_with_arguments(self): } sheet_reader = MockSheetReader(ci_sheet, sheet_dict) - ci_parser = ContentIndexParser(sheet_reader, 'parsers.creation.tests.datarowmodels.listmodel') + ci_parser = ContentIndexParser(sheet_reader, 'rpft.parsers.creation.tests.datarowmodels.listmodel') container = ci_parser.parse_all() render_output = container.render() messages_exp = [ @@ -386,7 +386,7 @@ def test_eval(self): } sheet_reader = MockSheetReader(ci_sheet, sheet_dict) - ci_parser = ContentIndexParser(sheet_reader, 'parsers.creation.tests.datarowmodels.evalmodels') + ci_parser = ContentIndexParser(sheet_reader, 'rpft.parsers.creation.tests.datarowmodels.evalmodels') container = ci_parser.parse_all() render_output = container.render() messages_exp = [ @@ -421,7 +421,7 @@ def test_tags(self): } sheet_reader = MockSheetReader(ci_sheet, sheet_dict) - ci_parser = ContentIndexParser(sheet_reader, 'parsers.creation.tests.datarowmodels.evalmodels') + ci_parser = ContentIndexParser(sheet_reader, 'rpft.parsers.creation.tests.datarowmodels.evalmodels') container = ci_parser.parse_all() render_output = container.render() self.assertEqual(self.get_flow_names(render_output), {"flow-world", "flow-t1", "flow-b1", "flow-t2", "flow-b2", "flow-t1t2", "flow-t1b2", "flow-b1t2"}) @@ -435,25 +435,25 @@ def test_tags(self): self.compare_messages(render_output, 'flow-b1t2', ['Hello Bag1Tag2']) tag_matcher = TagMatcher(["1", "tag1"]) - ci_parser = ContentIndexParser(sheet_reader, 'parsers.creation.tests.datarowmodels.evalmodels', tag_matcher) + ci_parser = ContentIndexParser(sheet_reader, 'rpft.parsers.creation.tests.datarowmodels.evalmodels', tag_matcher) container = ci_parser.parse_all() render_output = container.render() self.assertEqual(self.get_flow_names(render_output), {"flow-world", "flow-t1", "flow-t2", "flow-b2", "flow-t1t2", "flow-t1b2"}) tag_matcher = TagMatcher(["1", "tag1", "bag1"]) - ci_parser = ContentIndexParser(sheet_reader, 'parsers.creation.tests.datarowmodels.evalmodels', tag_matcher) + ci_parser = ContentIndexParser(sheet_reader, 'rpft.parsers.creation.tests.datarowmodels.evalmodels', tag_matcher) container = ci_parser.parse_all() render_output = container.render() self.assertEqual(self.get_flow_names(render_output), {"flow-world", "flow-t1", "flow-b1", "flow-t2", "flow-b2", "flow-t1t2", "flow-t1b2", "flow-b1t2"}) tag_matcher = TagMatcher(["1", "tag1", "2", "tag2"]) - ci_parser = ContentIndexParser(sheet_reader, 'parsers.creation.tests.datarowmodels.evalmodels', tag_matcher) + ci_parser = ContentIndexParser(sheet_reader, 'rpft.parsers.creation.tests.datarowmodels.evalmodels', tag_matcher) container = ci_parser.parse_all() render_output = container.render() self.assertEqual(self.get_flow_names(render_output), {"flow-world", "flow-t1","flow-t2","flow-t1t2"}) tag_matcher = TagMatcher(["5", "tag1", "bag1"]) - ci_parser = ContentIndexParser(sheet_reader, 'parsers.creation.tests.datarowmodels.evalmodels', tag_matcher) + ci_parser = ContentIndexParser(sheet_reader, 'rpft.parsers.creation.tests.datarowmodels.evalmodels', tag_matcher) container = ci_parser.parse_all() render_output = container.render() self.assertEqual(self.get_flow_names(render_output), {"flow-world", "flow-t1", "flow-b1", "flow-t2", "flow-b2", "flow-t1t2", "flow-t1b2", "flow-b1t2"}) diff --git a/parsers/creation/tests/test_flowparser.py b/src/rpft/parsers/creation/tests/test_flowparser.py similarity index 98% rename from parsers/creation/tests/test_flowparser.py rename to src/rpft/parsers/creation/tests/test_flowparser.py index 07297df..6489415 100644 --- a/parsers/creation/tests/test_flowparser.py +++ b/src/rpft/parsers/creation/tests/test_flowparser.py @@ -1,17 +1,16 @@ -import json import unittest import tablib -from parsers.creation.flowparser import FlowParser -from tests.utils import get_dict_from_csv, get_table_from_file, find_destination_uuid, Context, find_node_by_uuid, traverse_flow -from rapidpro.models.containers import RapidProContainer, FlowContainer -from rapidpro.models.actions import Group, AddContactGroupAction -from rapidpro.models.nodes import BasicNode +from rpft.parsers.creation.flowparser import FlowParser +from rpft.tests.utils import get_dict_from_csv, find_destination_uuid, Context, find_node_by_uuid, traverse_flow +from rpft.rapidpro.models.containers import RapidProContainer, FlowContainer +from rpft.rapidpro.models.actions import Group, AddContactGroupAction +from rpft.rapidpro.models.nodes import BasicNode -from parsers.common.rowparser import RowParser -from parsers.creation.flowrowmodel import FlowRowModel -from parsers.common.cellparser import CellParser -from parsers.common.tests.mock_sheetparser import MockSheetParser +from rpft.parsers.common.rowparser import RowParser +from rpft.parsers.creation.flowrowmodel import FlowRowModel +from rpft.parsers.common.cellparser import CellParser +from rpft.parsers.common.tests.mock_sheetparser import MockSheetParser from .row_data import get_start_row, get_message_with_templating, get_unconditional_node_from_1, get_conditional_node_from_1 diff --git a/parsers/creation/tests/test_tagmatcher.py b/src/rpft/parsers/creation/tests/test_tagmatcher.py similarity index 96% rename from parsers/creation/tests/test_tagmatcher.py rename to src/rpft/parsers/creation/tests/test_tagmatcher.py index 42f3512..caf7e7b 100644 --- a/parsers/creation/tests/test_tagmatcher.py +++ b/src/rpft/parsers/creation/tests/test_tagmatcher.py @@ -1,5 +1,6 @@ import unittest -from parsers.creation.tagmatcher import TagMatcher +from rpft.parsers.creation.tagmatcher import TagMatcher + class TestTagMatcher(unittest.TestCase): diff --git a/parsers/creation/tests/test_to_row_model.py b/src/rpft/parsers/creation/tests/test_to_row_model.py similarity index 95% rename from parsers/creation/tests/test_to_row_model.py rename to src/rpft/parsers/creation/tests/test_to_row_model.py index 8c6b307..2836ca3 100644 --- a/parsers/creation/tests/test_to_row_model.py +++ b/src/rpft/parsers/creation/tests/test_to_row_model.py @@ -1,15 +1,12 @@ import unittest -import json -from parsers.common.rowdatasheet import RowDataSheet -from parsers.common.rowparser import RowParser -from tests.utils import get_dict_from_csv, find_destination_uuid, Context, find_node_by_uuid -from rapidpro.models.containers import RapidProContainer, FlowContainer -from rapidpro.models.actions import Group, SendMessageAction, AddContactGroupAction, SetRunResultAction, SetContactFieldAction -from rapidpro.models.nodes import BasicNode, SwitchRouterNode, RandomRouterNode, EnterFlowNode -from parsers.creation.flowrowmodel import FlowRowModel, Edge, Condition - -from .row_data import get_start_row, get_unconditional_node_from_1 +from rpft.parsers.common.rowdatasheet import RowDataSheet +from rpft.parsers.common.rowparser import RowParser +from rpft.rapidpro.models.containers import FlowContainer +from rpft.rapidpro.models.actions import Group, SendMessageAction, AddContactGroupAction, SetRunResultAction, SetContactFieldAction +from rpft.rapidpro.models.nodes import BasicNode, SwitchRouterNode, RandomRouterNode, EnterFlowNode +from rpft.parsers.creation.flowrowmodel import FlowRowModel, Edge +from rpft.parsers.creation.tests.row_data import get_start_row, get_unconditional_node_from_1 class TestToRowModels(unittest.TestCase): diff --git a/parsers/creation/tests/utils.py b/src/rpft/parsers/creation/tests/utils.py similarity index 100% rename from parsers/creation/tests/utils.py rename to src/rpft/parsers/creation/tests/utils.py diff --git a/rapidpro/__init__.py b/src/rpft/parsers/sheets/__init__.py similarity index 100% rename from rapidpro/__init__.py rename to src/rpft/parsers/sheets/__init__.py diff --git a/parsers/sheets/csv_sheet_reader.py b/src/rpft/parsers/sheets/csv_sheet_reader.py similarity index 100% rename from parsers/sheets/csv_sheet_reader.py rename to src/rpft/parsers/sheets/csv_sheet_reader.py diff --git a/parsers/sheets/google_sheet_reader.py b/src/rpft/parsers/sheets/google_sheet_reader.py similarity index 60% rename from parsers/sheets/google_sheet_reader.py rename to src/rpft/parsers/sheets/google_sheet_reader.py index ea4eb65..8540781 100644 --- a/parsers/sheets/google_sheet_reader.py +++ b/src/rpft/parsers/sheets/google_sheet_reader.py @@ -1,45 +1,27 @@ +import json +import os import tablib + from googleapiclient.discovery import build from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials -import os +from google.oauth2.service_account import Credentials as ServiceAccountCredentials + class GoogleSheetReader: # If modifying these scopes, delete the file token.json. SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly'] - def __init__(self, spreadsheet_id): + def __init__(self, spreadsheet_id, credentials=None): ''' Args: spreadsheet_id: You can extract it from the spreadsheed URL, like this https://docs.google.com/spreadsheets/d/[spreadsheet_id]/edit ''' - # Authentication code nabbed from - # https://developers.google.com/sheets/api/quickstart/python - creds = None - # The file token.json stores the user's access and refresh tokens, and is - # created automatically when the authorization flow completes for the first - # time. - if os.path.exists('token.json'): - creds = Credentials.from_authorized_user_file('token.json', GoogleSheetReader.SCOPES) - # If there are no (valid) credentials available, let the user log in. - if not creds or not creds.valid: - if creds and creds.expired and creds.refresh_token: - creds.refresh(Request()) - else: - # TODO: Provide instructions how to obtain this file and get access - flow = InstalledAppFlow.from_client_secrets_file( - 'credentials.json', GoogleSheetReader.SCOPES) - creds = flow.run_local_server(port=0) - # Save the credentials for the next run - with open('token.json', 'w') as token: - token.write(creds.to_json()) - service = build('sheets', 'v4', credentials=creds) - - # Call the Sheets API + service = build('sheets', 'v4', credentials=get_credentials()) sheet_metadata = service.spreadsheets().get(spreadsheetId=spreadsheet_id).execute() sheets = sheet_metadata.get('sheets', '') titles = [] @@ -65,7 +47,7 @@ def __init__(self, spreadsheet_id): self.sheets[name] = self._table_from_content(content) if self.main_sheet is None: - raise ValueError(f'{filename} must have a sheet "content_index"') + raise ValueError(f'{spreadsheet_id} must have a sheet "content_index"') def _table_from_content(self, content): table = tablib.Dataset() @@ -81,3 +63,38 @@ def get_main_sheet(self): def get_sheet(self, name): return self.sheets[name] + + +def get_credentials(): + sa_creds = os.getenv("CREDENTIALS") + if sa_creds: + return ServiceAccountCredentials.from_service_account_info( + json.loads(sa_creds), + scopes=GoogleSheetReader.SCOPES + ) + + creds = None + token_file_name = "token.json" + + if os.path.exists(token_file_name): + creds = Credentials.from_authorized_user_file( + token_file_name, + scopes=GoogleSheetReader.SCOPES + ) + + # If there are no (valid) credentials available, let the user log in. + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file( + 'credentials.json', + GoogleSheetReader.SCOPES + ) + creds = flow.run_local_server(port=0) + + # Save the credentials for the next run + with open(token_file_name, 'w') as token: + token.write(creds.to_json()) + + return creds diff --git a/parsers/sheets/xlsx_sheet_reader.py b/src/rpft/parsers/sheets/xlsx_sheet_reader.py similarity index 98% rename from parsers/sheets/xlsx_sheet_reader.py rename to src/rpft/parsers/sheets/xlsx_sheet_reader.py index a1a1e8d..30d270b 100644 --- a/parsers/sheets/xlsx_sheet_reader.py +++ b/src/rpft/parsers/sheets/xlsx_sheet_reader.py @@ -1,5 +1,5 @@ import tablib -import os + class XLSXSheetReader: diff --git a/rapidpro/tests/__init__.py b/src/rpft/rapidpro/__init__.py similarity index 100% rename from rapidpro/tests/__init__.py rename to src/rpft/rapidpro/__init__.py diff --git a/tests/__init__.py b/src/rpft/rapidpro/models/__init__.py similarity index 100% rename from tests/__init__.py rename to src/rpft/rapidpro/models/__init__.py diff --git a/rapidpro/models/actions.py b/src/rpft/rapidpro/models/actions.py similarity index 98% rename from rapidpro/models/actions.py rename to src/rpft/rapidpro/models/actions.py index d9ae393..f276d96 100644 --- a/rapidpro/models/actions.py +++ b/src/rpft/rapidpro/models/actions.py @@ -1,6 +1,6 @@ -from rapidpro.utils import generate_new_uuid -from .exceptions import RapidProActionError -from rapidpro.models.common import Group, FlowReference, ContactFieldReference +from rpft.rapidpro.utils import generate_new_uuid +from rpft.rapidpro.models.exceptions import RapidProActionError +from rpft.rapidpro.models.common import Group, FlowReference, ContactFieldReference import copy @@ -376,4 +376,4 @@ def get_row_model_fields(self): "set_run_result" : SetRunResultAction, "start_session" : UnclassifiedAction, "transfer_airtime" : UnclassifiedAction, -} \ No newline at end of file +} diff --git a/rapidpro/models/campaigns.py b/src/rpft/rapidpro/models/campaigns.py similarity index 96% rename from rapidpro/models/campaigns.py rename to src/rpft/rapidpro/models/campaigns.py index 7813cfb..7cdf9e2 100644 --- a/rapidpro/models/campaigns.py +++ b/src/rpft/rapidpro/models/campaigns.py @@ -1,7 +1,8 @@ -from rapidpro.utils import generate_new_uuid -from rapidpro.models.common import Group, FlowReference, ContactFieldReference, generate_field_key import copy +from rpft.rapidpro.utils import generate_new_uuid +from rpft.rapidpro.models.common import Group, FlowReference, ContactFieldReference + class CampaignEvent: def __init__(self, diff --git a/rapidpro/models/common.py b/src/rpft/rapidpro/models/common.py similarity index 95% rename from rapidpro/models/common.py rename to src/rpft/rapidpro/models/common.py index 4168bd2..fcf370a 100644 --- a/rapidpro/models/common.py +++ b/src/rpft/rapidpro/models/common.py @@ -1,7 +1,8 @@ -from rapidpro.utils import generate_new_uuid -from .exceptions import RapidProActionError import re +from rpft.rapidpro.utils import generate_new_uuid +from rpft.rapidpro.models.exceptions import RapidProActionError + class Exit: def __init__(self, destination_uuid=None, uuid=None): @@ -108,4 +109,4 @@ def render(self): } if self.query: render_dict["query"] = query - return render_dict \ No newline at end of file + return render_dict diff --git a/rapidpro/models/containers.py b/src/rpft/rapidpro/models/containers.py similarity index 97% rename from rapidpro/models/containers.py rename to src/rpft/rapidpro/models/containers.py index 47d11f3..31b8dfb 100644 --- a/rapidpro/models/containers.py +++ b/src/rpft/rapidpro/models/containers.py @@ -1,10 +1,11 @@ -from rapidpro.utils import generate_new_uuid -from rapidpro.models.nodes import BaseNode -from rapidpro.models.actions import Group -from rapidpro.models.campaigns import Campaign -from parsers.creation.flowrowmodel import FlowRowModel, Edge import copy +from rpft.rapidpro.utils import generate_new_uuid +from rpft.rapidpro.models.nodes import BaseNode +from rpft.rapidpro.models.actions import Group +from rpft.rapidpro.models.campaigns import Campaign +from rpft.parsers.creation.flowrowmodel import FlowRowModel, Edge + class RapidProContainer: def __init__(self, campaigns=None, fields=None, flows=None, groups=None, site=None, triggers=None, version='13'): diff --git a/rapidpro/models/exceptions.py b/src/rpft/rapidpro/models/exceptions.py similarity index 100% rename from rapidpro/models/exceptions.py rename to src/rpft/rapidpro/models/exceptions.py diff --git a/rapidpro/models/nodes.py b/src/rpft/rapidpro/models/nodes.py similarity index 98% rename from rapidpro/models/nodes.py rename to src/rpft/rapidpro/models/nodes.py index 07f7033..f5464e0 100644 --- a/rapidpro/models/nodes.py +++ b/src/rpft/rapidpro/models/nodes.py @@ -1,11 +1,11 @@ import re from abc import ABC, abstractmethod -from parsers.creation.flowrowmodel import FlowRowModel, Edge -from rapidpro.models.actions import Action, EnterFlowAction -from rapidpro.models.common import Exit -from rapidpro.models.routers import SwitchRouter, RandomRouter -from rapidpro.utils import generate_new_uuid +from rpft.parsers.creation.flowrowmodel import FlowRowModel, Edge +from rpft.rapidpro.models.actions import Action, EnterFlowAction +from rpft.rapidpro.models.common import Exit +from rpft.rapidpro.models.routers import SwitchRouter, RandomRouter +from rpft.rapidpro.utils import generate_new_uuid # TODO: EnterFlowNode and WebhookNode are currently children of BaseNode. # Ideal class tree of nodes: diff --git a/rapidpro/models/routers.py b/src/rpft/rapidpro/models/routers.py similarity index 99% rename from rapidpro/models/routers.py rename to src/rpft/rapidpro/models/routers.py index 3e2663a..b7a4735 100644 --- a/rapidpro/models/routers.py +++ b/src/rpft/rapidpro/models/routers.py @@ -1,9 +1,9 @@ import logging import string -from rapidpro.models.common import Exit -from rapidpro.utils import generate_new_uuid -from parsers.creation.flowrowmodel import Condition, Edge +from rpft.rapidpro.models.common import Exit +from rpft.rapidpro.utils import generate_new_uuid +from rpft.parsers.creation.flowrowmodel import Condition, Edge logger = logging.getLogger(__name__) diff --git a/src/rpft/rapidpro/tests/__init__.py b/src/rpft/rapidpro/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rapidpro/tests/data/actions/add_contact_urn.json b/src/rpft/rapidpro/tests/data/actions/add_contact_urn.json similarity index 100% rename from rapidpro/tests/data/actions/add_contact_urn.json rename to src/rpft/rapidpro/tests/data/actions/add_contact_urn.json diff --git a/rapidpro/tests/data/actions/add_input_labels.json b/src/rpft/rapidpro/tests/data/actions/add_input_labels.json similarity index 100% rename from rapidpro/tests/data/actions/add_input_labels.json rename to src/rpft/rapidpro/tests/data/actions/add_input_labels.json diff --git a/rapidpro/tests/data/actions/call_classifier.json b/src/rpft/rapidpro/tests/data/actions/call_classifier.json similarity index 100% rename from rapidpro/tests/data/actions/call_classifier.json rename to src/rpft/rapidpro/tests/data/actions/call_classifier.json diff --git a/rapidpro/tests/data/actions/call_resthook.json b/src/rpft/rapidpro/tests/data/actions/call_resthook.json similarity index 100% rename from rapidpro/tests/data/actions/call_resthook.json rename to src/rpft/rapidpro/tests/data/actions/call_resthook.json diff --git a/rapidpro/tests/data/actions/call_webhook.json b/src/rpft/rapidpro/tests/data/actions/call_webhook.json similarity index 100% rename from rapidpro/tests/data/actions/call_webhook.json rename to src/rpft/rapidpro/tests/data/actions/call_webhook.json diff --git a/rapidpro/tests/data/actions/enter_flow.json b/src/rpft/rapidpro/tests/data/actions/enter_flow.json similarity index 100% rename from rapidpro/tests/data/actions/enter_flow.json rename to src/rpft/rapidpro/tests/data/actions/enter_flow.json diff --git a/rapidpro/tests/data/actions/open_ticket.json b/src/rpft/rapidpro/tests/data/actions/open_ticket.json similarity index 100% rename from rapidpro/tests/data/actions/open_ticket.json rename to src/rpft/rapidpro/tests/data/actions/open_ticket.json diff --git a/rapidpro/tests/data/actions/play_audio.json b/src/rpft/rapidpro/tests/data/actions/play_audio.json similarity index 100% rename from rapidpro/tests/data/actions/play_audio.json rename to src/rpft/rapidpro/tests/data/actions/play_audio.json diff --git a/rapidpro/tests/data/actions/remove_contact_groups.json b/src/rpft/rapidpro/tests/data/actions/remove_contact_groups.json similarity index 100% rename from rapidpro/tests/data/actions/remove_contact_groups.json rename to src/rpft/rapidpro/tests/data/actions/remove_contact_groups.json diff --git a/rapidpro/tests/data/actions/say_msg.json b/src/rpft/rapidpro/tests/data/actions/say_msg.json similarity index 100% rename from rapidpro/tests/data/actions/say_msg.json rename to src/rpft/rapidpro/tests/data/actions/say_msg.json diff --git a/rapidpro/tests/data/actions/send_broadcast.json b/src/rpft/rapidpro/tests/data/actions/send_broadcast.json similarity index 100% rename from rapidpro/tests/data/actions/send_broadcast.json rename to src/rpft/rapidpro/tests/data/actions/send_broadcast.json diff --git a/rapidpro/tests/data/actions/send_email.json b/src/rpft/rapidpro/tests/data/actions/send_email.json similarity index 100% rename from rapidpro/tests/data/actions/send_email.json rename to src/rpft/rapidpro/tests/data/actions/send_email.json diff --git a/rapidpro/tests/data/actions/send_msg.json b/src/rpft/rapidpro/tests/data/actions/send_msg.json similarity index 100% rename from rapidpro/tests/data/actions/send_msg.json rename to src/rpft/rapidpro/tests/data/actions/send_msg.json diff --git a/rapidpro/tests/data/actions/send_msg_with_attachments.json b/src/rpft/rapidpro/tests/data/actions/send_msg_with_attachments.json similarity index 100% rename from rapidpro/tests/data/actions/send_msg_with_attachments.json rename to src/rpft/rapidpro/tests/data/actions/send_msg_with_attachments.json diff --git a/rapidpro/tests/data/actions/send_msg_with_quick_replies.json b/src/rpft/rapidpro/tests/data/actions/send_msg_with_quick_replies.json similarity index 100% rename from rapidpro/tests/data/actions/send_msg_with_quick_replies.json rename to src/rpft/rapidpro/tests/data/actions/send_msg_with_quick_replies.json diff --git a/rapidpro/tests/data/actions/set_contact_channel.json b/src/rpft/rapidpro/tests/data/actions/set_contact_channel.json similarity index 100% rename from rapidpro/tests/data/actions/set_contact_channel.json rename to src/rpft/rapidpro/tests/data/actions/set_contact_channel.json diff --git a/rapidpro/tests/data/actions/set_contact_field.json b/src/rpft/rapidpro/tests/data/actions/set_contact_field.json similarity index 100% rename from rapidpro/tests/data/actions/set_contact_field.json rename to src/rpft/rapidpro/tests/data/actions/set_contact_field.json diff --git a/rapidpro/tests/data/actions/set_contact_language.json b/src/rpft/rapidpro/tests/data/actions/set_contact_language.json similarity index 100% rename from rapidpro/tests/data/actions/set_contact_language.json rename to src/rpft/rapidpro/tests/data/actions/set_contact_language.json diff --git a/rapidpro/tests/data/actions/set_contact_name.json b/src/rpft/rapidpro/tests/data/actions/set_contact_name.json similarity index 100% rename from rapidpro/tests/data/actions/set_contact_name.json rename to src/rpft/rapidpro/tests/data/actions/set_contact_name.json diff --git a/rapidpro/tests/data/actions/set_contact_status.json b/src/rpft/rapidpro/tests/data/actions/set_contact_status.json similarity index 100% rename from rapidpro/tests/data/actions/set_contact_status.json rename to src/rpft/rapidpro/tests/data/actions/set_contact_status.json diff --git a/rapidpro/tests/data/actions/set_contact_timezone.json b/src/rpft/rapidpro/tests/data/actions/set_contact_timezone.json similarity index 100% rename from rapidpro/tests/data/actions/set_contact_timezone.json rename to src/rpft/rapidpro/tests/data/actions/set_contact_timezone.json diff --git a/rapidpro/tests/data/actions/set_run_result.json b/src/rpft/rapidpro/tests/data/actions/set_run_result.json similarity index 100% rename from rapidpro/tests/data/actions/set_run_result.json rename to src/rpft/rapidpro/tests/data/actions/set_run_result.json diff --git a/rapidpro/tests/data/actions/start_session.json b/src/rpft/rapidpro/tests/data/actions/start_session.json similarity index 100% rename from rapidpro/tests/data/actions/start_session.json rename to src/rpft/rapidpro/tests/data/actions/start_session.json diff --git a/rapidpro/tests/data/actions/transfer_airtime.json b/src/rpft/rapidpro/tests/data/actions/transfer_airtime.json similarity index 100% rename from rapidpro/tests/data/actions/transfer_airtime.json rename to src/rpft/rapidpro/tests/data/actions/transfer_airtime.json diff --git a/rapidpro/tests/data/containers/flow_container_minimal.json b/src/rpft/rapidpro/tests/data/containers/flow_container_minimal.json similarity index 100% rename from rapidpro/tests/data/containers/flow_container_minimal.json rename to src/rpft/rapidpro/tests/data/containers/flow_container_minimal.json diff --git a/rapidpro/tests/data/containers/flow_container_node_ui.json b/src/rpft/rapidpro/tests/data/containers/flow_container_node_ui.json similarity index 100% rename from rapidpro/tests/data/containers/flow_container_node_ui.json rename to src/rpft/rapidpro/tests/data/containers/flow_container_node_ui.json diff --git a/rapidpro/tests/data/containers/flow_container_ui.json b/src/rpft/rapidpro/tests/data/containers/flow_container_ui.json similarity index 100% rename from rapidpro/tests/data/containers/flow_container_ui.json rename to src/rpft/rapidpro/tests/data/containers/flow_container_ui.json diff --git a/rapidpro/tests/data/containers/rapidpro_container_campaign.json b/src/rpft/rapidpro/tests/data/containers/rapidpro_container_campaign.json similarity index 100% rename from rapidpro/tests/data/containers/rapidpro_container_campaign.json rename to src/rpft/rapidpro/tests/data/containers/rapidpro_container_campaign.json diff --git a/rapidpro/tests/data/containers/rapidpro_container_minimal.json b/src/rpft/rapidpro/tests/data/containers/rapidpro_container_minimal.json similarity index 100% rename from rapidpro/tests/data/containers/rapidpro_container_minimal.json rename to src/rpft/rapidpro/tests/data/containers/rapidpro_container_minimal.json diff --git a/rapidpro/tests/data/exits/exit.json b/src/rpft/rapidpro/tests/data/exits/exit.json similarity index 100% rename from rapidpro/tests/data/exits/exit.json rename to src/rpft/rapidpro/tests/data/exits/exit.json diff --git a/rapidpro/tests/data/groups/group.json b/src/rpft/rapidpro/tests/data/groups/group.json similarity index 100% rename from rapidpro/tests/data/groups/group.json rename to src/rpft/rapidpro/tests/data/groups/group.json diff --git a/rapidpro/tests/data/nodes/node_basic.json b/src/rpft/rapidpro/tests/data/nodes/node_basic.json similarity index 100% rename from rapidpro/tests/data/nodes/node_basic.json rename to src/rpft/rapidpro/tests/data/nodes/node_basic.json diff --git a/rapidpro/tests/data/nodes/node_enter_flow.json b/src/rpft/rapidpro/tests/data/nodes/node_enter_flow.json similarity index 100% rename from rapidpro/tests/data/nodes/node_enter_flow.json rename to src/rpft/rapidpro/tests/data/nodes/node_enter_flow.json diff --git a/rapidpro/tests/data/nodes/node_random_router.json b/src/rpft/rapidpro/tests/data/nodes/node_random_router.json similarity index 100% rename from rapidpro/tests/data/nodes/node_random_router.json rename to src/rpft/rapidpro/tests/data/nodes/node_random_router.json diff --git a/rapidpro/tests/data/nodes/node_switch_router.json b/src/rpft/rapidpro/tests/data/nodes/node_switch_router.json similarity index 100% rename from rapidpro/tests/data/nodes/node_switch_router.json rename to src/rpft/rapidpro/tests/data/nodes/node_switch_router.json diff --git a/rapidpro/tests/data/nodes/ui.json b/src/rpft/rapidpro/tests/data/nodes/ui.json similarity index 100% rename from rapidpro/tests/data/nodes/ui.json rename to src/rpft/rapidpro/tests/data/nodes/ui.json diff --git a/rapidpro/tests/data/routers/case.json b/src/rpft/rapidpro/tests/data/routers/case.json similarity index 100% rename from rapidpro/tests/data/routers/case.json rename to src/rpft/rapidpro/tests/data/routers/case.json diff --git a/rapidpro/tests/data/routers/category.json b/src/rpft/rapidpro/tests/data/routers/category.json similarity index 100% rename from rapidpro/tests/data/routers/category.json rename to src/rpft/rapidpro/tests/data/routers/category.json diff --git a/rapidpro/tests/data/routers/exits.json b/src/rpft/rapidpro/tests/data/routers/exits.json similarity index 100% rename from rapidpro/tests/data/routers/exits.json rename to src/rpft/rapidpro/tests/data/routers/exits.json diff --git a/rapidpro/tests/data/routers/router_random_simple.json b/src/rpft/rapidpro/tests/data/routers/router_random_simple.json similarity index 100% rename from rapidpro/tests/data/routers/router_random_simple.json rename to src/rpft/rapidpro/tests/data/routers/router_random_simple.json diff --git a/rapidpro/tests/data/routers/router_random_with_result.json b/src/rpft/rapidpro/tests/data/routers/router_random_with_result.json similarity index 100% rename from rapidpro/tests/data/routers/router_random_with_result.json rename to src/rpft/rapidpro/tests/data/routers/router_random_with_result.json diff --git a/rapidpro/tests/data/routers/router_switch_simple.json b/src/rpft/rapidpro/tests/data/routers/router_switch_simple.json similarity index 100% rename from rapidpro/tests/data/routers/router_switch_simple.json rename to src/rpft/rapidpro/tests/data/routers/router_switch_simple.json diff --git a/rapidpro/tests/data/routers/router_switch_with_result.json b/src/rpft/rapidpro/tests/data/routers/router_switch_with_result.json similarity index 100% rename from rapidpro/tests/data/routers/router_switch_with_result.json rename to src/rpft/rapidpro/tests/data/routers/router_switch_with_result.json diff --git a/rapidpro/tests/data/routers/router_switch_with_wait.json b/src/rpft/rapidpro/tests/data/routers/router_switch_with_wait.json similarity index 100% rename from rapidpro/tests/data/routers/router_switch_with_wait.json rename to src/rpft/rapidpro/tests/data/routers/router_switch_with_wait.json diff --git a/rapidpro/tests/data/routers/router_switch_with_wait_category.json b/src/rpft/rapidpro/tests/data/routers/router_switch_with_wait_category.json similarity index 100% rename from rapidpro/tests/data/routers/router_switch_with_wait_category.json rename to src/rpft/rapidpro/tests/data/routers/router_switch_with_wait_category.json diff --git a/rapidpro/tests/test_actions.py b/src/rpft/rapidpro/tests/test_actions.py similarity index 89% rename from rapidpro/tests/test_actions.py rename to src/rpft/rapidpro/tests/test_actions.py index d1ff914..3fb9894 100644 --- a/rapidpro/tests/test_actions.py +++ b/src/rpft/rapidpro/tests/test_actions.py @@ -1,6 +1,6 @@ import unittest -from rapidpro.models.actions import EnterFlowAction +from rpft.rapidpro.models.actions import EnterFlowAction class TestActions(unittest.TestCase): diff --git a/rapidpro/tests/test_containers.py b/src/rpft/rapidpro/tests/test_containers.py similarity index 91% rename from rapidpro/tests/test_containers.py rename to src/rpft/rapidpro/tests/test_containers.py index b64c362..380c711 100644 --- a/rapidpro/tests/test_containers.py +++ b/src/rpft/rapidpro/tests/test_containers.py @@ -1,11 +1,9 @@ import unittest -import json -from rapidpro.models.containers import RapidProContainer, FlowContainer, UUIDDict -from rapidpro.models.actions import Group, SendMessageAction, AddContactGroupAction, RemoveContactGroupAction -from rapidpro.models.nodes import BasicNode, SwitchRouterNode, EnterFlowNode -from rapidpro.models.routers import SwitchRouter -from rapidpro.models.campaigns import Campaign, CampaignEvent +from rpft.rapidpro.models.containers import RapidProContainer, FlowContainer +from rpft.rapidpro.models.actions import Group, AddContactGroupAction +from rpft.rapidpro.models.nodes import BasicNode, SwitchRouterNode, EnterFlowNode +from rpft.rapidpro.models.campaigns import Campaign, CampaignEvent def get_flow_with_group_and_flow_node(): diff --git a/rapidpro/tests/test_import_export.py b/src/rpft/rapidpro/tests/test_import_export.py similarity index 64% rename from rapidpro/tests/test_import_export.py rename to src/rpft/rapidpro/tests/test_import_export.py index 127a7fe..af91f82 100644 --- a/rapidpro/tests/test_import_export.py +++ b/src/rpft/rapidpro/tests/test_import_export.py @@ -1,54 +1,54 @@ import unittest import json -import glob +from pathlib import Path -from rapidpro.models.actions import Action, Group -from rapidpro.models.common import Exit -from rapidpro.models.routers import RouterCase, RouterCategory, BaseRouter -from rapidpro.models.nodes import BaseNode -from rapidpro.models.containers import FlowContainer, RapidProContainer -from rapidpro.models.campaigns import Campaign, CampaignEvent +from rpft.rapidpro.models.actions import Action, Group +from rpft.rapidpro.models.common import Exit +from rpft.rapidpro.models.routers import RouterCase, RouterCategory, BaseRouter +from rpft.rapidpro.models.nodes import BaseNode +from rpft.rapidpro.models.containers import FlowContainer, RapidProContainer +from rpft.rapidpro.models.campaigns import Campaign, CampaignEvent class TestImportExport(unittest.TestCase): def setUp(self) -> None: - pass + self.data_dir = Path(__file__).parent / "data" def test_all_action_types(self): - actionFilenamesList = glob.glob('rapidpro/tests/data/actions/*.json') + actionFilenamesList = self.data_dir.glob('actions/*.json') for filename in actionFilenamesList: with open(filename, 'r') as f: - action_data = json.load(f) + action_data = json.load(f) action = Action.from_dict(action_data) render_output = action.render() self.assertEqual(render_output, action_data, msg=filename) def test_exits(self): - with open('rapidpro/tests/data/exits/exit.json', 'r') as f: - data = json.load(f) + with open(self.data_dir / 'exits/exit.json', 'r') as f: + data = json.load(f) exit = Exit.from_dict(data) render_output = exit.render() self.assertEqual(render_output, data) def test_groups(self): - with open('rapidpro/tests/data/groups/group.json', 'r') as f: - data = json.load(f) + with open(self.data_dir / 'groups/group.json', 'r') as f: + data = json.load(f) group = Group.from_dict(data) render_output = group.render() self.assertEqual(render_output, data) def test_cases(self): - with open('rapidpro/tests/data/routers/case.json', 'r') as f: - data = json.load(f) + with open(self.data_dir / 'routers/case.json', 'r') as f: + data = json.load(f) case = RouterCase.from_dict(data) render_output = case.render() self.assertEqual(render_output, data) def test_categories(self): - with open('rapidpro/tests/data/routers/category.json', 'r') as f: - data = json.load(f) - with open('rapidpro/tests/data/routers/exits.json', 'r') as f: - exit_data = json.load(f) + with open(self.data_dir / 'routers/category.json', 'r') as f: + data = json.load(f) + with open(self.data_dir / 'routers/exits.json', 'r') as f: + exit_data = json.load(f) exits = [Exit.from_dict(exit) for exit in exit_data] category = RouterCategory.from_dict(data, exits) render_output = category.render() @@ -59,12 +59,12 @@ def test_all_router_types(self): # within switch routers is always the last one. # This might change once we support "Expired" categories self.maxDiff = None - routerFilenamesList = glob.glob('rapidpro/tests/data/routers/router_*.json') + routerFilenamesList = self.data_dir.glob('routers/router_*.json') for filename in routerFilenamesList: with open(filename, 'r') as f: - router_data = json.load(f) - with open('rapidpro/tests/data/routers/exits.json', 'r') as f: - exit_data = json.load(f) + router_data = json.load(f) + with open(self.data_dir / 'routers/exits.json', 'r') as f: + exit_data = json.load(f) exits = [Exit.from_dict(exit) for exit in exit_data] router = BaseRouter.from_dict(router_data, exits) render_output = router.render() @@ -72,10 +72,10 @@ def test_all_router_types(self): def test_all_node_types(self): self.maxDiff = None - nodeFilenamesList = glob.glob('rapidpro/tests/data/nodes/node_*.json') + nodeFilenamesList = self.data_dir.glob('nodes/node_*.json') for filename in nodeFilenamesList: with open(filename, 'r') as f: - node_data = json.load(f) + node_data = json.load(f) node = BaseNode.from_dict(node_data) render_output = node.render() self.assertEqual(render_output, node_data, msg=filename) @@ -83,10 +83,10 @@ def test_all_node_types(self): def test_flow_containers(self): self.maxDiff = None # TODO: Add test with localization (of different objects) to ensure it is maintained - containerFilenamesList = glob.glob('rapidpro/tests/data/containers/flow_container_*.json') + containerFilenamesList = self.data_dir.glob('containers/flow_container_*.json') for filename in containerFilenamesList: with open(filename, 'r') as f: - container_data = json.load(f) + container_data = json.load(f) container = FlowContainer.from_dict(container_data) render_output = container.render() # TODO: compare nodes/UI element-wise, for smaller error output? @@ -94,30 +94,30 @@ def test_flow_containers(self): def test_rapidpro_containers(self): self.maxDiff = None - containerFilenamesList = glob.glob('rapidpro/tests/data/containers/rapidpro_container_*.json') + containerFilenamesList = self.data_dir.glob('containers/rapidpro_container_*.json') for filename in containerFilenamesList: with open(filename, 'r') as f: - container_data = json.load(f) + container_data = json.load(f) container = RapidProContainer.from_dict(container_data) render_output = container.render() self.assertEqual(render_output, container_data, msg=filename) def test_campaign_events(self): self.maxDiff = None - filenamesList = glob.glob('rapidpro/tests/data/campaigns/event_*.json') + filenamesList = self.data_dir.glob('campaigns/event_*.json') for filename in filenamesList: with open(filename, 'r') as f: - data = json.load(f) + data = json.load(f) event = CampaignEvent.from_dict(data) render_output = event.render() self.assertEqual(render_output, data, msg=filename) def test_campaigns(self): self.maxDiff = None - filenamesList = glob.glob('rapidpro/tests/data/campaigns/campaign_*.json') + filenamesList = self.data_dir.glob('campaigns/campaign_*.json') for filename in filenamesList: with open(filename, 'r') as f: - data = json.load(f) + data = json.load(f) campaign = Campaign.from_dict(data) render_output = campaign.render() self.assertEqual(render_output, data, msg=filename) diff --git a/rapidpro/tests/test_nodes.py b/src/rpft/rapidpro/tests/test_nodes.py similarity index 91% rename from rapidpro/tests/test_nodes.py rename to src/rpft/rapidpro/tests/test_nodes.py index 3d01313..e4232e6 100644 --- a/rapidpro/tests/test_nodes.py +++ b/src/rpft/rapidpro/tests/test_nodes.py @@ -1,7 +1,7 @@ import unittest -from rapidpro.models.actions import SendMessageAction -from rapidpro.models.nodes import BasicNode +from rpft.rapidpro.models.actions import SendMessageAction +from rpft.rapidpro.models.nodes import BasicNode class TestNodes(unittest.TestCase): diff --git a/rapidpro/tests/test_routers.py b/src/rpft/rapidpro/tests/test_routers.py similarity index 98% rename from rapidpro/tests/test_routers.py rename to src/rpft/rapidpro/tests/test_routers.py index d468d79..402acb2 100644 --- a/rapidpro/tests/test_routers.py +++ b/src/rpft/rapidpro/tests/test_routers.py @@ -1,6 +1,6 @@ import unittest -from rapidpro.models.routers import SwitchRouter, RandomRouter +from rpft.rapidpro.models.routers import SwitchRouter, RandomRouter class TestRouters(unittest.TestCase): diff --git a/rapidpro/utils.py b/src/rpft/rapidpro/utils.py similarity index 100% rename from rapidpro/utils.py rename to src/rpft/rapidpro/utils.py diff --git a/src/rpft/tests/__init__.py b/src/rpft/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/input/all_test_flows.xlsx b/src/rpft/tests/input/all_test_flows.xlsx similarity index 100% rename from tests/input/all_test_flows.xlsx rename to src/rpft/tests/input/all_test_flows.xlsx diff --git a/tests/input/example1/content_index.csv b/src/rpft/tests/input/example1/content_index.csv similarity index 100% rename from tests/input/example1/content_index.csv rename to src/rpft/tests/input/example1/content_index.csv diff --git a/tests/input/example1/content_index.xlsx b/src/rpft/tests/input/example1/content_index.xlsx similarity index 100% rename from tests/input/example1/content_index.xlsx rename to src/rpft/tests/input/example1/content_index.xlsx diff --git a/tests/input/example1/content_index1.csv b/src/rpft/tests/input/example1/content_index1.csv similarity index 100% rename from tests/input/example1/content_index1.csv rename to src/rpft/tests/input/example1/content_index1.csv diff --git a/tests/input/example1/content_index2.csv b/src/rpft/tests/input/example1/content_index2.csv similarity index 100% rename from tests/input/example1/content_index2.csv rename to src/rpft/tests/input/example1/content_index2.csv diff --git a/tests/input/example1/my_basic_flow.csv b/src/rpft/tests/input/example1/my_basic_flow.csv similarity index 100% rename from tests/input/example1/my_basic_flow.csv rename to src/rpft/tests/input/example1/my_basic_flow.csv diff --git a/tests/input/example1/my_campaign.csv b/src/rpft/tests/input/example1/my_campaign.csv similarity index 100% rename from tests/input/example1/my_campaign.csv rename to src/rpft/tests/input/example1/my_campaign.csv diff --git a/tests/input/example1/my_template.csv b/src/rpft/tests/input/example1/my_template.csv similarity index 100% rename from tests/input/example1/my_template.csv rename to src/rpft/tests/input/example1/my_template.csv diff --git a/tests/input/example1/nesteddata.csv b/src/rpft/tests/input/example1/nesteddata.csv similarity index 100% rename from tests/input/example1/nesteddata.csv rename to src/rpft/tests/input/example1/nesteddata.csv diff --git a/parsers/creation/tests/datarowmodels/nestedmodel.py b/src/rpft/tests/input/example1/nestedmodel.py similarity index 79% rename from parsers/creation/tests/datarowmodels/nestedmodel.py rename to src/rpft/tests/input/example1/nestedmodel.py index 4fb8a74..d89bad1 100644 --- a/parsers/creation/tests/datarowmodels/nestedmodel.py +++ b/src/rpft/tests/input/example1/nestedmodel.py @@ -1,5 +1,6 @@ -from parsers.creation.datarowmodel import DataRowModel -from parsers.common.rowparser import ParserModel +from rpft.parsers.creation.datarowmodel import DataRowModel +from rpft.parsers.common.rowparser import ParserModel + class CustomModel(ParserModel): # Because this does not directly define the content of a datasheet, diff --git a/tests/input/groups_and_flows.csv b/src/rpft/tests/input/groups_and_flows.csv similarity index 100% rename from tests/input/groups_and_flows.csv rename to src/rpft/tests/input/groups_and_flows.csv diff --git a/tests/input/loop_and_multiple_conditions.csv b/src/rpft/tests/input/loop_and_multiple_conditions.csv similarity index 100% rename from tests/input/loop_and_multiple_conditions.csv rename to src/rpft/tests/input/loop_and_multiple_conditions.csv diff --git a/tests/input/loop_from_start.csv b/src/rpft/tests/input/loop_from_start.csv similarity index 100% rename from tests/input/loop_from_start.csv rename to src/rpft/tests/input/loop_from_start.csv diff --git a/tests/input/master_sheet.csv b/src/rpft/tests/input/master_sheet.csv similarity index 100% rename from tests/input/master_sheet.csv rename to src/rpft/tests/input/master_sheet.csv diff --git a/tests/input/no_switch_nodes.csv b/src/rpft/tests/input/no_switch_nodes.csv similarity index 100% rename from tests/input/no_switch_nodes.csv rename to src/rpft/tests/input/no_switch_nodes.csv diff --git a/tests/input/no_switch_nodes_without_row_ids.csv b/src/rpft/tests/input/no_switch_nodes_without_row_ids.csv similarity index 100% rename from tests/input/no_switch_nodes_without_row_ids.csv rename to src/rpft/tests/input/no_switch_nodes_without_row_ids.csv diff --git a/tests/input/rejoin.csv b/src/rpft/tests/input/rejoin.csv similarity index 100% rename from tests/input/rejoin.csv rename to src/rpft/tests/input/rejoin.csv diff --git a/tests/input/switch_nodes.csv b/src/rpft/tests/input/switch_nodes.csv similarity index 100% rename from tests/input/switch_nodes.csv rename to src/rpft/tests/input/switch_nodes.csv diff --git a/tests/input/templates/yes_no_template.csv b/src/rpft/tests/input/templates/yes_no_template.csv similarity index 100% rename from tests/input/templates/yes_no_template.csv rename to src/rpft/tests/input/templates/yes_no_template.csv diff --git a/tests/input/templates/yes_no_template_instances.csv b/src/rpft/tests/input/templates/yes_no_template_instances.csv similarity index 100% rename from tests/input/templates/yes_no_template_instances.csv rename to src/rpft/tests/input/templates/yes_no_template_instances.csv diff --git a/tests/output/all_test_flows.json b/src/rpft/tests/output/all_test_flows.json similarity index 100% rename from tests/output/all_test_flows.json rename to src/rpft/tests/output/all_test_flows.json diff --git a/tests/test_contentindexparser.py b/src/rpft/tests/test_contentindexparser.py similarity index 64% rename from tests/test_contentindexparser.py rename to src/rpft/tests/test_contentindexparser.py index e06c0c9..b84cb91 100644 --- a/tests/test_contentindexparser.py +++ b/src/rpft/tests/test_contentindexparser.py @@ -1,13 +1,17 @@ import unittest -import json +from pathlib import Path + +from rpft.parsers.sheets.csv_sheet_reader import CSVSheetReader +from rpft.parsers.sheets.xlsx_sheet_reader import XLSXSheetReader +from rpft.parsers.creation.contentindexparser import ContentIndexParser +from rpft.tests.utils import traverse_flow, Context -from parsers.sheets.csv_sheet_reader import CSVSheetReader -from parsers.sheets.xlsx_sheet_reader import XLSXSheetReader -from parsers.creation.contentindexparser import ContentIndexParser -from tests.utils import traverse_flow, Context class TestParsing(unittest.TestCase): + def setUp(self): + self.input_dir = Path(__file__).parent / "input/example1" + def compare_messages(self, render_output, flow_name, messages_exp, context=None): flow_found = False for flow in render_output["flows"]: @@ -26,7 +30,7 @@ def compare_to_expected(self, render_output): self.assertEqual(render_output["campaigns"][0]["name"], "my_campaign") self.assertEqual(render_output["campaigns"][0]["group"]["name"], "My Group") self.assertEqual(render_output["campaigns"][0]["events"][0]["flow"]["name"], 'my_basic_flow') - self.assertEqual(render_output["campaigns"][0]["events"][0]["flow"]["uuid"], render_output["flows"][2]["uuid"]) + self.assertEqual(render_output["campaigns"][0]["events"][0]["flow"]["uuid"], render_output["flows"][2]["uuid"]) def check_example1(self, ci_parser): container = ci_parser.parse_all() @@ -36,21 +40,30 @@ def check_example1(self, ci_parser): def test_example1_csv(self): # Same test as test_generate_flows in parsers/creation/tests/test_contentindexparser # but with csvs - sheet_reader = CSVSheetReader('tests/input/example1/content_index.csv') - ci_parser = ContentIndexParser(sheet_reader, 'tests.input.example1.nestedmodel') + sheet_reader = CSVSheetReader(self.input_dir / "content_index.csv") + ci_parser = ContentIndexParser( + sheet_reader, + 'rpft.tests.input.example1.nestedmodel' + ) self.check_example1(ci_parser) def test_example1_split_csv(self): # Same test as test_generate_flows in parsers/creation/tests/test_contentindexparser # but with csvs - sheet_reader = CSVSheetReader('tests/input/example1/content_index1.csv') - ci_parser = ContentIndexParser(sheet_reader, 'tests.input.example1.nestedmodel') - sheet_reader = CSVSheetReader('tests/input/example1/content_index2.csv') + sheet_reader = CSVSheetReader(self.input_dir / "content_index1.csv") + ci_parser = ContentIndexParser( + sheet_reader, + 'rpft.tests.input.example1.nestedmodel' + ) + sheet_reader = CSVSheetReader(self.input_dir / "content_index2.csv") ci_parser.add_content_index(sheet_reader) self.check_example1(ci_parser) def test_example1_xlsx(self): # Same test as above - sheet_reader = XLSXSheetReader('tests/input/example1/content_index.xlsx') - ci_parser = ContentIndexParser(sheet_reader, 'tests.input.example1.nestedmodel') + sheet_reader = XLSXSheetReader(self.input_dir / "content_index.xlsx") + ci_parser = ContentIndexParser( + sheet_reader, + 'rpft.tests.input.example1.nestedmodel' + ) self.check_example1(ci_parser) diff --git a/tests/test_flowparser.py b/src/rpft/tests/test_flowparser.py similarity index 92% rename from tests/test_flowparser.py rename to src/rpft/tests/test_flowparser.py index 6450715..6a2913e 100644 --- a/tests/test_flowparser.py +++ b/src/rpft/tests/test_flowparser.py @@ -1,11 +1,13 @@ -import unittest -import json import copy +import json +import unittest +from pathlib import Path + +from rpft.tests.utils import traverse_flow, Context, get_table_from_file +from rpft.parsers.creation.flowparser import FlowParser +from rpft.rapidpro.models.containers import RapidProContainer, FlowContainer +from rpft.parsers.common.tests.mock_sheetparser import MockSheetParser -from .utils import traverse_flow, Context, get_dict_from_csv, get_table_from_file -from parsers.creation.flowparser import FlowParser -from rapidpro.models.containers import RapidProContainer, FlowContainer -from parsers.common.tests.mock_sheetparser import MockSheetParser class TestFlowParser(unittest.TestCase): def setUp(self) -> None: @@ -20,7 +22,7 @@ def run_example(self, filename, flow_name, context): # print(json.dumps(output_flow, indent=2)) # Load the expected output flow - with open("tests/output/all_test_flows.json", 'r') as file: + with open(Path(__file__).parent / "output/all_test_flows.json", 'r') as file: output_exp = json.load(file) for flow in output_exp["flows"]: if flow["name"] == flow_name: @@ -48,7 +50,7 @@ def run_example(self, filename, flow_name, context): def test_no_switch_nodes(self): self.run_example('input/no_switch_nodes.csv', 'no_switch_nodes', Context()) - def test_no_switch_nodes(self): + def test_no_switch_nodes_without_row_ids(self): self.run_example('input/no_switch_nodes_without_row_ids.csv', 'no_switch_nodes', Context()) def test_switch_nodes(self): diff --git a/tests/test_flowparser_reverse.py b/src/rpft/tests/test_flowparser_reverse.py similarity index 92% rename from tests/test_flowparser_reverse.py rename to src/rpft/tests/test_flowparser_reverse.py index 53231cc..29b249f 100644 --- a/tests/test_flowparser_reverse.py +++ b/src/rpft/tests/test_flowparser_reverse.py @@ -1,10 +1,9 @@ import unittest -import json -import copy -from .utils import traverse_flow, Context, get_dict_from_csv, get_table_from_file -from parsers.creation.flowparser import FlowParser -from rapidpro.models.containers import RapidProContainer +from rpft.tests.utils import get_table_from_file +from rpft.parsers.creation.flowparser import FlowParser +from rpft.rapidpro.models.containers import RapidProContainer + class TestFlowParserReverse(unittest.TestCase): def setUp(self) -> None: diff --git a/tests/test_template_sheet_parser.py b/src/rpft/tests/test_template_sheet_parser.py similarity index 86% rename from tests/test_template_sheet_parser.py rename to src/rpft/tests/test_template_sheet_parser.py index ef7e080..fd7dfee 100644 --- a/tests/test_template_sheet_parser.py +++ b/src/rpft/tests/test_template_sheet_parser.py @@ -1,12 +1,10 @@ -import json import unittest import copy -from parsers.common.rowparser import RowParser, ParserModel -from parsers.common.cellparser import CellParser -from parsers.creation.template_sheet_parser import TemplateSheetParser -from rapidpro.models.containers import RapidProContainer -from .utils import traverse_flow, Context, get_dict_from_csv, get_table_from_file +from rpft.parsers.common.rowparser import RowParser, ParserModel +from rpft.parsers.common.cellparser import CellParser +from rpft.parsers.creation.template_sheet_parser import TemplateSheetParser +from rpft.tests.utils import traverse_flow, Context, get_table_from_file class ResponseMessages(ParserModel): diff --git a/tests/utils.py b/src/rpft/tests/utils.py similarity index 99% rename from tests/utils.py rename to src/rpft/tests/utils.py index 5073cda..9a3cf15 100644 --- a/tests/utils.py +++ b/src/rpft/tests/utils.py @@ -1,12 +1,9 @@ import csv -import copy -import json -import re -import uuid -from collections import defaultdict from pathlib import Path +import re import tablib + def get_dict_from_csv(csv_file_path): with open(f'{Path(__file__).parents[0].absolute()}/{csv_file_path}') as csv_file: csv_reader = csv.DictReader(csv_file)