diff --git a/.github/workflows/build_one.yml b/.github/workflows/build_one.yml index 359989d20..f4f5fd115 100644 --- a/.github/workflows/build_one.yml +++ b/.github/workflows/build_one.yml @@ -40,6 +40,8 @@ jobs: conda activate base conda install conda-libmamba-solver conda config --set solver libmamba + - name: list files + run: doit build_list_existing_files:${{ inputs.project }} - name: prepare project run: doit build_prepare_project:${{ inputs.project }} - name: process notebooks @@ -49,6 +51,8 @@ jobs: name: ${{ inputs.project }} path: doc/${{ inputs.project }}/ retention-days: 3 + - name: clean project folder + run: doit clean build_list_existing_files:${{ inputs.project }} - name: deploy project # Only push to the branch when the workflow is called by pr_flow.yml -> build.yml # Could be updated to allow to build projects using worfklow_dispatch to see @@ -90,7 +94,7 @@ jobs: git push --force "https://pyviz-developers:${{ secrets.GITHUB_TOKEN }}@github.com/pyviz-topics/examples.git" HEAD:$BRANCHNAME git checkout main - name: clean up - run: doit clean --clean-dep build + run: doit clean --clean-dep build:${{ inputs.project }} - name: git diff run: git diff - name: check clean up diff --git a/.github/workflows/deploy_one.yml b/.github/workflows/deploy_one.yml index 121cf44d5..f7377d880 100644 --- a/.github/workflows/deploy_one.yml +++ b/.github/workflows/deploy_one.yml @@ -43,6 +43,8 @@ jobs: - name: create project archive run: doit doc_archive_projects --name ${{ inputs.changedproject }} --extension ".tar.bz2" - name: validate AE5 project + env: + EXAMPLES_HOLOVIZ_STRICT_DEPLOYMENT_POLICY: "true" run: doit ae5_validate_deployment --name ${{ inputs.changedproject }} - name: sync and deploy on AE5 run: doit ae5_sync_project --name ${{ inputs.changedproject }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 147b77652..9be90d14a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -7,15 +7,14 @@ ######## # Runs on: -# direct push to main: checkout `evaluated` and build main site -# merged PRs: merge `tmp_evaluated_fghgf_{PRBranchName}` into`evaluated -# ,push to `evaluated` and build main site -# workflow_call (from pr_flow.yml): +# - merged PRs: merge `tmp_evaluated_fghgf_{PRBranchName}` into`evaluated +# ,push to `evaluated` and build main site +# - workflow_call (from pr_flow.yml): # - at least one project built: pull the `tmp_evaluated_fghgf_{PRBranchName}` # and `evaluated` branches, merge locally (NOT pushed), and build the dev site # - no project built, checkout `evaluated` and build main site -# workflow_dispatch: checkout `evaluated` and build main or dev site -# schedule: checkout `evaluated`, don't build any site +# - workflow_dispatch: checkout `evaluated` and build main or dev site +# - schedule: checkout `evaluated`, don't build any site name: docs @@ -96,10 +95,6 @@ jobs: if [ "${{ inputs.type }}" == "workflow_call" ]; then NAME="${{ inputs.evaluated_branch }}" TARGET="dev" - # Push the main, use the evaluated branch - elif [ "$EVENTNAME" == "push" ]; then - NAME="evaluated" - TARGET="main" # Merged PRs, use the evaluated branch elif [ "$EVENTNAME" == "pull_request" ]; then NAME="tmp_evaluated_fghgf_${{ github.event.pull_request.head.ref }}" @@ -109,7 +104,8 @@ jobs: TARGET="${{ inputs.target }}" elif [ "$EVENTNAME" == "schedule" ]; then NAME="evaluated" - TARGET="dev" + # schedule events do not deploy a dev site, they're dry run + TARGET="" fi echo "Evaluated branch: $NAME" echo "Target: $TARGET" @@ -282,9 +278,6 @@ jobs: fi - name: archive projects run: | - ls - cat dodo.py - doit list doit doc_archive_projects - name: move thumbnails run: doit doc_move_thumbnails @@ -292,7 +285,13 @@ jobs: run: doit doc_move_assets - name: "temp: remove non evaluated projects" run: doit doc_remove_not_evaluated - - name: build website + - name: build dev website + if: steps.set-vars.outputs.target == 'dev' + env: + EXAMPLES_HOLOVIZ_DEV_SITE: 'true' + run: doit doc_build_website + - name: build main website + if: steps.set-vars.outputs.target == 'main' run: doit doc_build_website - name: build index redirects run: doit doc_index_redirects @@ -314,9 +313,7 @@ jobs: - name: Deploy dev # workflow_call, by pr_flow.yml # workflow_dispatch and dev target - if: | - (github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'dev') || - inputs.type == 'workflow_call' + if: steps.set-vars.outputs.target == 'dev' uses: peaceiris/actions-gh-pages@v3 with: personal_token: ${{ secrets.ACCESS_TOKEN }} @@ -326,11 +323,7 @@ jobs: - name: Deploy main # merged PR # workflow_dispatch and main target - # push to main (check inputs.type as workflow_call passes its context and it's push) - if: | - github.event.pull_request.merged == true || - (github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'main') || - (github.event_name == 'push' && inputs.type != 'workflow_call') + if: steps.set-vars.outputs.target == 'main' run: echo Deploy main # TODO: uncomment this # uses: peaceiris/actions-gh-pages@v3 @@ -340,7 +333,7 @@ jobs: # cname: examples.pyviz.org # force_orphan: true - name: Clean up - run: doit clean --clean-dep doc + run: doit clean --clean-dep doc_full - name: debug run: | ls -hla diff --git a/.github/workflows/test_one.yml b/.github/workflows/test_one.yml index 6677f6cc7..2ef75703b 100644 --- a/.github/workflows/test_one.yml +++ b/.github/workflows/test_one.yml @@ -48,7 +48,7 @@ jobs: - name: install nbval # TODO: latest nbval not yet available on conda-forge # when it is, remove this and add it to the lock file - run: pip install nbval >= 0.10.0 + run: pip install "nbval >= 0.10.0" - name: validate anaconda-project file run: doit validate_project_file:${{ inputs.project }} - name: validate anaconda-project lock file diff --git a/.gitignore b/.gitignore index 2d6bbc70d..1dd7e89a6 100644 --- a/.gitignore +++ b/.gitignore @@ -52,5 +52,8 @@ builtdocs/ # Ignore output of test_small_data_setup */tmp_catalog.yml +# Ignore output of build_list_existing_files +.examples_snapshot + # dot env file to store env vars .env diff --git a/doc/conf.py b/doc/conf.py index 578daeda7..a74b9ec74 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -133,6 +133,7 @@ def gallery_spec(name): last_updated = examples_config.get('last_updated', '') if not last_updated: last_updated = last_commit_date(name, root='..', verbose=False) + title = examples_config.get('title', '') or projname_to_title(spec['name']) # Default is empty string as deployments is injected into PROLOG_TEMPLATE deployments = examples_config.get('deployments', '') @@ -152,7 +153,7 @@ def gallery_spec(name): # filename where the metadata prolog is injected. endpoint += '/notebooks/{template_notebook_filename}' elif depl['command'] == 'dashboard': - text = 'Open app' + text = 'Open app(s)' material_icon = 'dashboard' endpoint = deployment_cmd_to_endpoint(depl['command'], name) formatted_depl = DEPLOYMENT_TEMPLATE.format( diff --git a/dodo.py b/dodo.py index 4b73fb404..961a77984 100644 --- a/dodo.py +++ b/dodo.py @@ -31,12 +31,14 @@ DEFAULT_DOC_EXCLUDE = [ '_static', '_templates', - # We don't want to include the template project in the website - # but we want to include it in all the other parts as it is tested - # and deployed. + # We don't want to include the template project in the main website 'template', ] +# But it's included on the dev site if this env var is set. +if os.getenv('EXAMPLES_HOLOVIZ_DEV_SITE') is not None: + DEFAULT_DOC_EXCLUDE.remove('template') + DEFAULT_SKIP_NOTEBOOKS_EVALUATION = False DEFAULT_NO_DATA_INGESTION = False DEFAULT_DEPLOYMENTS_AUTO_DEPLOY = True @@ -329,7 +331,7 @@ def project_has_test_data(name): path = pathlib.Path('test_data') / name if not path.is_dir(): return False - has_files = not any(path.iterdir()) + has_files = any(path.iterdir()) return has_files @@ -379,6 +381,25 @@ def removing_files(paths, verbose=True): path.unlink() +def run_fast_scandir(dir): + """ + Traverse the filesystem, ignoring /envs and .examples_snapshot + """ + subfolders, files = [], [] + + for f in os.scandir(dir): + if f.is_dir() and f.name != 'envs': + subfolders.append(f.path) + if f.is_file() and f.name != '.examples_snapshot': + files.append(f.path) + + for dir in list(subfolders): + sf, f = run_fast_scandir(dir) + subfolders.extend(sf) + files.extend(f) + return subfolders, files + + def _prepare_paths(root, name, test_data, filename='catalog.yml'): """ Return a dict of paths, useful to deal with the test data. @@ -807,6 +828,8 @@ def validate_project_file(name): except YAMLError as e: raise YAMLError('invalid file content') from e + user_config = spec.get('examples_config', []) + root_required = [ 'name', 'description', @@ -849,6 +872,17 @@ def validate_project_file(name): 'Linting is done by the system and should not be defined on ' 'a per-project basis, please remove the `lint` command.' ) + # Seems like defining the lint/test commands with -k *.ipynb was + # actually ignoring all the notebooks when there was more than one. + for cmd in ('test', 'lint'): + cmd_spec = commands.get('cmd', {}) + for target in ('unix', 'windows'): + cmd_string = cmd_spec.get(target, '') + if '-k *.ipynb' in cmd_string: + suggestion = '-k ".ipynb"' + complain( + f"Replace '-k *.ipynb' by '{suggestion}' in command {command}/{target}" + ) for cmd, cmd_spec in commands.items(): if 'notebook' in cmd_spec and cmd != 'notebook': @@ -861,15 +895,15 @@ def validate_project_file(name): complain( f'Command serving Panel/Lumen apps must be called `dashboard`, not {cmd!r}', ) - if ('-rest-session-info' not in cmd_spec['unix'] and - '--session-history -1' not in cmd_spec['unix']): + if ( + any(depl.get('command') == 'dashboard' for depl in user_config.get('deployments', [])) and + ('-rest-session-info' not in cmd_spec['unix'] or '--session-history -1' not in cmd_spec['unix']) + ): complain( 'Command serving Panel/Lumen apps must set "-rest-session-info --session-history -1"', ) env_specs = spec.get('env_specs', {}) - if not 'default' in env_specs: - complain('missing "default" env_spec') if 'test' in env_specs: complain( 'Found a "test" env_spec, are you sure you need it? If so ' @@ -882,15 +916,13 @@ def validate_project_file(name): if user_fields != ['examples_config']: complain('`user_fields` must be [examples_config]') - config = spec.get('examples_config', []) - # Validating maintainers and labels expected = ['maintainers', 'labels'] for entry in expected: - if entry not in config: + if entry not in user_config: complain(f'missing {entry!r} list') continue - value = config[entry] + value = user_config[entry] if not isinstance(value, list): complain(f'{entry!r} must be a list') if not all(isinstance(item, str) for item in value): @@ -903,7 +935,7 @@ def validate_project_file(name): complain(f'missing {label}.svg file in doc/_static/labels') # Validating created - created = config.get('created') + created = user_config.get('created') if created: if not isinstance(created, datetime.date): complain('`created` value must be a date expressed as YYYY-MM-DD') @@ -911,12 +943,17 @@ def validate_project_file(name): complain('`created` entry not found') # Validating last_updated - last_updated = config.get('last_updated', '') + last_updated = user_config.get('last_updated', '') if last_updated and not isinstance(last_updated, datetime.date): complain('`last_updated` value must be a date expressed as YYYY-MM-DD') + # Validating last_updated + title = user_config.get('title', '') + if title and not isinstance(title, str): + complain('`title` value must be a string') + # Validating deployments - deployments = config.get('deployments') + deployments = user_config.get('deployments') if deployments: if not isinstance(deployments, list): complain('`deployments` must be a list') @@ -944,26 +981,24 @@ def validate_project_file(name): complain(f'`auto_deploy` must be a boolean, not {auto_deploy}') # Validating skip_notebooks_evaluation - skip_notebooks_evaluation = config.get('skip_notebooks_evaluation', None) + skip_notebooks_evaluation = user_config.get('skip_notebooks_evaluation', None) if skip_notebooks_evaluation is not None and not isinstance(skip_notebooks_evaluation, bool): complain(f'`skip_notebooks_evaluation` must be a boolean, not {skip_notebooks_evaluation}') # Validating no_data_ingestion - no_data_ingestion = config.get('no_data_ingestion', None) + no_data_ingestion = user_config.get('no_data_ingestion', None) if no_data_ingestion is not None and not isinstance(no_data_ingestion, bool): complain(f'`no_data_ingestion` must be a boolean, not {no_data_ingestion}') required_config = ['created', 'maintainers', 'labels'] optional_config = [ 'last_updated', 'deployments', 'skip_notebooks_evaluation', - 'no_data_ingestion' + 'no_data_ingestion', 'title' ] - for key in config: + for key in user_config: if key not in required_config + optional_config: complain(f'Unexpected entry {key!r} found in `examples_config`') - # TODO: title entry? - for name in all_project_names(root=''): yield { 'name': name, @@ -974,7 +1009,9 @@ def task_validate_project_lock(): """Validate the existence of the anaconda-project-lock.yml file""" def validate_project_lock(name): + import anaconda_project.internal.conda_api as conda_api from anaconda_project.project import Project + from anaconda_project.project_lock_file import ProjectLockFile with removing_files([pathlib.Path(name, '.projectignore')], verbose=False): project = Project(directory_path=name, must_exist=True) @@ -991,6 +1028,46 @@ def validate_project_lock(name): f"Env spec '{env_spec_name}' has changed since the lock file was last updated." ) + if env_spec.platforms != env_spec.lock_set.platforms: + if len(env_spec.lock_set.platforms) == 0: + text = "Env spec '%s' specifies platforms '%s' but the lock file lists no platforms for it" % ( + env_spec.name, ",".join(env_spec.platforms)) + else: + text = ("Env spec '%s' specifies platforms '%s' but the lock file has " + + "locked versions for platforms '%s'") % (env_spec.name, ",".join( + env_spec.platforms), ",".join(env_spec.lock_set.platforms)) + complain(text) + + if len(env_spec.conda_packages) > 0: + for platform in env_spec.lock_set.platforms: + conda_packages = env_spec.lock_set.package_specs_for_platform(platform) + if len(conda_packages) == 0: + text = ("Lock file lists no packages for env spec '%s' on platform %s") % (env_spec.name, + platform) + complain(text) + else: + # If conda ever had RPM-like "Obsoletes" then this situation _may_ happen + # in correct scenarios. + lock_set_names = set() + for package in conda_packages: + parsed = conda_api.parse_spec(package) + if parsed is not None: + lock_set_names.add(parsed.name) + unlocked_names = env_spec.conda_package_names_set - lock_set_names + if len(unlocked_names) > 0: + text = "Lock file is missing %s packages for env spec %s on %s (%s)" % ( + len(unlocked_names), env_spec.name, platform, ",".join(sorted(list(unlocked_names)))) + complain(text) + + # Look for lock sets that don't go with an env spec + lock_file = ProjectLockFile.load_for_directory(project.directory_path) + lock_file = project.lock_file + lock_sets = lock_file.get_value(['env_specs'], {}) + for name in lock_sets.keys(): + if name not in project.env_specs: + text = ("Lock file lists env spec '%s' which is not in %s") % (name, 'anaconda-project.yml') + complain(text) + for name in all_project_names(root=''): yield { 'name': name, @@ -1092,11 +1169,6 @@ def validate_data_sources(name): complain( '.projectignore must not ignore the "data/" folder' ) - else: - complain( - 'The project has a "data/" folder, it must have a .projectignore ' - 'file that does not ignore the "data/" folder' - ) has_explicit_source = has_downloads or has_intake_catalog or has_data_folder if has_explicit_source and has_no_data_ingestion: @@ -1215,7 +1287,7 @@ def task_validate_thumbnails(): """Validated that the project has a thumbnail and that it's correct. - size < 1MB - - 1 < aspect ratio < 1.2 + - 0.9 < aspect ratio < 1.5 """ def validate_thumbnails(name): @@ -1241,15 +1313,16 @@ def validate_thumbnails(name): for thumb in thumb_folder.glob('*.png') ): complain(f'has no PNG thumbnail for notebook {notebook.name}') + return thumb = thumb_folder / (notebook.stem + '.png') size = thumb.stat().st_size * 1e-6 if size > 1: complain(f'thumbnail size ({size:.2f} MB) is above 1MB') w, h = get_png_dims(thumb) aspect_ratio = w / h - if not (1.0 <= aspect_ratio <= 1.2): + if not (0.9 <= aspect_ratio <= 1.5): complain( - f'thumbnail aspect ratio ({aspect_ratio:.2f}) must be between 1 and 1.2', + f'thumbnail aspect ratio ({aspect_ratio:.2f}) must be between 0.9 and 1.5', ) for name in all_project_names(root=''): @@ -1450,15 +1523,18 @@ def test_notebooks(name): } else: - import nbval.plugin - - old_runtest = nbval.plugin.IPyNbCell.runtest - - def runtest(self): - self.output_timeout = 10 - old_runtest(self) - - nbval.plugin.IPyNbCell.runtest = runtest + try: + import nbval.plugin + except ImportError: + pass + else: + old_runtest = nbval.plugin.IPyNbCell.runtest + + def runtest(self): + self.output_timeout = 10 + old_runtest(self) + + nbval.plugin.IPyNbCell.runtest = runtest yield { 'name': name, @@ -1482,6 +1558,50 @@ def runtest(self): #### Build #### +def task_build_list_existing_files(): + """ + Saves the existing files paths in a file. + + Saves in .examples_snapshot all the files and folders found in the + directory, except this file and the /envs folder. + """ + + def list_existing_items(name): + subfolders, files = run_fast_scandir(name) + paths = sorted(subfolders + files) + pathlib.Path(name, '.examples_snapshot').write_text("\n".join(paths)) + + def clean(name): + envs = pathlib.Path(name, 'envs') + if envs.is_dir(): + print(f'Removing the environment folder: {envs} ...') + shutil.rmtree(envs) + fsnapshot = pathlib.Path(name, '.examples_snapshot') + if not fsnapshot.exists(): + return + before = set(fsnapshot.read_text().splitlines()) + subfolders, files = run_fast_scandir(name) + now = set(subfolders + files) + new = now - before + for p in new: + p = pathlib.Path(p) + if p.is_file(): + print(f'Removing file {p}') + p.unlink() + elif p.is_dir(): + print(f'Removing directory {p}') + shutil.rmtree(p) + print('Removing snapshot') + fsnapshot.unlink() + + for name in all_project_names(root=''): + yield { + 'name': name, + 'actions': [(list_existing_items, [name]),], + 'clean': [(clean, [name]),], + } + + def task_build_prepare_project(): """ Run `anaconda-project prepare --directory @@ -1495,8 +1615,6 @@ def task_build_prepare_project(): f'anaconda-project prepare --directory {name}', ], 'uptodate': [(should_skip_notebooks_evaluation, [name])], - # TODO: is there more to clean up? - 'clean': [f'rm -rf {name}/envs'], } @@ -1550,6 +1668,13 @@ def run_notebooks(name): dir_name=name, ) + def clean_notebooks(name): + folder = pathlib.Path('doc', name) + if not folder.is_dir(): + return + print(f'Removing all from {folder}') + shutil.rmtree(folder) + def copy_notebooks(name): """ Copy notebooks from the project folder to the doc/{name} folder. @@ -1589,8 +1714,7 @@ def copy_notebooks(name): 'name': name, 'actions': actions, 'teardown': teardown, - # TODO - 'clean': [f'git clean -fxd doc/{name}'], + 'clean': [(clean_notebooks, [name]),], } @@ -1637,9 +1761,9 @@ def _archive_project(project, extension): spec.pop('user_fields', '') # commands and envs that users don't need - spec['commands'].pop('test', '') - spec['commands'].pop('lint', '') - spec['env_specs'].pop('test', '') + spec.get('commands', {}).pop('test', '') + spec.get('commands', {}).pop('lint', '') + spec.get('env_specs', {}).pop('test', '') # get rid of any empty fields spec = {k: v for k, v in spec.items() if bool(v)} @@ -2092,8 +2216,8 @@ def validate_deployment(name, hostname, username, password, admin_username, admi # Need an ADMIN account to get the list of ALL the deployments # to check that the project to update/add will not try to use # an endpoint already used by another project on the AE5 instance. - session = ae5_session(hostname, admin_username, admin_password, admin=True) - if not session: + admin_session = ae5_session(hostname, admin_username, admin_password, admin=True) + if not admin_session: complain('AE5 Admin Session could not be initialized', level='INFO') return @@ -2104,7 +2228,7 @@ def validate_deployment(name, hostname, username, password, admin_username, admi return # check no other project use one of the planned endpoints - all_deployments = list_ae5_deployments(session) + all_deployments = list_ae5_deployments(admin_session) uname = username or os.getenv(AE5_CREDENTIALS_ENV_VARS['non-admin']['username']) for deployment in all_deployments: # this is the project we aim to update, skip. @@ -2112,15 +2236,22 @@ def validate_deployment(name, hostname, username, password, admin_username, admi continue depl_endpoint = deployment['endpoint'] + # Will warn if the env var is set and this endpoint is already used + # on the instance by another project. if depl_endpoint in expected_endpoints: + if os.getenv('EXAMPLES_HOLOVIZ_STRICT_DEPLOYMENT_POLICY') is not None: + level = 'WARNING' + else: + level = 'INFO' complain( f'Endpoint {deployment["url"]!r} already used by project ' f'{deployment["project_name"]!r}. Ask a maintainer if ' - f'it can be stopped, if not, rename your project. \n\n{deployment!r}\n' + f'it can be stopped, if not, rename your project. \n\n{deployment!r}\n', + level=level ) # Switch to the user session - del session, all_deployments + del admin_session, all_deployments session = ae5_session(hostname, username, password) if not session: complain('AE5 Session could not be initialized', level='INFO') @@ -2346,14 +2477,42 @@ def task_build(): 'name': name, 'actions': None, 'task_dep': [ + f'build_list_existing_files:{name}', f'build_prepare_project:{name}', f'build_process_notebooks:{name}', ] } -def task_doc(): + +def task_doc_project(): + """ + Build the doc for a single project (doit doc_project --name ) + + Run the following command to clean the outputs: + doit clean doc_project + """ + return { + 'actions': [ + 'doit doc_archive_projects --name %(name)s', + 'doit doc_move_thumbnails --name %(name)s', + 'doit doc_move_assets --name %(name)s', + 'doit doc_build_website', + 'doit doc_index_redirects', + ], + 'clean': [ + 'doit clean doc_archive_projects', + 'doit clean doc_move_thumbnails', + 'doit clean doc_move_assets', + 'doit clean doc_build_website', + 'doit clean doc_index_redirects', + ], + 'params': [name_param], + } + + +def task_doc_full(): """ - Build the doc (doit doc) + Build the full doc (doit doc) Run the following command to clean the outputs: doit clean --clean-dep doc diff --git a/envs/environment-linux-64.lock b/envs/environment-linux-64.lock index 46b361149..736b74945 100644 --- a/envs/environment-linux-64.lock +++ b/envs/environment-linux-64.lock @@ -1,10 +1,10 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 61fb427fb0e85c9e66770959dc2afe372f4bcbc4c2f2926ebd60e43046364f49 +# input_hash: caee40e8d890385205cc8145f4db6492c18bbc7abcc2d1eb2809655c435e0fd4 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.12.7-ha878542_0.conda#ff9f73d45c4a07d6f424495288a26080 -https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.39-hcc3a1bd_1.conda#737be0d34c22d24432049ab7a3214de4 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda#7aca3059a1729aa76c597603f10b0dd3 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2#164b4b1acaedc47ee7e658ae6b308ca3 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-3_cp310.conda#4eb33d14d794b0f4be116443ffed3853 @@ -25,9 +25,9 @@ https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.18-h36c2ea0_1.tar. https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 -https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 +https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda#318b08df404f9c9be5712aaa5a6f0bb0 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 -https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.7-h0b41bf4_1.conda#7adaac6ff98219bcb99b45e408b80f4e +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.7-h0b41bf4_2.conda#45758f4ece9c8b7b5f99328bd5caae51 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.9-h7f98852_0.tar.bz2#bf6f803a544f26ebbdc3bfff272eb179 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2#be93aabceefa2fac576e971aef407908 @@ -66,10 +66,10 @@ https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-hfd0df8a_1.conda#c256 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/linux-64/lz4-4.2.0-py310h0cfdcf0_0.conda#5ee9fdbb5a4a8f426fbcf7f7e10b931f https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.2-py310h1fa729e_0.conda#a1f0db6709778b77b5903541eeac4032 +https://conda.anaconda.org/conda-forge/noarch/mccabe-0.7.0-pyhd8ed1ab_0.tar.bz2#34fc335fc50eef0b5ea708f2b5f54e0c https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.0.4-py310hbf28c38_1.tar.bz2#1fa34c9e9be72b7e4c3c9b95017463a3 https://conda.anaconda.org/conda-forge/linux-64/multidict-6.0.4-py310h1fa729e_0.conda#b33287be963a70f8fb4b143b4561ba62 -https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.5.6-pyhd8ed1ab_0.tar.bz2#7b868f21adde0d9b8b38f9c16836589b -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.1-py310h08bbf29_0.conda#0d1f2e988c8810be90ffe441a303090a +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.1-py310h8deb116_0.conda#c532c5df0bef4d138b2b0bdde99ab53e https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-hfec8fc6_2.conda#5ce6a42505c6e9e6151c54c3ec8d68ea https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b https://conda.anaconda.org/conda-forge/noarch/parso-0.8.3-pyhd8ed1ab_0.tar.bz2#17a565a0c3899244e938cdf417e7b094 @@ -78,7 +78,9 @@ https://conda.anaconda.org/conda-forge/noarch/pkgutil-resolve-name-1.3.10-pyhd8e https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py310h5764c6d_0.tar.bz2#c3c55664e9becc48e6a652e2b641961f https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd3deb0d_0.tar.bz2#359eeb6536da0e687af562ed265ec263 https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.2-pyhd8ed1ab_0.tar.bz2#6784285c7e55cb7212efabc79e4c2883 +https://conda.anaconda.org/conda-forge/noarch/pycodestyle-2.10.0-pyhd8ed1ab_0.conda#89843e4cc99c6a3fe5f4c86994cc8410 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff +https://conda.anaconda.org/conda-forge/noarch/pyflakes-3.0.1-pyhd8ed1ab_0.conda#44b7d77d96560c93e0e11437a3c35254 https://conda.anaconda.org/conda-forge/linux-64/pyrsistent-0.19.3-py310h1fa729e_0.conda#f732bec05ecc2e302a868d971ae484e0 https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py310hff52083_5.tar.bz2#378f2260e871f3ea46c6fa58d9f05277 https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.16.2-pyhd8ed1ab_0.tar.bz2#5fe4b6002f505336734ce92961b3e6a0 @@ -94,10 +96,10 @@ https://conda.anaconda.org/conda-forge/noarch/tokenize-rt-5.0.0-pyhd8ed1ab_0.tar https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_1.tar.bz2#be4a201ac582c11d89ed7d15b3157cc3 -https://conda.anaconda.org/conda-forge/noarch/traitlets-5.8.1-pyhd8ed1ab_0.conda#ef5179bcc6a9acc23b12ac92936e05ab +https://conda.anaconda.org/conda-forge/noarch/traitlets-5.9.0-pyhd8ed1ab_0.conda#d0b4f5c87cd35ac3fb3d47b223263a64 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2#c829cfb8cb826acb9de0ac1a2df0a940 -https://conda.anaconda.org/conda-forge/noarch/zipp-3.11.0-pyhd8ed1ab_0.conda#09b5b885341697137879a4f039a9e5a1 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.12.0-pyhd8ed1ab_0.conda#edc3568566cc48335f0b5d86d40fdbb9 https://conda.anaconda.org/conda-forge/noarch/aiosignal-1.3.1-pyhd8ed1ab_0.tar.bz2#d1e1eb7e21a9e2c74279d87dafb68156 https://conda.anaconda.org/conda-forge/noarch/asttokens-2.2.1-pyhd8ed1ab_0.conda#bf7f54dd0f25c3f06ecb82a07341841a https://conda.anaconda.org/conda-forge/noarch/backports.functools_lru_cache-1.6.4-pyhd8ed1ab_0.tar.bz2#c5b3edc62d6309088f4970b3eaaa65a6 @@ -105,6 +107,7 @@ https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_3.cond https://conda.anaconda.org/conda-forge/noarch/clyent-1.2.2-py_1.tar.bz2#b9ee3fdf59f49883497741509ea364b6 https://conda.anaconda.org/conda-forge/noarch/conda-pack-0.7.0-pyh6c4a22f_0.tar.bz2#84b284cebf2a158306653f9a6961d589 https://conda.anaconda.org/conda-forge/linux-64/cytoolz-0.12.0-py310h5764c6d_1.tar.bz2#fd18cd597d23b2b5ddde23bd5b7aec32 +https://conda.anaconda.org/conda-forge/noarch/flake8-6.0.0-pyhd8ed1ab_0.conda#e9345ba05d71742412b8aa6992ad9457 https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.0.0-pyha770c72_0.conda#691644becbcdca9f73243450b1c63e62 https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.10.2-pyhd8ed1ab_0.conda#de76905f801c22fc43e624058574eab3 https://conda.anaconda.org/conda-forge/noarch/jedi-0.18.2-pyhd8ed1ab_0.conda#b5e695ef9c3f0d27d6cd96bf5adc9e07 @@ -113,7 +116,7 @@ https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.6-pyhd8ed1ab https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/noarch/pexpect-4.8.0-pyh1a96a4e_2.tar.bz2#330448ce4403cc74990ac07c555942a1 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py310h4927cde_0.conda#66366aceea767f174f4d0408f3a62812 -https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d +https://conda.anaconda.org/conda-forge/noarch/pip-23.0-pyhd8ed1ab_0.conda#85b35999162ec95f9f999bac15279c02 https://conda.anaconda.org/conda-forge/noarch/pygments-2.14.0-pyhd8ed1ab_0.conda#c78cd16b11cd6a295484bd6c8f24bea1 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/noarch/python-dotenv-0.21.1-pyhd8ed1ab_0.conda#4ff7cfa0a3340c218990a95b9632301a @@ -125,8 +128,9 @@ https://conda.anaconda.org/conda-forge/noarch/async-timeout-4.0.2-pyhd8ed1ab_0.t https://conda.anaconda.org/conda-forge/linux-64/bokeh-2.4.3-py310hff52083_0.tar.bz2#31d18e459652472ee01e6985e21fca2c https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1005.tar.bz2#87669c3468dff637bbd0363bc0f895cf https://conda.anaconda.org/conda-forge/linux-64/cryptography-39.0.0-py310h34c0648_0.conda#af4b0c22dc4006ce3c095e840cb2efd7 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2023.1.0-pyhd8ed1ab_0.conda#c0ed29fe7454e551b7328024af21426f +https://conda.anaconda.org/conda-forge/noarch/dask-core-2023.1.1-pyhd8ed1ab_0.conda#025c84e82996ab0a01c0af0069399192 https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_0.tar.bz2#fc5e53d070f1ee7bb38c2ece282dcb82 +https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-6.0.0-hd8ed1ab_0.conda#a67d43e1527a37199dd8db913366f68e https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.17.3-pyhd8ed1ab_0.conda#723268a468177cd44568eb8f794e0d80 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.3-py310h9b08913_0.conda#467244b0dbb7da40927ac6ee0e9491de https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.2-pyhd8ed1ab_0.conda#0b4cc3f8181b0d8446eb5387d7848a54 @@ -135,22 +139,22 @@ https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.6-pyhd8ed1ab_0.conda#0 https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.19.0-py310hdeb6495_1.conda#2cce1a48e6687f64d371d2e7fc9c7fbf https://conda.anaconda.org/conda-forge/linux-64/aiohttp-3.8.3-py310h5764c6d_1.tar.bz2#7212b99b48dcdb1d384078d772227130 https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.7.0-pyhd8ed1ab_1.conda#1a2fa9e53cfbc2e4d9ab21990805a436 -https://conda.anaconda.org/conda-forge/linux-64/jupyter_core-5.1.5-py310hff52083_0.conda#0686cb6c84b6805eb0be38176dc7f2fa +https://conda.anaconda.org/conda-forge/linux-64/jupyter_core-5.2.0-py310hff52083_0.conda#fe002e7c5030e7baec9e0f9a6cdbe15e https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.36-pyha770c72_0.conda#4d79ec192e0bfd530a254006d123b9a6 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878 https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.0.2-pyh38be061_0.conda#44800e9bd13143292097c65e57323038 -https://conda.anaconda.org/conda-forge/noarch/ipython-8.8.0-pyh41d4057_0.conda#788c234d910ad9f37174d1845c1a52c7 -https://conda.anaconda.org/conda-forge/noarch/jupyter_client-7.4.9-pyhd8ed1ab_0.conda#5cbf9a31a19d4ef9103adb7d71fd45fd +https://conda.anaconda.org/conda-forge/noarch/ipython-8.9.0-pyh41d4057_0.conda#399217b9b00e59e990585576eeca3dde +https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.0.2-pyhd8ed1ab_0.conda#cbb8d182b6053ce14b5fe60ef1e36fbb https://conda.anaconda.org/conda-forge/noarch/nbformat-5.7.3-pyhd8ed1ab_0.conda#9714111cb6c7dbbc9a9f34de205c2f29 https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.36-hd8ed1ab_0.conda#482c15eb65dde2f899c4d68eaa938b1d https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29 https://conda.anaconda.org/conda-forge/noarch/click-repl-0.2.0-pyhd8ed1ab_0.tar.bz2#e18a67c7a7808b74df0ded854ffe672b -https://conda.anaconda.org/conda-forge/noarch/distributed-2023.1.0-pyhd8ed1ab_0.conda#5b649ff28d29ec9330a72a2c27f0f599 +https://conda.anaconda.org/conda-forge/noarch/distributed-2023.1.1-pyhd8ed1ab_0.conda#14a63c4d353c6200fad2fd9f48b9647a https://conda.anaconda.org/conda-forge/noarch/nbclient-0.7.2-pyhd8ed1ab_0.conda#6c7b0d75b66a220274bb5a28c23197f2 https://conda.anaconda.org/conda-forge/noarch/nbqa-1.6.1-pyhd8ed1ab_0.conda#66b3d5bcaa232fa047fa7a08854458b5 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983 https://conda.anaconda.org/ae5-admin/noarch/ae5-tools-0.4.1-py_0.tar.bz2#cded60c14f0998c227a706729a28530a https://conda.anaconda.org/conda-forge/noarch/anaconda-client-1.11.0-pyhd8ed1ab_1.tar.bz2#04098557a4837e0f5130c8b6ab929a3d -https://conda.anaconda.org/conda-forge/noarch/dask-2023.1.0-pyhd8ed1ab_0.conda#d250f59c269f98243efc7c681aa1a424 +https://conda.anaconda.org/conda-forge/noarch/dask-2023.1.1-pyhd8ed1ab_0.conda#1d46d636e097f973c7dd5d7ca8d94e53 https://conda.anaconda.org/conda-forge/noarch/anaconda-project-0.11.1-pyhd8ed1ab_0.tar.bz2#85406089db6aa63ee45da8e9f0b966b6 https://conda.anaconda.org/conda-forge/noarch/intake-0.6.6-pyhd8ed1ab_0.tar.bz2#1dc29fb9182b0f3ce935a8bd4c341ed5 diff --git a/envs/environment.yml b/envs/environment.yml index 301bee6f6..d0548cc76 100644 --- a/envs/environment.yml +++ b/envs/environment.yml @@ -14,6 +14,7 @@ dependencies: - nbformat - ae5-tools - nbqa + - flake8 - pip: - nbval >= 0.10.0 # - pydata-sphinx-theme <= 0.9.0 diff --git a/template/anaconda-project-lock.yml b/template/anaconda-project-lock.yml index 38fbf13ac..c390f5a5d 100644 --- a/template/anaconda-project-lock.yml +++ b/template/anaconda-project-lock.yml @@ -11,418 +11,10 @@ # locking_enabled: true +# +# A key goes in here for each env spec. +# env_specs: - test: - locked: true - env_spec_hash: b8d13111f1b3ca9a5856a85ce463962ccc436dcd - platforms: - - linux-64 - - osx-64 - - win-64 - packages: - all: - - argon2-cffi=21.3.0=pyhd3eb1b0_0 - - asttokens=2.0.5=pyhd3eb1b0_0 - - backcall=0.2.0=pyhd3eb1b0_0 - - blas=1.0=mkl - - bleach=4.1.0=pyhd3eb1b0_0 - - charset-normalizer=2.0.4=pyhd3eb1b0_0 - - cycler=0.11.0=pyhd3eb1b0_0 - - decorator=5.1.1=pyhd3eb1b0_0 - - defusedxml=0.7.1=pyhd3eb1b0_0 - - executing=0.8.3=pyhd3eb1b0_0 - - flit-core=3.6.0=pyhd3eb1b0_0 - - fonttools=4.25.0=pyhd3eb1b0_0 - - iniconfig=1.1.1=pyhd3eb1b0_0 - - ipython_genutils=0.2.0=pyhd3eb1b0_1 - - jupyterlab_pygments=0.1.2=py_0 - - munkres=1.1.4=py_0 - - pandocfilters=1.5.0=pyhd3eb1b0_0 - - parso=0.8.3=pyhd3eb1b0_0 - - pickleshare=0.7.5=pyhd3eb1b0_1003 - - pure_eval=0.2.2=pyhd3eb1b0_0 - - py=1.11.0=pyhd3eb1b0_0 - - pycparser=2.21=pyhd3eb1b0_0 - - pygments=2.11.2=pyhd3eb1b0_0 - - pyopenssl=22.0.0=pyhd3eb1b0_0 - - python-dateutil=2.8.2=pyhd3eb1b0_0 - - pyviz_comms=2.0.2=pyhd3eb1b0_0 - - send2trash=1.8.0=pyhd3eb1b0_1 - - six=1.16.0=pyhd3eb1b0_1 - - stack_data=0.2.0=pyhd3eb1b0_0 - - tzdata=2022g=h04d1e81_0 - - wcwidth=0.2.5=pyhd3eb1b0_0 - - wheel=0.37.1=pyhd3eb1b0_0 - unix: - - pexpect=4.8.0=pyhd3eb1b0_3 - - ptyprocess=0.7.0=pyhd3eb1b0_2 - linux-64: - - _libgcc_mutex=0.1=main - - anyio=3.5.0=py39h06a4308_0 - - argon2-cffi-bindings=21.2.0=py39h7f8727e_0 - - attrs=22.1.0=py39h06a4308_0 - - beautifulsoup4=4.11.1=py39h06a4308_0 - - bokeh=2.4.3=py39h06a4308_0 - - bottleneck=1.3.4=py39hce1f21e_0 - - brotli=1.0.9=he6710b0_2 - - brotlipy=0.7.0=py39h27cfd23_1003 - - ca-certificates=2022.10.11=h06a4308_0 - - certifi=2022.12.7=py39h06a4308_0 - - cffi=1.15.0=py39hd667e15_1 - - colorcet=3.0.1=py39h06a4308_0 - - comm=0.1.2=py39h06a4308_0 - - cryptography=38.0.4=py39h9ce1e76_0 - - debugpy=1.5.1=py39h295c915_0 - - entrypoints=0.4=py39h06a4308_0 - - freetype=2.11.0=h70c0345_0 - - giflib=5.2.1=h7b6447c_0 - - holoviews=1.15.3=py39h06a4308_0 - - hvplot=0.8.2=py39h06a4308_0 - - icu=58.2=he6710b0_3 - - idna=3.4=py39h06a4308_0 - - importlib-metadata=4.11.3=py39h06a4308_0 - - intel-openmp=2021.4.0=h06a4308_3561 - - ipykernel=6.19.2=py39hb070fc8_0 - - ipython=8.7.0=py39h06a4308_0 - - jedi=0.18.1=py39h06a4308_1 - - jinja2=3.1.2=py39h06a4308_0 - - jpeg=9e=h7f8727e_0 - - jsonschema=4.16.0=py39h06a4308_0 - - jupyter_client=7.2.2=py39h06a4308_0 - - jupyter_core=5.1.1=py39h06a4308_0 - - jupyter_server=1.23.4=py39h06a4308_0 - - kiwisolver=1.4.2=py39h295c915_0 - - lcms2=2.12=h3be6417_0 - - ld_impl_linux-64=2.38=h1181459_1 - - libffi=3.3=he6710b0_2 - - libgcc-ng=9.1.0=hdf63c60_0 - - libpng=1.6.37=hbc83047_0 - - libsodium=1.0.18=h7b6447c_0 - - libstdcxx-ng=9.1.0=hdf63c60_0 - - libtiff=4.2.0=h2818925_1 - - libwebp-base=1.2.2=h7f8727e_0 - - libwebp=1.2.2=h55f646e_0 - - libxml2=2.9.14=h74e7548_0 - - libxslt=1.1.35=h4e12654_0 - - lxml=4.8.0=py39h1f438cf_0 - - lz4-c=1.9.3=h295c915_1 - - markdown=3.4.1=py39h06a4308_0 - - markupsafe=2.1.1=py39h7f8727e_0 - - matplotlib-base=3.5.1=py39ha18d171_1 - - matplotlib-inline=0.1.6=py39h06a4308_0 - - mistune=0.8.4=py39h27cfd23_1000 - - mkl-service=2.4.0=py39h7f8727e_0 - - mkl=2021.4.0=h06a4308_640 - - mkl_fft=1.3.1=py39hd3c417c_0 - - mkl_random=1.2.2=py39h51133e4_0 - - nbclassic=0.4.8=py39h06a4308_0 - - nbclient=0.5.13=py39h06a4308_0 - - nbconvert=6.5.4=py39h06a4308_0 - - nbformat=5.7.0=py39h06a4308_0 - - nbsmoke=0.6.0=py39h06a4308_0 - - ncurses=6.3=h7f8727e_2 - - nest-asyncio=1.5.6=py39h06a4308_0 - - notebook-shim=0.2.2=py39h06a4308_0 - - notebook=6.5.2=py39h06a4308_0 - - numexpr=2.8.1=py39h6abb31d_0 - - numpy-base=1.22.3=py39hf524024_0 - - numpy=1.22.3=py39he7a7128_0 - - openssl=1.1.1s=h7f8727e_0 - - packaging=22.0=py39h06a4308_0 - - pandas=1.4.2=py39h295c915_0 - - panel=0.14.2=py39h06a4308_0 - - param=1.12.3=py39h06a4308_0 - - pillow=9.0.1=py39h22f2fdc_0 - - pip=22.3.1=py39h06a4308_0 - - platformdirs=2.5.2=py39h06a4308_0 - - pluggy=1.0.0=py39h06a4308_1 - - prometheus_client=0.14.1=py39h06a4308_0 - - prompt-toolkit=3.0.36=py39h06a4308_0 - - psutil=5.8.0=py39h27cfd23_1 - - pyct=0.4.8=py39h06a4308_1 - - pyflakes=3.0.1=py39h06a4308_0 - - pyparsing=3.0.9=py39h06a4308_0 - - pyrsistent=0.18.0=py39heee7806_0 - - pysocks=1.7.1=py39h06a4308_0 - - pytest=7.1.2=py39h06a4308_0 - - python-fastjsonschema=2.16.2=py39h06a4308_0 - - python=3.9.0=hdb3f193_2 - - pytz=2022.7=py39h06a4308_0 - - pyyaml=6.0=py39h7f8727e_1 - - pyzmq=22.3.0=py39h295c915_2 - - readline=8.1.2=h7f8727e_1 - - requests=2.28.1=py39h06a4308_0 - - setuptools=65.6.3=py39h06a4308_0 - - sniffio=1.2.0=py39h06a4308_1 - - soupsieve=2.3.2.post1=py39h06a4308_0 - - sqlite=3.38.5=hc218d9a_0 - - terminado=0.17.1=py39h06a4308_0 - - tinycss2=1.2.1=py39h06a4308_0 - - tk=8.6.12=h1ccaba5_0 - - tomli=2.0.1=py39h06a4308_0 - - tornado=6.1=py39h27cfd23_0 - - tqdm=4.64.1=py39h06a4308_0 - - traitlets=5.7.1=py39h06a4308_0 - - typing-extensions=4.4.0=py39h06a4308_0 - - typing_extensions=4.4.0=py39h06a4308_0 - - urllib3=1.26.14=py39h06a4308_0 - - webencodings=0.5.1=py39h06a4308_1 - - websocket-client=0.58.0=py39h06a4308_4 - - xz=5.2.5=h7f8727e_1 - - yaml=0.2.5=h7b6447c_0 - - zeromq=4.3.4=h2531618_0 - - zipp=3.11.0=py39h06a4308_0 - - zlib=1.2.12=h7f8727e_2 - - zstd=1.5.2=ha4553b6_0 - osx-64: - - anyio=3.5.0=py39hecd8cb5_0 - - appnope=0.1.2=py39hecd8cb5_1001 - - argon2-cffi-bindings=21.2.0=py39hca72f7f_0 - - attrs=22.1.0=py39hecd8cb5_0 - - beautifulsoup4=4.11.1=py39hecd8cb5_0 - - bokeh=2.4.3=py39hecd8cb5_0 - - bottleneck=1.3.5=py39h67323c0_0 - - brotli-bin=1.0.9=hca72f7f_7 - - brotli=1.0.9=hca72f7f_7 - - brotlipy=0.7.0=py39h9ed2024_1003 - - ca-certificates=2022.10.11=hecd8cb5_0 - - certifi=2022.12.7=py39hecd8cb5_0 - - cffi=1.15.1=py39hc55c11b_0 - - colorcet=3.0.1=py39hecd8cb5_0 - - comm=0.1.2=py39hecd8cb5_0 - - contourpy=1.0.5=py39haf03e11_0 - - cryptography=38.0.4=py39hf6deb26_0 - - debugpy=1.5.1=py39he9d5cce_0 - - entrypoints=0.4=py39hecd8cb5_0 - - freetype=2.12.1=hd8bbffd_0 - - giflib=5.2.1=haf1e3a3_0 - - holoviews=1.15.3=py39hecd8cb5_0 - - hvplot=0.8.2=py39hecd8cb5_0 - - icu=58.2=h0a44026_3 - - idna=3.4=py39hecd8cb5_0 - - importlib-metadata=4.11.3=py39hecd8cb5_0 - - intel-openmp=2021.4.0=hecd8cb5_3538 - - ipykernel=6.19.2=py39h01d92e1_0 - - ipython=8.7.0=py39hecd8cb5_0 - - jedi=0.18.1=py39hecd8cb5_1 - - jinja2=3.1.2=py39hecd8cb5_0 - - jpeg=9e=hca72f7f_0 - - jsonschema=4.16.0=py39hecd8cb5_0 - - jupyter_client=7.4.8=py39hecd8cb5_0 - - jupyter_core=5.1.1=py39hecd8cb5_0 - - jupyter_server=1.23.4=py39hecd8cb5_0 - - kiwisolver=1.4.4=py39hcec6c5f_0 - - lcms2=2.12=hf1fd2bf_0 - - lerc=3.0=he9d5cce_0 - - libbrotlicommon=1.0.9=hca72f7f_7 - - libbrotlidec=1.0.9=hca72f7f_7 - - libbrotlienc=1.0.9=hca72f7f_7 - - libcxx=14.0.6=h9765a3e_0 - - libdeflate=1.8=h9ed2024_5 - - libffi=3.3=hb1e8313_2 - - libiconv=1.16=hca72f7f_2 - - libpng=1.6.37=ha441bb4_0 - - libsodium=1.0.18=h1de35cc_0 - - libtiff=4.5.0=h2cd0358_0 - - libwebp-base=1.2.4=hca72f7f_0 - - libwebp=1.2.4=h56c3ce4_0 - - libxml2=2.9.14=hbf8cd5e_0 - - libxslt=1.1.35=h5b33f42_0 - - lxml=4.9.1=py39h65b224f_0 - - lz4-c=1.9.4=hcec6c5f_0 - - markdown=3.4.1=py39hecd8cb5_0 - - markupsafe=2.1.1=py39hca72f7f_0 - - matplotlib-base=3.6.2=py39h220de94_0 - - matplotlib-inline=0.1.6=py39hecd8cb5_0 - - mistune=0.8.4=py39h9ed2024_1000 - - mkl-service=2.4.0=py39h9ed2024_0 - - mkl=2021.4.0=hecd8cb5_637 - - mkl_fft=1.3.1=py39h4ab4a9b_0 - - mkl_random=1.2.2=py39hb2f4e1b_0 - - nbclassic=0.4.8=py39hecd8cb5_0 - - nbclient=0.5.13=py39hecd8cb5_0 - - nbconvert=6.5.4=py39hecd8cb5_0 - - nbformat=5.7.0=py39hecd8cb5_0 - - nbsmoke=0.6.0=py39hecd8cb5_0 - - ncurses=6.3=hca72f7f_3 - - nest-asyncio=1.5.6=py39hecd8cb5_0 - - notebook-shim=0.2.2=py39hecd8cb5_0 - - notebook=6.5.2=py39hecd8cb5_0 - - numexpr=2.8.4=py39he696674_0 - - numpy-base=1.23.5=py39h9cd3388_0 - - numpy=1.23.5=py39he696674_0 - - openssl=1.1.1s=hca72f7f_0 - - packaging=22.0=py39hecd8cb5_0 - - pandas=1.5.2=py39h07fba90_0 - - panel=0.14.2=py39hecd8cb5_0 - - param=1.12.3=py39hecd8cb5_0 - - pillow=9.3.0=py39h81888ad_1 - - pip=22.3.1=py39hecd8cb5_0 - - platformdirs=2.5.2=py39hecd8cb5_0 - - pluggy=1.0.0=py39hecd8cb5_1 - - prometheus_client=0.14.1=py39hecd8cb5_0 - - prompt-toolkit=3.0.36=py39hecd8cb5_0 - - psutil=5.9.0=py39hca72f7f_0 - - pyct=0.4.8=py39hecd8cb5_1 - - pyflakes=3.0.1=py39hecd8cb5_0 - - pyparsing=3.0.9=py39hecd8cb5_0 - - pyrsistent=0.18.0=py39hca72f7f_0 - - pysocks=1.7.1=py39hecd8cb5_0 - - pytest=7.1.2=py39hecd8cb5_0 - - python-fastjsonschema=2.16.2=py39hecd8cb5_0 - - python=3.9.0=h88f2d9e_2 - - pytz=2022.7=py39hecd8cb5_0 - - pyyaml=6.0=py39h6c40b1e_1 - - pyzmq=23.2.0=py39he9d5cce_0 - - readline=8.2=hca72f7f_0 - - requests=2.28.1=py39hecd8cb5_0 - - setuptools=65.6.3=py39hecd8cb5_0 - - sniffio=1.2.0=py39hecd8cb5_1 - - soupsieve=2.3.2.post1=py39hecd8cb5_0 - - sqlite=3.40.1=h880c91c_0 - - terminado=0.17.1=py39hecd8cb5_0 - - tinycss2=1.2.1=py39hecd8cb5_0 - - tk=8.6.12=h5d9f67b_0 - - tomli=2.0.1=py39hecd8cb5_0 - - tornado=6.2=py39hca72f7f_0 - - tqdm=4.64.1=py39hecd8cb5_0 - - traitlets=5.7.1=py39hecd8cb5_0 - - typing-extensions=4.4.0=py39hecd8cb5_0 - - typing_extensions=4.4.0=py39hecd8cb5_0 - - urllib3=1.26.14=py39hecd8cb5_0 - - webencodings=0.5.1=py39hecd8cb5_1 - - websocket-client=0.58.0=py39hecd8cb5_4 - - xz=5.2.10=h6c40b1e_1 - - yaml=0.2.5=haf1e3a3_0 - - zeromq=4.3.4=h23ab428_0 - - zipp=3.11.0=py39hecd8cb5_0 - - zlib=1.2.13=h4dc903c_0 - - zstd=1.5.2=hcb37349_0 - win-64: - - anyio=3.5.0=py39haa95532_0 - - argon2-cffi-bindings=21.2.0=py39h2bbff1b_0 - - atomicwrites=1.4.0=py_0 - - attrs=22.1.0=py39haa95532_0 - - beautifulsoup4=4.11.1=py39haa95532_0 - - bokeh=2.4.3=py39haa95532_0 - - bottleneck=1.3.5=py39h080aedc_0 - - brotli-bin=1.0.9=h2bbff1b_7 - - brotli=1.0.9=h2bbff1b_7 - - brotlipy=0.7.0=py39h2bbff1b_1003 - - ca-certificates=2022.10.11=haa95532_0 - - certifi=2022.12.7=py39haa95532_0 - - cffi=1.15.1=py39h2bbff1b_3 - - colorama=0.4.6=py39haa95532_0 - - colorcet=3.0.1=py39haa95532_0 - - comm=0.1.2=py39haa95532_0 - - contourpy=1.0.5=py39h59b6b97_0 - - cryptography=38.0.4=py39h21b164f_0 - - debugpy=1.5.1=py39hd77b12b_0 - - entrypoints=0.4=py39haa95532_0 - - freetype=2.12.1=ha860e81_0 - - holoviews=1.15.3=py39haa95532_0 - - hvplot=0.8.2=py39haa95532_0 - - idna=3.4=py39haa95532_0 - - importlib-metadata=4.11.3=py39haa95532_0 - - intel-openmp=2021.4.0=haa95532_3556 - - ipykernel=6.19.2=py39hd4e2768_0 - - ipython=8.7.0=py39haa95532_0 - - jedi=0.18.1=py39haa95532_1 - - jinja2=3.1.2=py39haa95532_0 - - jpeg=9e=h2bbff1b_0 - - jsonschema=4.16.0=py39haa95532_0 - - jupyter_client=7.4.8=py39haa95532_0 - - jupyter_core=5.1.1=py39haa95532_0 - - jupyter_server=1.23.4=py39haa95532_0 - - kiwisolver=1.4.4=py39hd77b12b_0 - - lerc=3.0=hd77b12b_0 - - libbrotlicommon=1.0.9=h2bbff1b_7 - - libbrotlidec=1.0.9=h2bbff1b_7 - - libbrotlienc=1.0.9=h2bbff1b_7 - - libdeflate=1.8=h2bbff1b_5 - - libiconv=1.16=h2bbff1b_2 - - libpng=1.6.37=h2a8f88b_0 - - libsodium=1.0.18=h62dcd97_0 - - libtiff=4.5.0=h8a3f274_0 - - libwebp-base=1.2.4=h2bbff1b_0 - - libwebp=1.2.4=h2bbff1b_0 - - libxml2=2.9.14=h0ad7f3c_0 - - libxslt=1.1.35=h2bbff1b_0 - - lxml=4.9.1=py39h1985fb9_0 - - lz4-c=1.9.4=h2bbff1b_0 - - markdown=3.4.1=py39haa95532_0 - - markupsafe=2.1.1=py39h2bbff1b_0 - - matplotlib-base=3.6.2=py39h1094b8e_0 - - matplotlib-inline=0.1.6=py39haa95532_0 - - mistune=0.8.4=py39h2bbff1b_1000 - - mkl-service=2.4.0=py39h2bbff1b_0 - - mkl=2021.4.0=haa95532_640 - - mkl_fft=1.3.1=py39h277e83a_0 - - mkl_random=1.2.2=py39hf11a4ad_0 - - nbclassic=0.4.8=py39haa95532_0 - - nbclient=0.5.13=py39haa95532_0 - - nbconvert=6.5.4=py39haa95532_0 - - nbformat=5.7.0=py39haa95532_0 - - nbsmoke=0.6.0=py39haa95532_0 - - nest-asyncio=1.5.6=py39haa95532_0 - - notebook-shim=0.2.2=py39haa95532_0 - - notebook=6.5.2=py39haa95532_0 - - numexpr=2.8.4=py39h5b0cc5e_0 - - numpy-base=1.23.5=py39h4da318b_0 - - numpy=1.23.5=py39h3b20f71_0 - - openssl=1.1.1s=h2bbff1b_0 - - packaging=22.0=py39haa95532_0 - - pandas=1.5.2=py39hf11a4ad_0 - - panel=0.14.2=py39haa95532_0 - - param=1.12.3=py39haa95532_0 - - pillow=9.3.0=py39hdc2b20a_1 - - pip=22.3.1=py39haa95532_0 - - platformdirs=2.5.2=py39haa95532_0 - - pluggy=1.0.0=py39haa95532_1 - - prometheus_client=0.14.1=py39haa95532_0 - - prompt-toolkit=3.0.36=py39haa95532_0 - - psutil=5.9.0=py39h2bbff1b_0 - - pyct=0.4.8=py39haa95532_1 - - pyflakes=3.0.1=py39haa95532_0 - - pyparsing=3.0.9=py39haa95532_0 - - pyrsistent=0.18.0=py39h196d8e1_0 - - pysocks=1.7.1=py39haa95532_0 - - pytest=7.1.2=py39haa95532_0 - - python-fastjsonschema=2.16.2=py39haa95532_0 - - python=3.9.0=h6244533_2 - - pytz=2022.7=py39haa95532_0 - - pywin32=305=py39h2bbff1b_0 - - pywinpty=2.0.2=py39h5da7b33_0 - - pyyaml=6.0=py39h2bbff1b_1 - - pyzmq=23.2.0=py39hd77b12b_0 - - requests=2.28.1=py39haa95532_0 - - setuptools=65.6.3=py39haa95532_0 - - sniffio=1.2.0=py39haa95532_1 - - soupsieve=2.3.2.post1=py39haa95532_0 - - sqlite=3.40.1=h2bbff1b_0 - - terminado=0.17.1=py39haa95532_0 - - tinycss2=1.2.1=py39haa95532_0 - - tk=8.6.12=h2bbff1b_0 - - tomli=2.0.1=py39haa95532_0 - - tornado=6.2=py39h2bbff1b_0 - - tqdm=4.64.1=py39haa95532_0 - - traitlets=5.7.1=py39haa95532_0 - - typing-extensions=4.4.0=py39haa95532_0 - - typing_extensions=4.4.0=py39haa95532_0 - - urllib3=1.26.14=py39haa95532_0 - - vc=14.2=h21ff451_1 - - vs2015_runtime=14.27.29016=h5e58377_2 - - webencodings=0.5.1=py39haa95532_1 - - websocket-client=0.58.0=py39haa95532_4 - - win_inet_pton=1.1.0=py39haa95532_0 - - wincertstore=0.2=py39haa95532_2 - - winpty=0.4.3=4 - - xz=5.2.10=h8cc25b3_1 - - yaml=0.2.5=he774522_0 - - zeromq=4.3.4=hd77b12b_0 - - zipp=3.11.0=py39haa95532_0 - - zlib=1.2.13=h8cc25b3_0 - - zstd=1.5.2=h19a0ad4_0 default: locked: true env_spec_hash: 92ade5113c13c10d6e86640761a39f82b5fccf49 @@ -475,7 +67,7 @@ env_specs: - bottleneck=1.3.4=py39hce1f21e_0 - brotli=1.0.9=he6710b0_2 - brotlipy=0.7.0=py39h27cfd23_1003 - - ca-certificates=2022.10.11=h06a4308_0 + - ca-certificates=2023.01.10=h06a4308_0 - certifi=2022.12.7=py39h06a4308_0 - cffi=1.15.0=py39hd667e15_1 - colorcet=3.0.1=py39h06a4308_0 @@ -492,7 +84,7 @@ env_specs: - importlib-metadata=4.11.3=py39h06a4308_0 - intel-openmp=2021.4.0=h06a4308_3561 - ipykernel=6.19.2=py39hb070fc8_0 - - ipython=8.7.0=py39h06a4308_0 + - ipython=8.8.0=py39h06a4308_0 - jedi=0.18.1=py39h06a4308_1 - jinja2=3.1.2=py39h06a4308_0 - jpeg=9e=h7f8727e_0 @@ -589,7 +181,7 @@ env_specs: - brotli-bin=1.0.9=hca72f7f_7 - brotli=1.0.9=hca72f7f_7 - brotlipy=0.7.0=py39h9ed2024_1003 - - ca-certificates=2022.10.11=hecd8cb5_0 + - ca-certificates=2023.01.10=hecd8cb5_0 - certifi=2022.12.7=py39hecd8cb5_0 - cffi=1.15.1=py39hc55c11b_0 - colorcet=3.0.1=py39hecd8cb5_0 @@ -599,7 +191,7 @@ env_specs: - debugpy=1.5.1=py39he9d5cce_0 - entrypoints=0.4=py39hecd8cb5_0 - freetype=2.12.1=hd8bbffd_0 - - giflib=5.2.1=haf1e3a3_0 + - giflib=5.2.1=h6c40b1e_1 - holoviews=1.15.3=py39hecd8cb5_0 - hvplot=0.8.2=py39hecd8cb5_0 - icu=58.2=h0a44026_3 @@ -607,7 +199,7 @@ env_specs: - importlib-metadata=4.11.3=py39hecd8cb5_0 - intel-openmp=2021.4.0=hecd8cb5_3538 - ipykernel=6.19.2=py39h01d92e1_0 - - ipython=8.7.0=py39hecd8cb5_0 + - ipython=8.8.0=py39hecd8cb5_0 - jedi=0.18.1=py39hecd8cb5_1 - jinja2=3.1.2=py39hecd8cb5_0 - jpeg=9e=hca72f7f_0 @@ -647,7 +239,7 @@ env_specs: - nbclient=0.5.13=py39hecd8cb5_0 - nbconvert=6.5.4=py39hecd8cb5_0 - nbformat=5.7.0=py39hecd8cb5_0 - - ncurses=6.3=hca72f7f_3 + - ncurses=6.4=hcec6c5f_0 - nest-asyncio=1.5.6=py39hecd8cb5_0 - notebook-shim=0.2.2=py39hecd8cb5_0 - notebook=6.5.2=py39hecd8cb5_0 @@ -707,7 +299,7 @@ env_specs: - brotli-bin=1.0.9=h2bbff1b_7 - brotli=1.0.9=h2bbff1b_7 - brotlipy=0.7.0=py39h2bbff1b_1003 - - ca-certificates=2022.10.11=haa95532_0 + - ca-certificates=2023.01.10=haa95532_0 - certifi=2022.12.7=py39haa95532_0 - cffi=1.15.1=py39h2bbff1b_3 - colorama=0.4.6=py39haa95532_0 @@ -724,7 +316,7 @@ env_specs: - importlib-metadata=4.11.3=py39haa95532_0 - intel-openmp=2021.4.0=haa95532_3556 - ipykernel=6.19.2=py39hd4e2768_0 - - ipython=8.7.0=py39haa95532_0 + - ipython=8.8.0=py39haa95532_0 - jedi=0.18.1=py39haa95532_1 - jinja2=3.1.2=py39haa95532_0 - jpeg=9e=h2bbff1b_0 diff --git a/template/anaconda-project.yml b/template/anaconda-project.yml index 4f7f49549..b89c9fd57 100644 --- a/template/anaconda-project.yml +++ b/template/anaconda-project.yml @@ -1,5 +1,3 @@ -# To reproduce: install 'anaconda-project', then 'anaconda-project run' - # required: name MUST match the directory name. # name must be of the form r'[a-z_]+' name: template @@ -24,6 +22,11 @@ examples_config: - "panel" ### OPTIONAL ### + # The project title is used to display the project name in the gallery. The + # default title is a prettyfied version of the project name, + # i.e. 'foo_bar' becomes 'Foo Bar'. + # This parameters allows to override the default title. + title: "Template" # Listed deployments will by default automatically be started. # Maximum number of deployments is 2. # Each deployment must declare the command it deploys, options @@ -54,17 +57,17 @@ user_fields: [examples_config] # required: list of the channels needed to solve the environment channels: - defaults - # To remove the default channel, if need be. +# Use nodefaults to remove the "defaults" channel # - nodefaults # required: list of the direct dependencies needed to run the project. # Good practice is to pin their minimum version (>=) to indicate the # version with which the project has been created. Updates to the project -# should updates these pins, if relevant. +# should in practice update these pins. packages: &pkgs - # required: must be installed +# required: the "notebook" package must be installed - notebook >=6.5.2 - # then list all your dependencies, including Python itself +# then list all your dependencies, including Python itself - python ==3.9 - panel >=0.14.2 - hvplot >=0.8.2 @@ -80,37 +83,41 @@ commands: notebook: template.ipynb # shortcut to open all the notebook # notebook: . - # if you intend to deploy a panel/lumen app, set the `dashboard` command + # if you intend to declare a deployment of a panel/lumen app, set the `dashboard` command dashboard: - # the `--rest-session-info --session-history -1` options are required + # for a deployment, the `--rest-session-info --session-history -1` options are required unix: panel serve --rest-session-info --session-history -1 template.ipynb supports_http_options: true - # required: a project must have a test command that references the 'test' env_spec - test: - unix: pytest --nbsmoke-run -k *.ipynb --ignore envs - windows: pytest --nbsmoke-run -k *.ipynb --ignore envs - env_spec: test - # required: a project must have a lint command that references the 'test' env_spec - lint: - unix: pytest --nbsmoke-lint -k *.ipynb --ignore envs - windows: pytest --nbsmoke-lint -k *.ipynb --ignore envs - env_spec: test + # optional: a project can have a test command that references the 'test' env_spec + # if not, the project will be tested automatically by the system + # test: + # unix: pytest --nbsmoke-run -k ".ipynb" --ignore envs + # windows: pytest --nbsmoke-run -k ".ipynb" --ignore envs + # env_spec: test -# required -env_specs: - default: {} - test: - packages: &testpkgs - - nbsmoke - - pytest - dependencies: *testpkgs +# optional: declare the test env specs +# env_specs: +# default: {} +# test: +# packages: &testpkgs +# - nbsmoke +# - pytest +# dependencies: *testpkgs -# optional environment variables +# optional: environment variables variables: {} # optional downloads -downloads: {} +downloads: + DATA: + url: https://datasets.holoviz.org/penguins/v1/penguins.csv + description: | + Penguins dataset + # The output must be in the data/ subfolder + filename: data/penguins.csv + +# required: supported plaforms platforms: - linux-64 - osx-64 diff --git a/template/data/penguins.csv b/test_data/template/penguins.csv similarity index 100% rename from template/data/penguins.csv rename to test_data/template/penguins.csv