.. highlight:: shell ============ Contributing ============ Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. You can contribute in many ways: Types of Contributions ---------------------- Report Bugs ~~~~~~~~~~~ Report bugs at https://gitlab.aicrowd.com/flatland/flatland/issues. If you are reporting a bug, please include: * Your operating system name and version. * Any details about your local setup that might be helpful in troubleshooting. * Detailed steps to reproduce the bug. Fix Bugs ~~~~~~~~ Look through the Repository Issue Tracker for bugs. Anything tagged with "bug" and "help wanted" is open to whoever wants to implement it. Implement Features ~~~~~~~~~~~~~~~~~~ Look through the Repository Issue Tracker for features. Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it. Write Documentation ~~~~~~~~~~~~~~~~~~~ flatland could always use more documentation, whether as part of the official flatland docs, in docstrings, or even on the web in blog posts, articles, and such. A quick reference for writing good docstrings is available at : https://docs.python-guide.org/writing/documentation/#writing-docstrings Submit Feedback ~~~~~~~~~~~~~~~ The best way to send feedback is to file an issue at https://gitlab.aicrowd.com/flatland/flatland/issues. If you are proposing a feature: * Explain in detail how it would work. * Keep the scope as narrow as possible, to make it easier to implement. * Remember that this is a volunteer-driven project, and that contributions are welcome :) Get Started! ------------ Ready to contribute? Here's how to set up `flatland` for local development. 1. Fork the `flatland` repo on https://gitlab.aicrowd.com/flatland/flatland . 2. Clone your fork locally:: $ git clone git@gitlab.aicrowd.com:flatland/flatland.git 3. Install the software dependencies via Anaconda-3 or Miniconda-3. (This assumes you have Anaconda installed by following the instructions `here <https://www.anaconda.com/distribution>`_) $ conda install -c conda-forge tox-conda $ conda install tox $ tox -v --recreate This will create a virtual env you can then use. These steps are performed if you run $ getting_started/getting_started.bat/.sh from Anaconda prompt. 4. Create a branch for local development:: $ git checkout -b name-of-your-bugfix-or-feature Now you can make your changes locally. 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: $ flake8 flatland tests examples benchmarks $ python setup.py test or py.test $ tox To get flake8 and tox, just pip install them into your virtualenv. 6. Commit your changes and push your branch to Gitlab:: $ git add . $ git commit -m "Addresses #<issue-number> Your detailed description of your changes." $ git push origin name-of-your-bugfix-or-feature 7. Submit a merge request through the Gitlab repository website. Merge Request Guidelines ------------------------- Before you submit a merge request, check that it meets these guidelines: 1. The merge request should include tests. 2. If the merge request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst. 3. The merge request should work for Python 3.6, 3.7 and for PyPy. Check https://gitlab.aicrowd.com/flatland/flatland/pipelines and make sure that the tests pass for all supported Python versions. Tips ---- To run a subset of tests:: $ py.test tests.test_flatland Deploying --------- A reminder for the maintainers on how to deploy. Make sure all your changes are committed . Then run:: $ bumpversion patch # possible: major / minor / patch $ git push $ git push --tags TODO: Travis will then deploy to PyPI if tests pass. (To be configured properly by Mohanty) Local Evaluation ---------------- This document explains you how to locally evaluate your submissions before making an official submission to the competition. Requirements ~~~~~~~~~~~~ * **flatland-rl** : We expect that you have `flatland-rl` installed by following the instructions in [README.md](README.md). * **redis** : Additionally you will also need to have `redis installed <https://redis.io/topics/quickstart>`_ and **should have it running in the background.** Test Data ~~~~~~~~~ * **test env data** : You can `download and untar the test-env-data <https://www.aicrowd.com/challenges/flatland-challenge/dataset_files>`, at a location of your choice, lets say `/path/to/test-env-data/`. After untarring the folder, the folder structure should look something like: .. code-block:: console . └── test-env-data ├── Test_0 │ ├── Level_0.pkl │ └── Level_1.pkl ├── Test_1 │ ├── Level_0.pkl │ └── Level_1.pkl ├.................. ├.................. ├── Test_8 │ ├── Level_0.pkl │ └── Level_1.pkl └── Test_9 ├── Level_0.pkl └── Level_1.pkl Evaluation Service ~~~~~~~~~~~~~~~~~~ * **start evaluation service** : Then you can start the evaluator by running : .. code-block:: console flatland-evaluator --tests /path/to/test-env-data/ RemoteClient ~~~~~~~~~~~~ * **run client** : Some `sample submission code can be found in the starter-kit <https://github.com/AIcrowd/flatland-challenge-starter-kit/>`_, but before you can run your code locally using `FlatlandRemoteClient`, you will have to set the `AICROWD_TESTS_FOLDER` environment variable to the location where you previous untarred the folder with `the test-env-data`: .. code-block:: console export AICROWD_TESTS_FOLDER="/path/to/test-env-data/" # or on Windows : # # set AICROWD_TESTS_FOLDER "\path\to\test-env-data\" # and then finally run your code python run.py Technical Guidelines -------------------- Merge Requests ~~~~~~~~~~~~~~ Although we cannot enforce it technically, we ask for * merge requests to be reviewed * review points to be implemented using the 'discussions resolved' .. image:: images/DiscussionsResolved.PNG * source branches to be deleted and commits to be squashed .. image:: images/SourceBranchSquash.PNG Naming Conventions ~~~~~~~~~~~~~~~~~~ We use the pylint naming conventions: `module_name`, `package_name`, `ClassName`, `method_name`, `ExceptionName`, `function_name`, `GLOBAL_CONSTANT_NAME`, `global_var_name`, `instance_var_name`, `function_parameter_name`, `local_var_name`. numpydoc ~~~~~~~~ Docstrings should be formatted using numpydoc_. .. _numpydoc: https://numpydoc.readthedocs.io/en/latest/format.html Acessing resources ~~~~~~~~~~~~~~~~~~ We use `importlib-resources`_ to read from local files. Sample usages: .. code-block:: python from importlib_resources import path with path(package, resource) as file_in: new_grid = np.load(file_in) And: .. code-block:: python from importlib_resources import read_binary load_data = read_binary(package, resource) self.set_full_state_msg(load_data) .. _importlib-resources: https://importlib-resources.readthedocs.io/en/latest/ Renders the scene into a image (screenshot) .. code-block:: python renderer.gl.save_image("filename.bmp") Type Hints ~~~~~~~~~~ We use Type Hints (type_hints_pep484_) for better readability and better IDE support. .. code-block:: python # This is how you declare the type of a variable type in Python 3.6 age: int = 1 # In Python 3.5 and earlier you can use a type comment instead # (equivalent to the previous definition) age = 1 # type: int # You don't need to initialize a variable to annotate it a: int # Ok (no value at runtime until assigned) # The latter is useful in conditional branches child: bool if age < 18: child = True else: child = False Have a look at the _type_hints_cheat_sheet to get started with Type Hints. Caveat: We discourage the usage of Type Aliases for structured data since its members remain unnamed (see refactor_unnamed_tuples_). .. code-block:: python # Discouraged: Type Alias with unnamed members Tuple[int, int] # Better: use NamedTuple from typing import NamedTuple Position = NamedTuple('Position', [ ('r', int), ('c', int) ] .. _type_hints_pep484: https://www.python.org/dev/peps/pep-0484/ .. _type_hints_cheat_sheet: https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html .. _refactor_unnamed_tuples: https://gitlab.aicrowd.com/flatland/flatland/issues/284 NamedTuple ~~~~~~~~~~ For structured data containers for which we do not write methods that have to ensure some (class) invariant over multiple members, we use `NamedTuple`s instead of plain `Dict`s to ensure better readability by .. code-block:: python from typing import NamedTuple RailEnvNextAction = NamedTuple('RailEnvNextAction', [ ('action', RailEnvActions), ('next_position', RailEnvGridPos), ('next_direction', Grid4TransitionsEnum) ]) Members of NamedTuple can then be accessed through `.<member>` instead of `['<key>']`. Class Attributes ~~~~~~~~~~~~~~~~ We use classes for data structures if we need to write methods that ensure (class) invariants over multiple members. We use the attrs_ class decorator and a way to declaratively define the attributes on that class: .. code-block:: python @attrs class Replay(object): position = attrib(type=Tuple[int, int]) .. _attrs: https://github.com/python-attrs/attrs Abstract Base Classes ~~~~~~~~~~~~~~~~~~~~~ We use the abc_ class decorator and a way to declaratively define the attributes on that class: .. code-block:: python # abc_base.py import abc class PluginBase(metaclass=abc.ABCMeta): @abc.abstractmethod def load(self, input): """Retrieve data from the input source and return an object. """ @abc.abstractmethod def save(self, output, data): """Save the data object to the output.""" And then .. code-block:: python # abc_subclass.py import abc from abc_base import PluginBase class SubclassImplementation(PluginBase): def load(self, input): return input.read() def save(self, output, data): return output.write(data) if __name__ == '__main__': print('Subclass:', issubclass(SubclassImplementation, PluginBase)) print('Instance:', isinstance(SubclassImplementation(), PluginBase)) .. _abc: https://pymotw.com/3/abc/ Currying ~~~~~~~~ We discourage currying to encapsulate state since we often the stateful object to have multiple methods. Thus, we should refactor our generators and use classes instead (refactor_currying_). .. code-block:: python # Type Alias RailGeneratorProduct = Tuple[GridTransitionMap, Optional[Dict]] RailGenerator = Callable[[int, int, int, int], RailGeneratorProduct] # Currying: a function that returns a confectioned function with internal state def complex_rail_generator(nr_start_goal=1, nr_extra=100, min_dist=20, max_dist=99999, seed=1) -> RailGenerator: .. _refactor_currying: https://gitlab.aicrowd.com/flatland/flatland/issues/283