diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1b2adb0e..eea6c589 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +exclude: (quetz/migrations) repos: - repo: https://github.com/pycqa/isort rev: 5.12.0 @@ -41,3 +42,8 @@ repos: rev: v1.1.314 hooks: - id: pyright + - repo: https://github.com/Quantco/pre-commit-mirrors-typos + rev: 1.16.26 + hooks: + - id: typos-conda + exclude: (quetz/tests/authentification/test_oauth.py) diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 00000000..36e8b015 --- /dev/null +++ b/_typos.toml @@ -0,0 +1,5 @@ +[default.extend-words] +fo = "fo" + +[files] +extend-exclude = ["quetz/migrations/*.po", "quetz/tests/authentification/test_oauth.py"] diff --git a/docker/jupyterhub_config.py b/docker/jupyterhub_config.py index f61ae4c9..98e8c0e5 100644 --- a/docker/jupyterhub_config.py +++ b/docker/jupyterhub_config.py @@ -1161,7 +1161,7 @@ # # This function is called after the user has passed all authentication checks # and is ready to successfully authenticate. This function must return the -# authentication dict reguardless of changes to it. +# authentication dict regardless of changes to it. # # This maybe a coroutine. # diff --git a/docs/source/deploying/authenticators.rst b/docs/source/deploying/authenticators.rst index e3566bf8..784aef1e 100644 --- a/docs/source/deploying/authenticators.rst +++ b/docs/source/deploying/authenticators.rst @@ -98,7 +98,7 @@ For example, the custom authenticator might be: Registering authenticator ^^^^^^^^^^^^^^^^^^^^^^^^^ -The standard way to register an authenticator with Quetz, is to distibute it as a plugin +The standard way to register an authenticator with Quetz, is to distribute it as a plugin (see :ref:`plugins_section`). To automatize the creation of a plugin, check out our cookiecutter `template`_. diff --git a/docs/source/deploying/configuration.rst b/docs/source/deploying/configuration.rst index a39e23e5..306118c3 100644 --- a/docs/source/deploying/configuration.rst +++ b/docs/source/deploying/configuration.rst @@ -72,14 +72,14 @@ Configure default user permissions, creating default channel and super-admin per admins = ["github:admin_user"] # users with maintainer role maintainers = ["google:other_user"] - # users with memeber role + # users with member role members = ["github:some", "github:random", "github:name"] # default role assigned to new users # leave out if role should be null default_role = "member" # create a default channel for new users named {username} create_default_channel = false - # wether to collect email addresses when users register + # whether to collect email addresses when users register collect_emails = false You can use one of the following options to configure privileged users: @@ -193,7 +193,7 @@ Quetz can store packages in Google Cloud Storage. To configure, use the followin region="..." :project: The Google Cloud Project ID to work under -:token: A token to pass the `gcsfs`. See the `gcsfs documention `_ for valid values. +:token: A token to pass the `gcsfs`. See the `gcsfs documentation `_ for valid values. :bucket_prefix: :bucket_suffix: channel buckets on GCS are created with the following semantics: ``{bucket_prefix}{channel_name}{bucket_suffix}`` :cache_timeout: Timeout in s after which local GCS cache entries are invalidated. Set to a value <=0 to disable caching completely. Default is that entries are never invalidated. @@ -270,9 +270,9 @@ You can also use a couple of environment variables to configure the behaviour of Variable description values default ======================= ====================================== =========================== =================== ``QUETZ_LOG_LEVEL`` log level ERROR, INFO, WARNING, DEBUG INFO or config file -``QUETZ_API_KEY`` api key used by quetz-client log level string +``QUETZ_API_KEY`` api key used by quetz-client string ``QUETZ_TEST_DATABASE`` uri to the database used in tests string sqlite:///:memory: -``QUETZ_TEST_DBINIT`` method to create db tabels in tests "create-tables" or "create-tables" +``QUETZ_TEST_DBINIT`` method to create db tables in tests "create-tables" or "create-tables" "use-migrations" ``S3_ACCESS_KEY`` access key to s3 (used in tests) string ``S3_SECRET_KEY`` secret key to s3 (used in tests) string diff --git a/docs/source/deploying/index.rst b/docs/source/deploying/index.rst index adbb40a9..391c3ff8 100644 --- a/docs/source/deploying/index.rst +++ b/docs/source/deploying/index.rst @@ -2,7 +2,7 @@ Deploying ========= There are many options for deploying a package repository with Quetz. You can deploy Quetz either locally for -testing, on a production server or in the cloud. The process can be tailored to your needs and exisiting +testing, on a production server or in the cloud. The process can be tailored to your needs and existing infrastructure. .. toctree:: diff --git a/docs/source/deploying/migrations.rst b/docs/source/deploying/migrations.rst index 70f5e08b..35dfad08 100644 --- a/docs/source/deploying/migrations.rst +++ b/docs/source/deploying/migrations.rst @@ -5,7 +5,7 @@ When the data classes in Quetz are modified (for example, a column is added, rem .. note:: - Before running any of the commands below, you need to make sure that your database is backed up. The backup process depends on the infrastrcuture, but in the simplest case it may involve the dump of the whole database (using ``pg_dump`` for example). + Before running any of the commands below, you need to make sure that your database is backed up. The backup process depends on the infrastructure, but in the simplest case it may involve the dump of the whole database (using ``pg_dump`` for example). Migrating database ------------------ diff --git a/docs/source/qeps/qep-001-user-permissions.rst b/docs/source/qeps/qep-001-user-permissions.rst index f6f7fee4..550d0f4c 100644 --- a/docs/source/qeps/qep-001-user-permissions.rst +++ b/docs/source/qeps/qep-001-user-permissions.rst @@ -61,7 +61,7 @@ Roles can be configure with a config file: admins = ["wolfv"] # users with maintainer role maintainers = ["btel"] - # users with memeber role + # users with member role members = ["some", "random", "name"] # default role assigned to new users # leave out if role should be null diff --git a/quetz/authentication/azuread.py b/quetz/authentication/azuread.py index 7e0911b6..258d9a31 100644 --- a/quetz/authentication/azuread.py +++ b/quetz/authentication/azuread.py @@ -16,7 +16,7 @@ class AzureADAuthenticator(OAuthAuthenticator): tenant_id = "tenant-name or id" You can obtain ``client_id`` and ``client_secret`` by registering your - application with AzureAD platfrom at this URL: + application with AzureAD platform at this URL: `https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps` The ``tenant_id`` can either be a specific tenant's GUID identifier or one of three diff --git a/quetz/authentication/google.py b/quetz/authentication/google.py index 468558f2..91243390 100644 --- a/quetz/authentication/google.py +++ b/quetz/authentication/google.py @@ -18,7 +18,7 @@ class GoogleAuthenticator(OAuthAuthenticator): client_secret = "03728444a12abff17e9444fd231b4379d58f0b" You can obtain ``client_id`` and ``client_secret`` by registering your - application with Google platfrom at this URL: + application with Google platform at this URL: ``_. """ diff --git a/quetz/authentication/jupyterhub.py b/quetz/authentication/jupyterhub.py index b9fc23ee..7be69a01 100644 --- a/quetz/authentication/jupyterhub.py +++ b/quetz/authentication/jupyterhub.py @@ -60,7 +60,7 @@ def register(cls, config: Config): class JupyterhubAuthenticator(OAuthAuthenticator): - """Use Oauth2 protcol to authenticate with jupyterhub server, which acts + """Use OAuth2 protocol to authenticate with jupyterhub server, which acts as identity provider. To activate add the following section to the ``config.toml`` (see :ref:`configfile`): @@ -74,13 +74,13 @@ class JupyterhubAuthenticator(OAuthAuthenticator): client_id = "quetz_client" client_secret = "super-secret" - # token enpoint of Jupyterhub, needs to be accessible from Quetz server + # token endpoint of Jupyterhub, needs to be accessible from Quetz server access_token_url = "http://JUPYTERHUB_HOST:PORT/hub/api/oauth2/token" # authorize endpoint of JupyterHub, needs to be accessible from users' browser authorize_url = "http://JUPYTERHUB_HOST:PORT/hub/api/oauth2/authorize" - # API root, needs to be accesible from Quetz server + # API root, needs to be accessible from Quetz server api_base_url = "http://JUPYTERHUB_HOST:PORT/hub/api/" To configure quetz as an oauth client in JupyterHub, you will need to define diff --git a/quetz/authentication/oauth2.py b/quetz/authentication/oauth2.py index a1702d3a..f9c9ae92 100644 --- a/quetz/authentication/oauth2.py +++ b/quetz/authentication/oauth2.py @@ -40,18 +40,18 @@ class OAuthAuthenticator(BaseAuthenticator): """Base class for authenticators using Oauth2 protocol and its variants. The :py:meth:`authenticate` method is already implemented, but you will need to - override some of the following variables in sublasses to make it work: + override some of the following variables in subclasses to make it work: :var str provider: name of the provider (it will be used in the url) :var handler_cls: class with handlers for all oauth2 relevant endpoints in Quetz server - :type handler_cls: sublass of :py:class:`OauthHandlers` + :type handler_cls: subclass of :py:class:`OauthHandlers` :var client_id: required, client id registered with the provider :var client_secret: required, likewise :var bool is_enabled: True if authenticator is enabled, can be configured in :py:meth:`configure` method - :var access_token_url: URL of the OAuth2 endpoint ot request a token + :var access_token_url: URL of the OAuth2 endpoint to request a token :var authorize_url: URL of the OAuth2 ``authorize`` endpoint :var api_base_url: URL of the API root of the provider server :var validate_token_url: path of endpoint to validate the token diff --git a/quetz/authorization.py b/quetz/authorization.py index 9b175c80..68f0cf02 100644 --- a/quetz/authorization.py +++ b/quetz/authorization.py @@ -262,7 +262,7 @@ def assert_create_api_key_roles(self, roles): ) self.assert_channel_roles(role.channel, required_channel_roles) else: - # create key without assigning special channel/package privilages + # create key without assigning special channel/package privileges return True def assert_delete_api_key(self, api_key): diff --git a/quetz/channel_data.py b/quetz/channel_data.py index 4219d87e..331e57b3 100644 --- a/quetz/channel_data.py +++ b/quetz/channel_data.py @@ -81,7 +81,7 @@ def export(dao, channel_name): packages = channeldata["packages"] subdirs = set(['noarch']) - for name, info in dao.get_channel_datas(channel_name): + for name, info in dao.get_channel_data(channel_name): if info is not None: data = json.loads(info) packages[name] = data diff --git a/quetz/config.py b/quetz/config.py index ae2460d9..eb477bdd 100644 --- a/quetz/config.py +++ b/quetz/config.py @@ -401,18 +401,18 @@ def _get_environ_config(self) -> Dict[str, Any]: if key.startswith(_env_prefix) } for var, value in quetz_var.items(): - splitted_key = var.split('_') - config_key = splitted_key[1].lower() + split_key = var.split('_') + config_key = split_key[1].lower() idx = 2 # look for the first level of config_map. # It must be done in loop as the key itself can contains '_'. first_level = None - while idx < len(splitted_key): + while idx < len(split_key): first_level = self._find_first_level_config(config_key) if first_level: break - config_key += f"_{ splitted_key[idx].lower()}" + config_key += f"_{ split_key[idx].lower()}" idx += 1 # no first_level found, the variable is useless. @@ -423,7 +423,7 @@ def _get_environ_config(self) -> Dict[str, Any]: config[first_level.name] = value # the first level is a section. elif isinstance(first_level, ConfigSection): - entry = "_".join(splitted_key[idx:]).lower() + entry = "_".join(split_key[idx:]).lower() # the entry does not exist in section, the variable is useless. if entry not in [ section_entry.name for section_entry in first_level.entries @@ -432,7 +432,7 @@ def _get_environ_config(self) -> Dict[str, Any]: # add the entry to the config. if first_level.name not in config: config[first_level.name]: Dict[str, Any] = {} - config[first_level.name]["_".join(splitted_key[idx:]).lower()] = value + config[first_level.name]["_".join(split_key[idx:]).lower()] = value return config @@ -499,7 +499,7 @@ def configured_section(self, section: str) -> bool: Returns ------- bool - Wether or not the given section is configured + Whether or not the given section is configured """ return bool(self.config.get(section)) diff --git a/quetz/dao.py b/quetz/dao.py index 9077363d..31bcdcca 100644 --- a/quetz/dao.py +++ b/quetz/dao.py @@ -165,9 +165,9 @@ def _parse_sort_by(query, model, sortstr: str): sorts = sortstr.split(',') for s in sorts: - splitted = s.split(':') - if len(splitted) == 2: - field, order = splitted + split_result = s.split(':') + if len(split_result) == 2: + field, order = split_result else: field = s order = 'desc' @@ -1000,7 +1000,7 @@ def get_package_infos(self, channel_name: str, subdir: str): .order_by(PackageVersion.filename) ) - def get_channel_datas(self, channel_name: str): + def get_channel_data(self, channel_name: str): # Returns iterator return ( self.db.query(Package.name, Package.channeldata) diff --git a/quetz/hooks.py b/quetz/hooks.py index 40f9c7ea..9d7654da 100644 --- a/quetz/hooks.py +++ b/quetz/hooks.py @@ -22,7 +22,7 @@ def register_router() -> 'fastapi.APIRouter': def post_add_package_version( version: 'quetz.db_models.PackageVersion', condainfo: 'quetz.condainfo.CondaInfo' ) -> None: - """hook for post-processsing after adding a package file. + """hook for post-processing after adding a package file. :param quetz.db_models.PackageVersion version: package version model that was added in to the database @@ -42,7 +42,7 @@ def post_package_indexing( files: dict, packages: dict, ) -> None: - """hook for post-processsing after building indexes. + """hook for post-processing after building indexes. :param quetz.pkgstores.PackageStore pkgstore: package store used to store/retrieve packages @@ -70,7 +70,7 @@ def post_index_creation( channel_name: str, subdir: str, ) -> None: - """hook for post-processsing after creating package index. + """hook for post-processing after creating package index. :param dict raw_repodata: the package index diff --git a/quetz/jobs/runner.py b/quetz/jobs/runner.py index 17c88c86..fda6ada7 100644 --- a/quetz/jobs/runner.py +++ b/quetz/jobs/runner.py @@ -334,14 +334,14 @@ def _update_running_jobs(self): .outerjoin(Job) .filter(running_job) .group_by(Task.job_id) - # select jobs where all tasks are finsihed + # select jobs where all tasks are finished .having(all_true(task_done)) .all() ) for job_id, repeat, failed in results: if repeat: # job with repeat non-null repeat column should be - # kept runing until cancelled + # kept running until cancelled status = JobStatus.pending elif failed: status = JobStatus.failed diff --git a/quetz/main.py b/quetz/main.py index 9bd35574..f10c38bc 100644 --- a/quetz/main.py +++ b/quetz/main.py @@ -1770,7 +1770,7 @@ async def task(): @app.on_event("shutdown") -async def stop_sync_donwload_counts(): +async def stop_sync_download_counts(): app.sync_download_task.cancel() try: await app.sync_download_task diff --git a/quetz/metrics/tasks.py b/quetz/metrics/tasks.py index eef665ee..09f9a23f 100644 --- a/quetz/metrics/tasks.py +++ b/quetz/metrics/tasks.py @@ -57,7 +57,7 @@ def synchronize_metrics_from_mirrors( packages = response_data["packages"] except KeyError: logger.error( - f"malfromated respose received from {metrics_url}: " + f"malfromated response received from {metrics_url}: " "missing 'packages' key" ) continue diff --git a/quetz/tasks/indexing.py b/quetz/tasks/indexing.py index a1f32562..79dd2dff 100644 --- a/quetz/tasks/indexing.py +++ b/quetz/tasks/indexing.py @@ -123,7 +123,7 @@ def validate_packages(dao, pkgstore, channel_name): in_ls_not_db = ls_result_set - db_result_set in_db_not_ls = db_result_set - ls_result_set - valid, inexistant, wrong_size = 0, 0, 0 + valid, inexistent, wrong_size = 0, 0, 0 # remove all files that are in database and not uploaded for f in in_db_not_ls: @@ -138,7 +138,7 @@ def validate_packages(dao, pkgstore, channel_name): .one() ) dao.db.delete(db_pkg_to_delete) - inexistant += 1 + inexistent += 1 db_dict = dict(db_result) for f in ls_result: @@ -173,7 +173,7 @@ def validate_packages(dao, pkgstore, channel_name): logger.info(f"Valid files: {valid}") logger.info(f"Wrong size: {wrong_size}") - logger.info(f"Not uploaded: {inexistant}") + logger.info(f"Not uploaded: {inexistent}") update_indexes(dao, pkgstore, channel_name) diff --git a/quetz/testing/fixtures.py b/quetz/testing/fixtures.py index 25a964c9..a7bd8907 100644 --- a/quetz/testing/fixtures.py +++ b/quetz/testing/fixtures.py @@ -120,10 +120,10 @@ def auto_rollback(): @pytest.fixture def session_maker(sql_connection, create_tables, auto_rollback): # run the tests with a separate external DB transaction - # so that we can easily rollback all db changes (even if commited) + # so that we can easily rollback all db changes (even if committed) # done by the test client - # Note: when rollback is explictly called in the implementation, + # Note: when rollback is explicitly called in the implementation, # it will remove all objects created in the test even the ones # that were already committed! @@ -255,7 +255,7 @@ def get_session_mock(*args, **kwargs): mocker.patch("quetz.database.get_session", get_session_mock) - # overiding dependency works with all requests handlers that + # overriding dependency works with all requests handlers that # depend on quetz.deps.get_db app.dependency_overrides[get_db] = lambda: db diff --git a/quetz/tests/api/test_channels.py b/quetz/tests/api/test_channels.py index 3976feab..109c72d2 100644 --- a/quetz/tests/api/test_channels.py +++ b/quetz/tests/api/test_channels.py @@ -275,7 +275,7 @@ def remove_jobs(db): def test_channel_names_are_case_insensitive( auth_client, maintainer, remove_package_versions ): - channel_name = "MyChanneL" + channel_name = "MyChannel" response = auth_client.post( "/api/channels", json={"name": channel_name, "private": False} @@ -348,7 +348,7 @@ def test_channel_names_are_case_insensitive( def test_unique_channel_names_are_case_insensitive(auth_client, maintainer): - channel_name = "MyChanneL" + channel_name = "MyChannel" response = auth_client.post( "/api/channels", json={"name": channel_name, "private": False} diff --git a/quetz/tests/api/test_main_packages.py b/quetz/tests/api/test_main_packages.py index 1723df2a..d1845545 100644 --- a/quetz/tests/api/test_main_packages.py +++ b/quetz/tests/api/test_main_packages.py @@ -901,7 +901,7 @@ def api_key(db, dao: Dao, owner, private_channel): ], ) ), - "API key with role restruction", + "API key with role restriction", ) yield key diff --git a/quetz/tests/test_auth.py b/quetz/tests/test_auth.py index 696c9589..452d7246 100644 --- a/quetz/tests/test_auth.py +++ b/quetz/tests/test_auth.py @@ -25,17 +25,17 @@ def __init__(self, db): self.keya = "akey" self.keyb = "bkey" - self.usera = User(id=uuid.uuid4().bytes, username='usera') - Profile(name='usera', user=self.usera, avatar_url='') - db.add(self.usera) + self.user_a = User(id=uuid.uuid4().bytes, username='user_a') + Profile(name='user_a', user=self.user_a, avatar_url='') + db.add(self.user_a) - self.userb = User(id=uuid.uuid4().bytes, username='userb') - Profile(name='userb', user=self.userb, avatar_url='') - db.add(self.userb) + self.user_b = User(id=uuid.uuid4().bytes, username='user_b') + Profile(name='user_b', user=self.user_b, avatar_url='') + db.add(self.user_b) - self.userc = User(id=uuid.uuid4().bytes, username='userc', role="owner") - Profile(name='userc', user=self.userc, avatar_url='') - db.add(self.userc) + self.user_c = User(id=uuid.uuid4().bytes, username='user_c', role="owner") + Profile(name='user_c', user=self.user_c, avatar_url='') + db.add(self.user_c) assert len(db.query(User).all()) == 3 @@ -43,8 +43,8 @@ def __init__(self, db): key=self.keya, time_created=date.today(), expire_at=date(2030, 1, 1), - user_id=self.usera.id, - owner_id=self.usera.id, + user_id=self.user_a.id, + owner_id=self.user_a.id, ) db.add(self.keya_obj) @@ -53,8 +53,8 @@ def __init__(self, db): key=self.keyb, time_created=date.today(), expire_at=date(2030, 1, 1), - user_id=self.userb.id, - owner_id=self.userb.id, + user_id=self.user_b.id, + owner_id=self.user_b.id, ) ) @@ -75,7 +75,7 @@ def __init__(self, db): build_string="", filename="filename.tar.bz2", info="{}", - uploader_id=self.usera.id, + uploader_id=self.user_a.id, size=101, ) self.package_version_2 = PackageVersion( @@ -89,19 +89,19 @@ def __init__(self, db): build_string="", filename="filename2.tar.bz2", info="{}", - uploader_id=self.usera.id, + uploader_id=self.user_a.id, size=101, ) self.channel_member = ChannelMember( - channel=self.channel2, user=self.usera, role='maintainer' + channel=self.channel2, user=self.user_a, role='maintainer' ) self.channel_member_userc = ChannelMember( - channel=self.channel2, user=self.userc, role='owner' + channel=self.channel2, user=self.user_c, role='owner' ) self.package_member = PackageMember( - channel=self.channel2, user=self.userc, package=self.package2, role="owner" + channel=self.channel2, user=self.user_c, package=self.package2, role="owner" ) for el in [ @@ -235,7 +235,7 @@ def test_private_channels(data, client): assert response.status_code == 200 assert len(response.json()) == 2 assert ('role', data.channel_member.role) in response.json()[0].items() - assert response.json()[0]['user']['id'] == str(uuid.UUID(bytes=data.usera.id)) + assert response.json()[0]['user']['id'] == str(uuid.UUID(bytes=data.user_a.id)) # Package # @@ -328,7 +328,7 @@ def test_private_channels(data, client): assert len(response.json()) == 1 # Package Search # - # query = f"channel:test -format:conda uploader:{data.usera.username}" + # query = f"channel:test -format:conda uploader:{data.user_a.username}" query = "channel:test" query = quote(query) response = client.get(f'/api/packages/search/?q={query}') @@ -336,7 +336,7 @@ def test_private_channels(data, client): assert len(response.json()) == 1 assert response.json()[0]['name'] == data.package1.name - # query = f"format:conda platform:linux-64,noarch uploader:{data.userb.username}" + # query = f"format:conda platform:linux-64,noarch uploader:{data.user_b.username}" # query = quote(query) # response = client.get( # f'/api/packages/search/?q={query}', headers={"X-Api-Key": data.keyb} @@ -463,7 +463,7 @@ def test_private_channels_download(db, client, data, channel_dirs): def test_create_api_key(data, client): - response = client.get(f"/api/dummylogin/{data.userc.username}") + response = client.get(f"/api/dummylogin/{data.user_c.username}") assert response.status_code == 200 response = client.post( @@ -537,7 +537,7 @@ def test_create_api_key(data, client): def test_use_wildcard_api_key_to_authenticate(data, client): - response = client.get(f"/api/dummylogin/{data.userc.username}") + response = client.get(f"/api/dummylogin/{data.user_c.username}") assert response.status_code == 200 response = client.post( @@ -629,7 +629,7 @@ def test_authorizations_with_deleted_api_key(data: Data, db): user_id = auth.get_user() - assert user_id == data.usera.id + assert user_id == data.user_a.id data.keya_obj.deleted = True @@ -643,7 +643,7 @@ def test_authorizations_with_deleted_api_key(data: Data, db): def test_authorizations_with_expired_api_key(data, client): - response = client.get(f"/api/dummylogin/{data.userc.username}") + response = client.get(f"/api/dummylogin/{data.user_c.username}") assert response.status_code == 200 response = client.post( @@ -675,8 +675,8 @@ def test_authorizations_with_api_key_no_expiry(data, client, db): key=key, time_created=date.today(), expire_at=None, - user_id=data.usera.id, - owner_id=data.usera.id, + user_id=data.user_a.id, + owner_id=data.user_a.id, ) db.add(keya_obj) db.commit() diff --git a/quetz/tests/test_mirror.py b/quetz/tests/test_mirror.py index e715a253..bfa651ca 100644 --- a/quetz/tests/test_mirror.py +++ b/quetz/tests/test_mirror.py @@ -1245,7 +1245,7 @@ def dummy_user(db): def test_create_packages_from_channeldata_update_existing( dao, dummy_user, local_channel, db, local_package ): - # update exisiting package + # update existing package channeldata = json.loads(channeldata_json) diff --git a/quetz/versionorder.py b/quetz/versionorder.py index db0ce3dd..26d7fc85 100644 --- a/quetz/versionorder.py +++ b/quetz/versionorder.py @@ -29,7 +29,7 @@ class VersionOrder: (A-Za-z0-9), separated into components by dots and underscores. Empty segments (i.e. two consecutive dots, a leading/trailing underscore) are not permitted. An optional epoch number - an integer - followed by '!' - can preceed the actual version string + followed by '!' - can proceed the actual version string (this is useful to indicate a change in the versioning scheme itself). Version comparison is case-insensitive.