python-poetry / python-poetry/poetry

Credentials passed via `POETRY_HTTP_BASIC_<repo>_USERNAME` aren't found when the repository name contains an underscore `_`

Open
#7,156 0 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

kind/bug status/triage
Dominant language
Python
Stars
34.3k
Forks
2.5k
Avg merge
2d 19h
Merged PRs (30d)
30

Description

  • Poetry version: Poetry (version 1.2.2)
  • Python version: Python 3.10.6
  • OS version and name: Alpine Linux v3.16
  • pyproject.toml: Independent of pyproject.toml settings
  • I am on the latest stable Poetry version, installed using a recommended method.
  • I have searched the issues of this repo and believe that this is not a duplicate.
  • I have consulted the FAQ and blog for any relevant entries or release notes.
  • If an exception occurs when executing a command, I executed it again in debug mode (-vvv option) and have included the output below.

Issue

poetry publish no longer seems to correctly parse username/password for repositories provided via environment variables when the repository name contains an underscore.

Try publishing to a private repository named my_repo like this:

export POETRY_REPOSITORIES_MY_REPO_URL='your url here'
export POETRY_HTTP_BASIC_MY_REPO_USERNAME='example user'
export POETRY_HTTP_BASIC_MY_REPO_PASSWORD='example password'
poetry publish --repository=my_repo

You will get a 401 unauthorized error. Adding a debug print in upload() at poetry/publishing/uploader.py:309 shows that the URL is correct, but username/password are None, suggesting that the environment variable didn't get parsed correctly.

Now try with the private repository named myrepo:

export POETRY_REPOSITORIES_MYREPO_URL='your url here'
export POETRY_HTTP_BASIC_MYREPO_USERNAME='example user'
export POETRY_HTTP_BASIC_MYREPO_PASSWORD='example password'
poetry publish --repository=myrepo

This one works. It looks like removing the underscore made a difference.

Now the fun part, try the first case again, but name the repository in the command my-repo with a dash:

export POETRY_REPOSITORIES_MY_REPO_URL='your url here'
export POETRY_HTTP_BASIC_MY_REPO_USERNAME='example user'
export POETRY_HTTP_BASIC_MY_REPO_PASSWORD='example password'
poetry publish --repository=my-repo

That one works too.

The failing case seems to work in 1.1.15, but stops working in 1.2.0.

Based on these results, it sounds like there is some ambiguity between my-repo and my_repo, as both names become MY_REPO for environment variable lookup. But this shouldn't make a difference unless both my-repo and my_repo exist, or if something tries to convert the variable name back into a repository name.

I've seen a separate issue discussing whether special characters in repository names should be supported. I think this case is different because the underscore _ is less of a special character than -, especially in the realm of Python.

The exception (not really relevant, but including anyway):

Loading configuration file /tmp/build/put/********/poetry.toml

