diff --git a/.gitignore b/.gitignore index 0df0a146..dd1868e0 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ tox.ini /.vscode .eggs *.code-workspace +dragonfly.log* diff --git a/README.md b/README.md index 6af2bd95..b54fcffe 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,24 @@ Dragonfly is collection of libraries to model and analyze urban climate, energy use, and daylight. It extends the capabilites of [honeybee-core](https://github.com/ladybug-tools/honeybee-core) for the urban scale. +This repository is the core repository that provides dragonfly's common functionalities. +To extend these functionalities you should install available Dragonfly extensions or write +your own. + +Here are a number of frequently used extensions for Dragonfly: +- [dragonfly-energy](https://github.com/ladybug-tools/dragonfly-energy): Adds Energy simulation to Dragonfly. + + ## Installation -```console -pip install dragonfly-core -``` -## QuickStart -```python -import dragonfly +`pip install -U dragonfly-core` -``` +If you want to also include the command line interface try: + +`pip install -U dragonfly-core[cli]` + +To check if Dragonfly command line is installed correctly try `dragonfly viz` and you +should get a `viiiiiiiiiiiiizzzzzzzzz!` back in response! :bee: ## [API Documentation](https://www.ladybug.tools/dragonfly-core/docs/) diff --git a/dev-requirements.txt b/dev-requirements.txt index 5a5fd4e2..8b63345f 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -2,6 +2,7 @@ coverage==4.5.2 coveralls==1.5.1 pytest==4.1.0 pytest-cov==2.6.1 +click>=5.1 Sphinx==1.8.5 sphinx-bootstrap-theme==0.6.5 sphinxcontrib-fulltoc==1.2.0 diff --git a/docs/cli.rst b/docs/cli.rst new file mode 100644 index 00000000..09f100e0 --- /dev/null +++ b/docs/cli.rst @@ -0,0 +1,6 @@ +dragonfly command line interface +=============================== + +.. click:: dragonfly.cli:main + :prog: dragonfly + :show-nested: diff --git a/docs/conf.py b/docs/conf.py index cc04e76b..78b76fa1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ import sys import datetime now = datetime.datetime.now() -sys.path.insert(0, os.path.abspath('../')) +sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- @@ -49,7 +49,8 @@ 'sphinx.ext.viewcode', 'sphinx.ext.githubpages', 'sphinxcontrib.fulltoc', - 'sphinx.ext.napoleon' + 'sphinx.ext.napoleon', + 'sphinx_click.ext' ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/index.rst b/docs/index.rst index a9822be4..9f1abb6e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,11 +1,43 @@ -Welcome to dragonfly-core's documentation! -=================================== +Welcome to Dragonfly's documentation! +========================================= + +.. image:: http://www.ladybug.tools/assets/img/dragonfly.png + +Dragonfly is collection of libraries to model and analyze urban climate, energy +use, and daylight. It extends the capabilites of +`honeybee-core `_ for the urban scale. + + +Installation +============ + +To install the core library try ``pip install -U dragonfly-core``. + +If you want to also include the command line interface try ``pip install -U dragonfly-core[cli]``. + +To check if the Dragonfly command line is installed correctly try ``dragonfly viz`` and you +should get a ``viiiiiiiiiiiiizzzzzzzzz!`` back in response! + + +Documentation +============= + +This document includes `Dragonfly API documentation <#dragonfly>`_ and +`Dragonfly Command Line Interface <#id1>`_ documentation for ``dragonfly core`` and does +not include the documentation for dragonfly extensions. For each extension refer to +extension's documentation page. + +Here are a number of popular Dragonfly extensions: + +- `dragonfly-energy `_ + .. toctree:: :maxdepth: 2 :caption: Contents: .. include:: modules.rst +.. include:: cli.rst Indices and tables diff --git a/dragonfly/__main__.py b/dragonfly/__main__.py new file mode 100644 index 00000000..b7574377 --- /dev/null +++ b/dragonfly/__main__.py @@ -0,0 +1,4 @@ +from dragonfly.cli import main + +if __name__ == '__main__': + main() diff --git a/dragonfly/cli/__init__.py b/dragonfly/cli/__init__.py new file mode 100644 index 00000000..6dc56280 --- /dev/null +++ b/dragonfly/cli/__init__.py @@ -0,0 +1,82 @@ +""" +Command Line Interface (CLI) entry point for dragonfly and dragonfly extensions. + +Use this file only to add commands related to dragonfly-core. For adding extra commands +from each extention see below. + +Dragonfly is using click (https://click.palletsprojects.com/en/7.x/) for creating the CLI. +You can extend the command line interface from inside each extention by following these +steps: + +1. Create a ``cli`` folder in your extension. +2. Import the ``main`` function from this ``dragonfly.cli``. +3. Add your commands and command groups to main using add_command method. +4. Add ``import [your-extention].cli`` to ``__init__.py`` file to the commands are added + to the cli when the module is loaded. + +Good practice is to group all your extention commands in a command group named after +the extension. This will make the commands organized under extension namespace. For +instance commands for `dragonfly-energy` will be called like ``dragonfly energy [energy-command]``. + + +.. code-block:: python + + import click + from dragonfly.cli import main + + @click.group() + def energy(): + pass + + # add commands to energy group + @energy.command('to-idf') + # ... + def to_idf(): + pass + + # finally add the newly created commands to dragonfly cli + main.add_command(energy) + + # do not forget to import this module in __init__.py otherwise it will not be added + # to dragonfly commands. + +Note: + For extension with several commands you can use a folder structure instead of a single + file. +""" + +try: + import click +except ImportError: + raise ImportError( + 'click module is not installed. Try `pip install dragonfly-core[cli]` command.' + ) + +from dragonfly.cli.validate import validate + +import sys +import os +import logging +import json + + +_logger = logging.getLogger(__name__) + + +@click.group() +@click.version_option() +def main(): + pass + + +@main.command('viz') +def viz(): + """Check if dragonfly is flying!""" + click.echo('viiiiiiiiiiiiizzzzzzzzz!') + + +main.add_command(validate) + + +if __name__ == "__main__": + main() diff --git a/dragonfly/cli/validate.py b/dragonfly/cli/validate.py new file mode 100644 index 00000000..530caccc --- /dev/null +++ b/dragonfly/cli/validate.py @@ -0,0 +1,56 @@ +"""dragonfly validation commands.""" + +try: + import click +except ImportError: + raise ImportError( + 'click is not installed. Try `pip install . [cli]` command.' + ) + +from dragonfly.model import Model + +import sys +import os +import logging +import json + +_logger = logging.getLogger(__name__) + +try: + import dragonfly_schema.model as schema_model +except ImportError: + _logger.exception( + 'dragonfly_schema is not installed. Try `pip install . [cli]` command.' + ) + + +@click.group(help='Commands for validating Dragonfly JSON files.') +def validate(): + pass + +@validate.command('model') +@click.argument('model-json') +def validate_model(model_json): + """Validate a Model JSON file against the Dragonfly schema. + \b + Args: + model_json: Full path to a Model JSON file. + """ + try: + assert os.path.isfile(model_json), 'No JSON file found at {}.'.format(model_json) + + # validate the Model JSON + click.echo('Validating Model JSON ...') + schema_model.Model.parse_file(model_json) + click.echo('Pydantic validation passed.') + with open(model_json) as json_file: + data = json.load(json_file) + parsed_model = Model.from_dict(data) + parsed_model.check_missing_adjacencies(raise_exception=True) + click.echo('Python re-serialization passed.') + click.echo('Congratulations! Yout Model JSON is valid!') + except Exception as e: + _logger.exception('Model validation failed.\n{}'.format(e)) + sys.exit(1) + else: + sys.exit(0) diff --git a/setup.py b/setup.py index 8a4ede19..7d7f92af 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setuptools.setup( name="dragonfly-core", - use_scm_version = True, + use_scm_version=True, setup_requires=['setuptools_scm'], author="Ladybug Tools", author_email="info@ladybug.tools", @@ -20,6 +20,12 @@ url="https://github.com/ladybug-tools/dragonfly-core", packages=setuptools.find_packages(exclude=["tests"]), install_requires=requirements, + extra_requires={ + 'cli': ['click>=5.1', 'dragonfly-schema>=1.2.0'] + }, + entry_points={ + "console_scripts": ["dragonfly = dragonfly.cli:main"] + }, classifiers=[ "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.6", diff --git a/tests/cli_test.py b/tests/cli_test.py new file mode 100644 index 00000000..7febfeae --- /dev/null +++ b/tests/cli_test.py @@ -0,0 +1,12 @@ +"""Test cli.""" + +from click.testing import CliRunner +from dragonfly.cli import viz + + +def test_viz(): + runner = CliRunner() + result = runner.invoke(viz) + assert result.exit_code == 0 + assert result.output.startswith('vi') + assert result.output.endswith('z!\n')