{"payload":{"allShortcutsEnabled":false,"fileTree":{"doc":{"items":[{"name":"_static","path":"doc/_static","contentType":"directory"},{"name":"commands","path":"doc/commands","contentType":"directory"},{"name":"config","path":"doc/config","contentType":"directory"},{"name":"remotes","path":"doc/remotes","contentType":"directory"},{"name":"runtimes","path":"doc/runtimes","contentType":"directory"},{"name":".gitignore","path":"doc/.gitignore","contentType":"file"},{"name":"Makefile","path":"doc/Makefile","contentType":"file"},{"name":"aws-batch.md","path":"doc/aws-batch.md","contentType":"file"},{"name":"conf.py","path":"doc/conf.py","contentType":"file"},{"name":"development.md","path":"doc/development.md","contentType":"file"},{"name":"glossary.rst","path":"doc/glossary.rst","contentType":"file"},{"name":"index.rst","path":"doc/index.rst","contentType":"file"},{"name":"installation.rst","path":"doc/installation.rst","contentType":"file"},{"name":"make.bat","path":"doc/make.bat","contentType":"file"},{"name":"redirects.yaml","path":"doc/redirects.yaml","contentType":"file"},{"name":"upgrading.rst","path":"doc/upgrading.rst","contentType":"file"}],"totalCount":16},"":{"items":[{"name":".github","path":".github","contentType":"directory"},{"name":"bin","path":"bin","contentType":"directory"},{"name":"devel","path":"devel","contentType":"directory"},{"name":"doc","path":"doc","contentType":"directory"},{"name":"nextstrain","path":"nextstrain","contentType":"directory"},{"name":"tests","path":"tests","contentType":"directory"},{"name":".flake8","path":".flake8","contentType":"file"},{"name":".gitignore","path":".gitignore","contentType":"file"},{"name":".readthedocs.yml","path":".readthedocs.yml","contentType":"file"},{"name":"CHANGES.md","path":"CHANGES.md","contentType":"file"},{"name":"LICENSE","path":"LICENSE","contentType":"file"},{"name":"LICENSE.cognito-srp","path":"LICENSE.cognito-srp","contentType":"file"},{"name":"LICENSE.id3c","path":"LICENSE.id3c","contentType":"file"},{"name":"LICENSE.sphinx","path":"LICENSE.sphinx","contentType":"file"},{"name":"MANIFEST.in","path":"MANIFEST.in","contentType":"file"},{"name":"README.md","path":"README.md","contentType":"file"},{"name":"mypy.ini","path":"mypy.ini","contentType":"file"},{"name":"pyoxidizer.bzl","path":"pyoxidizer.bzl","contentType":"file"},{"name":"pyproject.toml","path":"pyproject.toml","contentType":"file"},{"name":"pyrightconfig.json","path":"pyrightconfig.json","contentType":"file"},{"name":"pytest.ini","path":"pytest.ini","contentType":"file"},{"name":"setup.py","path":"setup.py","contentType":"file"}],"totalCount":22}},"fileTreeProcessingTime":7.565237,"foldersToFetch":[],"repo":{"id":139047738,"defaultBranch":"master","name":"cli","ownerLogin":"nextstrain","currentUserCanPush":false,"isFork":false,"isEmpty":false,"createdAt":"2018-06-28T17:13:28.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/22159334?v=4","public":true,"private":false,"isOrgOwned":true},"symbolsExpanded":false,"treeExpanded":true,"refInfo":{"name":"master","listCacheKey":"v0:1710358402.0","canEdit":false,"refType":"branch","currentOid":"4fa53169059047454fb10b55a584395239b61a1a"},"path":"doc/development.md","currentUser":null,"blob":{"rawLines":null,"stylingDirectives":null,"colorizedLines":null,"csv":null,"csvError":null,"dependabotInfo":{"showConfigurationBanner":false,"configFilePath":null,"networkDependabotPath":"/nextstrain/cli/network/updates","dismissConfigurationNoticePath":"/settings/dismiss-notice/dependabot_configuration_notice","configurationNoticeDismissed":null},"displayName":"development.md","displayUrl":"https://github.com/nextstrain/cli/blob/master/doc/development.md?raw=true","headerInfo":{"blobSize":"8.72 KB","deleteTooltip":"You must be signed in to make or propose changes","editTooltip":"You must be signed in to make or propose changes","ghDesktopPath":"https://desktop.github.com","isGitLfs":false,"onBranch":true,"shortPath":"33a4208","siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2Fnextstrain%2Fcli%2Fblob%2Fmaster%2Fdoc%2Fdevelopment.md","isCSV":false,"isRichtext":true,"toc":[{"level":1,"text":"Nextstrain CLI Development","anchor":"nextstrain-cli-development","htmlText":"Nextstrain CLI Development"},{"level":2,"text":"Setup","anchor":"setup","htmlText":"Setup"},{"level":2,"text":"Running with local changes","anchor":"running-with-local-changes","htmlText":"Running with local changes"},{"level":2,"text":"External resources","anchor":"external-resources","htmlText":"External resources"},{"level":2,"text":"Releasing","anchor":"releasing","htmlText":"Releasing"},{"level":3,"text":"Recovering from release CI failures","anchor":"recovering-from-release-ci-failures","htmlText":"Recovering from release CI failures"},{"level":3,"text":"Updating the Bioconda recipe","anchor":"updating-the-bioconda-recipe","htmlText":"Updating the Bioconda recipe"},{"level":2,"text":"Tests","anchor":"tests","htmlText":"Tests"},{"level":2,"text":"Type annotations and static analysis","anchor":"type-annotations-and-static-analysis","htmlText":"Type annotations and static analysis"},{"level":2,"text":"Documentation","anchor":"documentation","htmlText":"Documentation"}],"lineInfo":{"truncatedLoc":"222","truncatedSloc":"156"},"mode":"file"},"image":false,"isCodeownersFile":null,"isPlain":false,"isValidLegacyIssueTemplate":false,"issueTemplate":null,"discussionTemplate":null,"language":"Markdown","languageID":222,"large":false,"planSupportInfo":{"repoIsFork":null,"repoOwnedByCurrentUser":null,"requestFullPath":"/nextstrain/cli/blob/master/doc/development.md","showFreeOrgGatedFeatureMessage":null,"showPlanSupportBanner":null,"upgradeDataAttributes":null,"upgradePath":null},"publishBannersInfo":{"dismissActionNoticePath":"/settings/dismiss-notice/publish_action_from_dockerfile","releasePath":"/nextstrain/cli/releases/new?marketplace=true","showPublishActionBanner":false},"rawBlobUrl":"https://github.com/nextstrain/cli/raw/master/doc/development.md","renderImageOrRaw":false,"richText":"

Nextstrain CLI Development

\n

Development of nextstrain-cli happens at https://github.com/nextstrain/cli.

\n

We currently target compatibility with Python 3.8 and higher.

\n

Versions for this project follow the Semantic Versioning rules.

\n

Setup

\n

Setup an isolated development environment in .venv/ with:

\n
./devel/setup-venv\n
\n

Activate the venv to run any of the commands below:

\n
source .venv/bin/activate\n
\n

(Or alternatively, run the commands below via ./devel/venv-run ….)

\n

Now test that you can run:

\n
nextstrain --help\n
\n

The development environment includes our dev tools (described below):

\n
pytest           # runs doctests as well as mypy and flake8\nmypy -p nextstrain.cli\nflake8\nmake -C doc livehtml\n
\n

Running with local changes

\n

The project is installed into editable mode when using the venv setup above, so\nany changes you make during development will be used automatically.

\n

If you need to run with local changes in setups without editable mode, you can\nrun ./bin/nextstrain to test your local changes without installing them.\n(Note that ./bin/nextstrain is not the script that gets installed by pip as\nnextstrain; that script is generated by the entry_points configuration in\nsetup.py.)

\n

External resources

\n

Environment variables can be used to change the location or identity of\nexternal resources/services used by some Nextstrain CLI commands. This is\nhelpful when performing manual integration testing against development or\ntesting resources.

\n

When running the nextstrain.org server locally, you can make the\nnextstrain remote family of commands talk to it instead of the real\nnextstrain.org by setting NEXTSTRAIN_DOT_ORG, e.g.:

\n
NEXTSTRAIN_DOT_ORG=http://localhost:5000 \\\n  nextstrain remote ls nextstrain.org/groups/test\n
\n

If you need authenticated access and the local server is using the AWS Cognito\nresources from our \"testing\" configuration, you can configure nextstrain\nwith the same, e.g.:

\n
export NEXTSTRAIN_DOT_ORG=http://localhost:5000\n\nnextstrain login\nnextstrain whoami\nnextstrain remote ls groups/test-private\n
\n

Most of the times the above is not necessary, however, as you can specify the\nlocal remote explicitly instead of pretending it's nextstrain.org, e.g.:

\n
nextstrain remote ls http://localhost:5000/groups/test\n\nnextstrain login http://localhost:5000\nnextstrain whoami http://localhost:5000\nnextstrain remote ls http://localhost:5000/groups/test-private\n
\n

Setting NEXTSTRAIN_DOT_ORG is mostly useful when testing the default-remote\ncode paths themselves.

\n

Releasing

\n

New releases are made frequently and tagged in git using a signed tag.\nThere is a ./devel/release script which will prepare a new release from your\nlocal repository. It ends with instructions for you on how to push the release\ncommit/tag.

\n

When a release tag is pushed, the CI workflow builds source\ndistributions and built distributions (wheels), tests them, and if tests\npass, uploads them to the nextstrain-cli project on\nPyPi.

\n

Recovering from release CI failures

\n

When CI for a release fails due to transient errors (like DNS or other network\nissues), it can be recovered by retrying the GitHub Action workflow run.

\n

However, when CI for a release fails due to non-transient errors that require a\ncode change, the recovery method is to cut a new release. If only non-packaged\nchanges were made (e.g. fixes to the CI process itself but not in\nnextstrain), then bump the version to a post-release version (e.g.\nX.Y.Z.postN). Otherwise, use an appropriate semantic version bump. Update\nthe changelog to note the unreleased version as such and that the new version\ncontains all the changes from the unreleased version. You can compare to\nprevious unreleased versions for wording.

\n

Two examples of recovering from a failed release:

\n\n

Updating the Bioconda recipe

\n

Typically the autobump PR created by BiocondaBot when it notices the PyPI\nrelease will be sufficient. Look it over and comment:

\n
\n

@BiocondaBot please add label

\n
\n

to get it reviewed and merged.

\n

However, if there are changes to the minimum Python version or dependency list in setup.py, then\ncreate a PR in bioconda-recipes translating the changes in setup.py to\nConda package match specifications.\nFollow instructions at nextstrain/bioconda-recipes/README.md.\nYou'll need to ask that the autobump PR is closed in favor of your own PR.

\n

Tests

\n

Tests are run with pytest. To run everything, use:

\n
./devel/pytest\n
\n

This includes the type annotation and static analysis checks described below.

\n

Type annotations and static analysis

\n

Our goal is to gradually add type annotations to our code so that we can\ncatch errors earlier and be explicit about the interfaces expected and\nprovided. Annotation pairs well with the functional approach taken by the\npackage.

\n

During development you can run static type checks using mypy:

\n
$ mypy nextstrain\n# No output is good!\n
\n

and pyright:

\n
$ npx pyright\n...\nFound 40 source files\n0 errors, 0 warnings, 0 infos\nCompleted in 2sec\n
\n

There are also many editor integrations for mypy, and Pyright is integrated\ninto VS Code's Python support.

\n

The typing_extensions module should be used for features not yet available\nin the the standard typings module of supported Python versions.

\n

We also use Flake8 for some static analysis checks focusing on runtime\nsafety and correctness. You can run them like this:

\n
$ flake8\n# No output is good!\n
\n

Documentation

\n

A Sphinx project for Nextstrain CLI's documentation, primarily reference and\nexplanatory material, lives in the doc/\ndirectory. Pages are written in reStructuredText (rST), though some older\npages originally written in Markdown haven't been converted yet.

\n

To locally build the HTML version of the documentation (i.e. what's served at\nhttps://docs.nextstrain.org/projects/cli/), run:

\n
$ make -C doc livehtml\n
\n

This will start a server on http://localhost:8000 which you can browse.\nChanges to source documentation files will be reflected automatically.

\n

The rST source documentation files for the individual nextstrain commands are\ngenerated from the Python source files and checked into version control.\nChanges should be kept in sync by regenerating with:

\n
$ ./devel/generate-command-doc\n
\n

This will emit the file paths it updates, if any. This also happens\nautomatically as part of the documentation build process to ensure the built\ndocs always reflect the current source. Tests run in CI ensure the generated\nfiles are current and don't vary based on ambient environmental factors.

\n

The devel/generate-command-doc program also has --diff and --check modes\nwhich may be helpful during development; see --help for details.

\n
","renderedFileInfo":null,"shortPath":null,"symbolsEnabled":true,"tabSize":8,"topBannersInfo":{"overridingGlobalFundingFile":false,"globalPreferredFundingPath":null,"showInvalidCitationWarning":false,"citationHelpUrl":"https://docs.github.com/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-citation-files","actionsOnboardingTip":null},"truncated":false,"viewable":true,"workflowRedirectUrl":null,"symbols":{"timed_out":false,"not_analyzed":false,"symbols":[{"name":"Nextstrain CLI Development","kind":"section_1","ident_start":2,"ident_end":28,"extent_start":0,"extent_end":8925,"fully_qualified_name":"Nextstrain CLI Development","ident_utf16":{"start":{"line_number":0,"utf16_col":2},"end":{"line_number":0,"utf16_col":28}},"extent_utf16":{"start":{"line_number":0,"utf16_col":0},"end":{"line_number":222,"utf16_col":0}}},{"name":"Setup","kind":"section_2","ident_start":246,"ident_end":251,"extent_start":243,"extent_end":744,"fully_qualified_name":"Setup","ident_utf16":{"start":{"line_number":8,"utf16_col":3},"end":{"line_number":8,"utf16_col":8}},"extent_utf16":{"start":{"line_number":8,"utf16_col":0},"end":{"line_number":31,"utf16_col":0}}},{"name":"Running with local changes","kind":"section_2","ident_start":747,"ident_end":773,"extent_start":744,"extent_end":1248,"fully_qualified_name":"Running with local changes","ident_utf16":{"start":{"line_number":31,"utf16_col":3},"end":{"line_number":31,"utf16_col":29}},"extent_utf16":{"start":{"line_number":31,"utf16_col":0},"end":{"line_number":42,"utf16_col":0}}},{"name":"External resources","kind":"section_2","ident_start":1251,"ident_end":1269,"extent_start":1248,"extent_end":2599,"fully_qualified_name":"External resources","ident_utf16":{"start":{"line_number":42,"utf16_col":3},"end":{"line_number":42,"utf16_col":21}},"extent_utf16":{"start":{"line_number":42,"utf16_col":0},"end":{"line_number":78,"utf16_col":0}}},{"name":"Releasing","kind":"section_2","ident_start":2602,"ident_end":2611,"extent_start":2599,"extent_end":5142,"fully_qualified_name":"Releasing","ident_utf16":{"start":{"line_number":78,"utf16_col":3},"end":{"line_number":78,"utf16_col":12}},"extent_utf16":{"start":{"line_number":78,"utf16_col":0},"end":{"line_number":134,"utf16_col":0}}},{"name":"Recovering from release CI failures","kind":"section_3","ident_start":3113,"ident_end":3148,"extent_start":3109,"extent_end":4547,"fully_qualified_name":"Recovering from release CI failures","ident_utf16":{"start":{"line_number":90,"utf16_col":4},"end":{"line_number":90,"utf16_col":39}},"extent_utf16":{"start":{"line_number":90,"utf16_col":0},"end":{"line_number":119,"utf16_col":0}}},{"name":"Updating the Bioconda recipe","kind":"section_3","ident_start":4551,"ident_end":4579,"extent_start":4547,"extent_end":5142,"fully_qualified_name":"Updating the Bioconda recipe","ident_utf16":{"start":{"line_number":119,"utf16_col":4},"end":{"line_number":119,"utf16_col":32}},"extent_utf16":{"start":{"line_number":119,"utf16_col":0},"end":{"line_number":134,"utf16_col":0}}},{"name":"Tests","kind":"section_2","ident_start":5145,"ident_end":5150,"extent_start":5142,"extent_end":5326,"fully_qualified_name":"Tests","ident_utf16":{"start":{"line_number":134,"utf16_col":3},"end":{"line_number":134,"utf16_col":8}},"extent_utf16":{"start":{"line_number":134,"utf16_col":0},"end":{"line_number":142,"utf16_col":0}}},{"name":"Type annotations and static analysis","kind":"section_2","ident_start":5329,"ident_end":5365,"extent_start":5326,"extent_end":6271,"fully_qualified_name":"Type annotations and static analysis","ident_utf16":{"start":{"line_number":142,"utf16_col":3},"end":{"line_number":142,"utf16_col":39}},"extent_utf16":{"start":{"line_number":142,"utf16_col":0},"end":{"line_number":174,"utf16_col":0}}},{"name":"Documentation","kind":"section_2","ident_start":6274,"ident_end":6287,"extent_start":6271,"extent_end":8925,"fully_qualified_name":"Documentation","ident_utf16":{"start":{"line_number":174,"utf16_col":3},"end":{"line_number":174,"utf16_col":16}},"extent_utf16":{"start":{"line_number":174,"utf16_col":0},"end":{"line_number":222,"utf16_col":0}}}]}},"copilotInfo":null,"copilotAccessAllowed":false,"csrf_tokens":{"/nextstrain/cli/branches":{"post":"Q4iVNtzwOSimA7PJ8noyEs0oru06yWtKz_XEscHqE4jyrkVtL_NGLXC3_uwKMO7xjBYW6Ec9d0B9RBv9xua4vg"},"/repos/preferences":{"post":"khVTKkbJtb10yK5Lgls5stmxc4397s0eXwMoSQ9vg3eS3eKnImQcTD6-laO5NPdobFh8QcgsG06a5JFNZrMPvg"}}},"title":"cli/doc/development.md at master · nextstrain/cli"}