[keyring.backend] Loading KWallet
[keyring.backend] Loading SecretService
[keyring.backend] Loading Windows
[keyring.backend] Loading chainer
[keyring.backend] Loading libsecret
[keyring.backend] Loading macOS
No suitable keyring backend found
No suitable keyring backends were found
Keyring is not available, credentials will be stored and retrieved from configuration files as plaintext.
Publishing ******** to my_repo
 - Uploading ********-py3-none-any.whl 0%[urllib3.connectionpool] Starting new HTTPS connection (1): ********
 - Uploading ********-py3-none-any.whl 100%[urllib3.connectionpool] ******** "POST ******** HTTP/1.1" 401 None
 - Uploading ********-py3-none-any.whl FAILED

  Stack trace:

  1  /usr/local/lib/python3.10/site-packages/poetry/publishing/uploader.py:287 in _upload_file
      285│                     bar.display()
      286│                 else:
    → 287│                     resp.raise_for_status()
      288│             except (requests.ConnectionError, requests.HTTPError) as e:
      289│                 if self._io.output.is_decorated():

  HTTPError

  401 Client Error: Unauthorized for url: ********

  at /usr/local/lib/python3.10/site-packages/requests/models.py:1021 in raise_for_status
      1017│                 f"{self.status_code} Server Error: {reason} for url: {self.url}"
      1018│             )
      1019│ 
      1020│         if http_error_msg:
    → 1021│             raise HTTPError(http_error_msg, response=self)
      1022│ 
      1023│     def close(self):
      1024│         """Releases the connection back to the pool. Once this method has been
      1025│         called the underlying ``raw`` object must not be accessed again.  

The following error occurred when trying to handle this error:


  Stack trace:

  11  /usr/local/lib/python3.10/site-packages/cleo/application.py:329 in run
       327│ 
       328│             try:
     → 329│                 exit_code = self._run(io)
       330│             except Exception as e:
       331│                 if not self._catch_exceptions:

  10  /usr/local/lib/python3.10/site-packages/poetry/console/application.py:185 in _run
       183│         self._load_plugins(io)
       184│ 
     → 185│         exit_code: int = super()._run(io)
       186│         return exit_code
       187│ 

   9  /usr/local/lib/python3.10/site-packages/cleo/application.py:423 in _run
       421│             io.input.set_stream(stream)
       422│ 
     → 423│         exit_code = self._run_command(command, io)
       424│         self._running_command = None
       425│ 

   8  /usr/local/lib/python3.10/site-packages/cleo/application.py:465 in _run_command
       463│ 
       464│         if error is not None:
     → 465│             raise error
       466│ 
       467│         return event.exit_code

   7  /usr/local/lib/python3.10/site-packages/cleo/application.py:449 in _run_command
       447│ 
       448│             if event.command_should_run():
     → 449│                 exit_code = command.run(io)
       450│             else:
       451│                 exit_code = ConsoleCommandEvent.RETURN_CODE_DISABLED

   6  /usr/local/lib/python3.10/site-packages/cleo/commands/base_command.py:119 in run
       117│         io.input.validate()
       118│ 
     → 119│         status_code = self.execute(io)
       120│ 
       121│         if status_code is None:

   5  /usr/local/lib/python3.10/site-packages/cleo/commands/command.py:83 in execute
        81│ 
        82│         try:
     →  83│             return self.handle()
        84│         except KeyboardInterrupt:
        85│             return 1

   4  /usr/local/lib/python3.10/site-packages/poetry/console/commands/publish.py:82 in handle
        80│         )
        81│ 
     →  82│         publisher.publish(
        83│             self.option("repository"),
        84│             self.option("username"),

   3  /usr/local/lib/python3.10/site-packages/poetry/publishing/publisher.py:86 in publish
        84│         )
        85│ 
     →  86│         self._uploader.upload(
        87│             url,
        88│             cert=resolved_cert,

   2  /usr/local/lib/python3.10/site-packages/poetry/publishing/uploader.py:125 in upload
       123│ 
       124│         try:
     → 125│             self._upload(session, url, dry_run, skip_existing)
       126│         finally:
       127│             session.close()

   1  /usr/local/lib/python3.10/site-packages/poetry/publishing/uploader.py:216 in _upload
       214│             # TODO: Check existence
       215│ 
     → 216│             self._upload_file(session, url, file, dry_run, skip_existing)
       217│ 
       218│     def _upload_file(

  UploadError

  HTTP Error 401: Unauthorized | b''

  at /usr/local/lib/python3.10/site-packages/poetry/publishing/uploader.py:293 in _upload_file
      289│                 if self._io.output.is_decorated():
      290│                     self._io.overwrite(
      291│                         f" - Uploading {file.name} FAILED"
      292│                     )
    → 293│                 raise UploadError(e)
      294│             finally:
      295│                 self._io.write_line("")
      296│ 
      297│     def _register(self, session: requests.Session, url: str) -> requests.Response:

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at poetry/publishing/uploader.py around upload(), where the report observed missing credentials, and trace how POETRY_HTTP_BASIC_* environment variables are resolved for the selected repository. Reproduce with repository names my_repo, myrepo, and my-repo; done means credentials are found for underscore-containing names and publishing no longer returns 401.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.