From 551175ed026752df5ffabab7eeec448920e62b6d Mon Sep 17 00:00:00 2001 From: Daniel Peng Date: Thu, 21 Aug 2025 10:21:18 -0400 Subject: [PATCH 1/7] docs: Add authentication instructions for Polaris Hub API key in README.md, publish branch --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 0f470ea4..b8b0a1a1 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,14 @@ You can also use pip: pip install polaris-lib ``` +## Authentication + +Set an API key in your environment variables file for programmatic access to the Polaris Hub. + +```bash +POLARIS_API_KEY="" +``` + ## Development lifecycle ### Setup dev environment From 2c7eb5a86e550c8e33c1cf061a750d4b8e9577ec Mon Sep 17 00:00:00 2001 From: Daniel Peng Date: Thu, 21 Aug 2025 10:53:19 -0400 Subject: [PATCH 2/7] refactor: Remove ExternalAuthClient and related documentation --- docs/api/hub.external_client.md | 5 - mkdocs.yml | 1 - polaris/hub/external_client.py | 160 -------------------------------- polaris/hub/oauth.py | 16 ---- pyproject.toml | 1 - 5 files changed, 183 deletions(-) delete mode 100644 docs/api/hub.external_client.md delete mode 100644 polaris/hub/external_client.py diff --git a/docs/api/hub.external_client.md b/docs/api/hub.external_client.md deleted file mode 100644 index edde82ba..00000000 --- a/docs/api/hub.external_client.md +++ /dev/null @@ -1,5 +0,0 @@ -::: polaris.hub.external_client.ExternalAuthClient - options: - merge_init_into_class: true - filters: ["!create_authorization_url", "!fetch_token"] ---- diff --git a/mkdocs.yml b/mkdocs.yml index 08f62438..fca6ebc5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -32,7 +32,6 @@ nav: - Evaluation: api/evaluation.md - Hub: - Client: api/hub.client.md - - External Auth Client: api/hub.external_client.md - Additional: - Base classes: api/base.md - Types: api/utils.types.md diff --git a/polaris/hub/external_client.py b/polaris/hub/external_client.py deleted file mode 100644 index 49098313..00000000 --- a/polaris/hub/external_client.py +++ /dev/null @@ -1,160 +0,0 @@ -import logging -import webbrowser -from typing import Literal, Optional, TypeAlias - -from authlib.common.security import generate_token -from authlib.integrations.base_client import OAuthError -from authlib.integrations.httpx_client import OAuth2Client -from authlib.oauth2 import OAuth2Error, TokenAuth -from authlib.oauth2.rfc6749 import OAuth2Token - -from polaris.hub.oauth import ExternalCachedTokenAuth -from polaris.hub.settings import PolarisHubSettings -from polaris.utils.errors import PolarisHubError, PolarisUnauthorizedError - -logger = logging.getLogger(__name__) - -Scope: TypeAlias = Literal["read", "write"] - - -class ExternalAuthClient(OAuth2Client): - """ - This authentication client is used to obtain OAuth 2 tokens from Polaris's external OAuth2 server. - These can in turn be used to obtain Polaris Hub tokens. - - Note: Internal use - This class is intended for internal use by the `PolarisHubClient` class, and you should not have to - interact with it directly. - """ - - def __init__( - self, - settings: PolarisHubSettings, - cache_auth_token: bool = True, - **kwargs: dict, - ): - """ - Args: - settings: A `PolarisHubSettings` instance. - cache_auth_token: Whether to cache the auth token to a file. - **kwargs: Additional keyword arguments passed to the authlib `OAuth2Client` constructor. - """ - self._user_info = None - - # We cache the auth token by default, but allow the user to disable this. - self.token_auth_class = ExternalCachedTokenAuth if cache_auth_token else TokenAuth - - self.settings = settings - - self.code_verifier = generate_token(48) - - super().__init__( - # OAuth2Client - client_id=self.settings.client_id, - redirect_uri=self.settings.callback_url, - scope=self.settings.scopes, - token_endpoint=self.settings.token_fetch_url, - code_challenge_method="S256", - # httpx.Client - timeout=self.settings.default_timeout, - cert=self.settings.ca_bundle, - # Extra - **kwargs, - ) - - def create_authorization_url(self, **kwargs) -> tuple[str, Optional[str]]: - """Light wrapper to automatically pass in the right URL.""" - return super().create_authorization_url( - url=self.settings.authorize_url, code_verifier=self.code_verifier, **kwargs - ) - - def fetch_token(self, **kwargs) -> dict: - """Light wrapper to automatically pass in the right URL""" - try: - return super().fetch_token( - url=self.settings.token_fetch_url, code_verifier=self.code_verifier, **kwargs - ) - except OAuth2Error as error: - raise PolarisHubError( - message=f"Could not obtain a token from the external OAuth2 server. Error was: {error.error} - {error.description}" - ) from error - - def ensure_active_token(self, token: OAuth2Token | None = None) -> bool: - try: - # This won't be needed with if we set a lower bound for authlib: >=1.3.2 - # See https://github.com/lepture/authlib/pull/625 - # As of now, this latest version is not available on Conda though. - token = token or self.token - return super().ensure_active_token(token) if token else False - except OAuthError: - # The refresh attempt can fail with this error - return False - - @property - def user_info(self) -> dict: - """ - Get information about the currently logged-in user through the OAuth2 User Info flow.""" - - # NOTE (cwognum): We override the default `auth` and `headers` argument, since - # the defaults trigger a 530 error (Cloudflare) due to the header ordering. - # Because of this, we also have to copy some code from the base `request` method to - # make auto-refresh a token if needed. For more info, see: https://stackoverflow.com/a/62687390 - - try: - if self.token is None or not self.ensure_active_token(self.token): - raise PolarisUnauthorizedError - except OAuthError: - raise PolarisUnauthorizedError - - if self._user_info is None: - user_info = self.get( - self.settings.user_info_url, - auth=None, # type: ignore - headers={"authorization": f"Bearer {self.token['access_token']}"}, - ) - user_info.raise_for_status() - self._user_info = user_info.json() - - return self._user_info - - def interactive_login(self, overwrite: bool = False, auto_open_browser: bool = True): - """ - Login to the Polaris Hub using an interactive flow, through a Web browser. - - Warning: Headless authentication - It is currently not possible to log in to the Polaris Hub without a browser. - See [this GitHub issue](https://github.com/polaris-hub/polaris/issues/30) for more info. - - Args: - overwrite: Whether to overwrite the current token if the user is already logged in. - auto_open_browser: Whether to automatically open the browser to visit the authorization URL. - """ - - # Check if the user is already logged in - token_is_valid = self.token is not None and self.ensure_active_token(self.token) - if token_is_valid and not overwrite: - try: - info = self.user_info - logger.info( - f"You are already logged in to the Polaris Hub as {info['email']}. Set `overwrite=True` to force re-authentication." - ) - return - except PolarisUnauthorizedError: - pass - - # Step 1: Redirect user to the authorization URL - authorization_url, _ = self.create_authorization_url() - - if auto_open_browser: - logger.info(f"Your browser has been opened to visit:\n{authorization_url}\n") - webbrowser.open_new_tab(authorization_url) - else: - logger.info(f"Please visit the following URL:\n{authorization_url}\n") - - # Step 2: After user grants permission, we'll get the authorization code through the callback URL - authorization_code = input("Please enter the authorization token: ") - - # Step 3: Exchange authorization code for an access token - self.fetch_token(code=authorization_code, grant_type="authorization_code") - - logger.info(f"Successfully authenticated to the Polaris Hub as `{self.user_info['email']}`!") diff --git a/polaris/hub/oauth.py b/polaris/hub/oauth.py index 550d2aa7..87216119 100644 --- a/polaris/hub/oauth.py +++ b/polaris/hub/oauth.py @@ -42,22 +42,6 @@ def set_token(self, token: dict): self.token_cache_path.write_text(json.dumps(token)) -class ExternalCachedTokenAuth(CachedTokenAuth): - """ - Cached token for external authentication. - """ - - def __init__( - self, - token: dict | None = None, - token_placement="header", - client=None, - cache_dir=DEFAULT_CACHE_DIR, - filename="external_auth_token.json", - ): - super().__init__(token, token_placement, client, cache_dir, filename) - - class ArtifactPaths(BaseModel): """ Base model class for artifact paths. diff --git a/pyproject.toml b/pyproject.toml index 9094961f..cc406599 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -149,7 +149,6 @@ omit = [ # We cannot yet test the interaction with the Hub. # See e.g. https://github.com/polaris-hub/polaris/issues/30 "polaris/hub/client.py", - "polaris/hub/external_client.py", "polaris/hub/settings.py", "polaris/hub/oauth.py", "polaris/hub/storage.py", From 8044d4c8a723d971a7ce0fa51e1af7f5b8f8706d Mon Sep 17 00:00:00 2001 From: Daniel Peng Date: Thu, 21 Aug 2025 11:06:23 -0400 Subject: [PATCH 3/7] docs: Update documentation to introduce POLARIS_API_KEY, auth/sign-up --- .github/workflows/test.yml | 6 ++--- docs/quickstart.md | 26 +++++++++++++--------- docs/tutorials/submit_to_benchmark.ipynb | 2 +- docs/tutorials/submit_to_competition.ipynb | 2 +- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cdcce47a..7931cdff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,8 +45,7 @@ jobs: - name: Run tests run: uv run pytest env: - POLARIS_USERNAME: ${{ secrets.POLARIS_USERNAME }} - POLARIS_PASSWORD: ${{ secrets.POLARIS_PASSWORD }} + POLARIS_API_KEY: ${{ secrets.POLARIS_API_KEY }} - name: Test CLI run: uv run polaris --help @@ -90,8 +89,7 @@ jobs: - name: Run pytest run: pytest env: - POLARIS_USERNAME: ${{ secrets.POLARIS_USERNAME }} - POLARIS_PASSWORD: ${{ secrets.POLARIS_PASSWORD }} + POLARIS_API_KEY: ${{ secrets.POLARIS_API_KEY }} - name: Test CLI run: polaris --help diff --git a/docs/quickstart.md b/docs/quickstart.md index 5a82c792..17b22366 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -25,21 +25,25 @@ Polaris explicitly distinguished **datasets** and **benchmarks**. One dataset can therefore be associated with multiple benchmarks. ## Login -To submit or upload artifacts to the [Polaris Hub](https://polarishub.io/) from the client, you must first authenticate yourself. If you don't have an account yet, you can create one [here](https://polarishub.io/sign-up). +To submit or upload artifacts to the [Polaris Hub](https://polarishub.io/) from the client, you must first authenticate yourself. If you don't have an account yet, you can create one [here](https://polarishub.io/auth/sign-up). -You can do this via the following command in your terminal: +Use an API key for programmatic access. Create one from your Hub settings page under the "Security" tab and set it as an environment variable in your .env: ```bash -polaris login -``` - -or in Python: -```py -from polaris.hub.client import PolarisHubClient - -with PolarisHubClient() as client: - client.login() +POLARIS_API_KEY="" ``` +- and run the following command in your terminal: + ```bash + polaris login + ``` + +- or in Python: + ```py + from polaris.hub.client import PolarisHubClient + + with PolarisHubClient() as client: + client.login() + ``` ## Benchmark API To get started, we will submit a result to the [`polaris/hello-world-benchmark`](https://polarishub.io/benchmarks/polaris/hello-world-benchmark). diff --git a/docs/tutorials/submit_to_benchmark.ipynb b/docs/tutorials/submit_to_benchmark.ipynb index 53c5c93a..4b93b6ea 100644 --- a/docs/tutorials/submit_to_benchmark.ipynb +++ b/docs/tutorials/submit_to_benchmark.ipynb @@ -50,7 +50,7 @@ "metadata": {}, "source": [ "## Login\n", - "We first need to authenticate ourselves using our Polaris account. If you don't have an account yet, you can create one [here](https://polarishub.io/sign-up)." + "We first need to authenticate ourselves using our Polaris account. If you don't have an account yet, you can create one [here](https://polarishub.io/auth/sign-up)." ] }, { diff --git a/docs/tutorials/submit_to_competition.ipynb b/docs/tutorials/submit_to_competition.ipynb index 89779cc9..0d89aa0e 100644 --- a/docs/tutorials/submit_to_competition.ipynb +++ b/docs/tutorials/submit_to_competition.ipynb @@ -52,7 +52,7 @@ "metadata": {}, "source": [ "## Login\n", - "As before, we first need to authenticate ourselves using our Polaris account. If you don't have an account yet, you can create one [here](https://polarishub.io/sign-up)." + "As before, we first need to authenticate ourselves using our Polaris account. If you don't have an account yet, you can create one [here](https://polarishub.io/auth/sign-up)." ] }, { From 64bdd98940d1fc2be3c425bc2e1b03f74d7a648d Mon Sep 17 00:00:00 2001 From: Daniel Peng Date: Thu, 21 Aug 2025 11:15:28 -0400 Subject: [PATCH 4/7] feat: refactor PolarisHubClient authentication by removing external client (username/password) and enabling API key support --- polaris/cli.py | 7 +--- polaris/hub/client.py | 81 ++++++++++++++++++----------------------- polaris/hub/settings.py | 33 ++--------------- 3 files changed, 40 insertions(+), 81 deletions(-) diff --git a/polaris/cli.py b/polaris/cli.py index a796009a..c2a62dc5 100644 --- a/polaris/cli.py +++ b/polaris/cli.py @@ -16,19 +16,16 @@ def login( client_env_file: Annotated[ str, typer.Option(help="Environment file to overwrite the default environment variables") ] = ".env", - auto_open_browser: Annotated[ - bool, typer.Option(help="Whether to automatically open the link in a browser to retrieve the token") - ] = True, overwrite: Annotated[ bool, typer.Option(help="Whether to overwrite the access token if you are already logged in") ] = False, ): """Authenticate to the Polaris Hub. - This CLI will use the OAuth2 protocol to gain token-based access to the Polaris Hub API. + Set POLARIS_API_KEY in your environment and run this command to cache a Hub token. """ client = PolarisHubClient(settings=PolarisHubSettings(_env_file=client_env_file)) - client.login(auto_open_browser=auto_open_browser, overwrite=overwrite) + client.login(overwrite=overwrite) @app.command(hidden=True) diff --git a/polaris/hub/client.py b/polaris/hub/client.py index 3345bcc0..95d2ce89 100644 --- a/polaris/hub/client.py +++ b/polaris/hub/client.py @@ -23,7 +23,6 @@ from polaris.dataset import DatasetV1, DatasetV2 from polaris.evaluate import BenchmarkResultsV1, BenchmarkResultsV2, CompetitionPredictions from polaris.prediction._predictions_v2 import BenchmarkPredictionsV2 -from polaris.hub.external_client import ExternalAuthClient from polaris.hub.oauth import CachedTokenAuth from polaris.hub.settings import PolarisHubSettings from polaris.hub.storage import StorageSession @@ -114,66 +113,49 @@ def __init__( **kwargs, ) - # We use an external client to get an auth token that can be exchanged for a Polaris Hub token - self.external_client = ExternalAuthClient( - settings=self.settings, cache_auth_token=cache_auth_token, **kwargs - ) - def __enter__(self: Self) -> Self: super().__enter__() return self - @property - def has_user_password(self) -> bool: - return bool(self.settings.username and self.settings.password) - def _prepare_token_endpoint_body(self, body, grant_type, **kwargs): """ Override to support required fields for the token exchange grant type. See https://datatracker.ietf.org/doc/html/rfc8693#name-request """ - if grant_type == "urn:ietf:params:oauth:grant-type:token-exchange": - kwargs.update( - { - "subject_token": self.external_client.token["access_token"], - "subject_token_type": "urn:ietf:params:oauth:token-type:access_token", - "requested_token_type": "urn:ietf:params:oauth:token-type:jwt", - } - ) return super()._prepare_token_endpoint_body(body, grant_type, **kwargs) def ensure_active_token(self, token: OAuth2Token | None = None) -> bool: """ Override the active check to trigger a refetch of the token if it is not active. """ - # This won't be needed with if we set a lower bound for authlib: >=1.3.2 - # See https://github.com/lepture/authlib/pull/625 - # As of now, this latest version is not available on Conda though. token = token or self.token is_active = super().ensure_active_token(token) if token else False if is_active: return True - # Check if external token is still valid, or we're using password auth - if not (self.has_user_password or self.external_client.ensure_active_token()): - return False + # If we have an API key, use it to get a new Hub JWT + if self.settings.api_key: + self.token = self.fetch_token() + return True - # If so, use it to get a new Hub token - self.token = self.fetch_token() - return True + return False def fetch_token(self, **kwargs): """ - Handles the optional support for password grant type, and provide better error messages. + Fetch a Hub JWT using the API key grant. """ try: - return super().fetch_token( - username=self.settings.username, - password=self.settings.password, - grant_type="password" - if self.has_user_password - else "urn:ietf:params:oauth:grant-type:token-exchange", - **kwargs, + if self.settings.api_key: + return super().fetch_token( + grant_type="api_key", + api_key=self.settings.api_key, + **kwargs, + ) + # No API key set: raise a clear error + raise PolarisHubError( + message=( + "No API key configured. Please set POLARIS_API_KEY or pass settings.api_key to PolarisHubClient." + ) ) except (OAuthError, OAuth2Error) as error: raise PolarisHubError( @@ -190,6 +172,13 @@ def _base_request_to_hub(self, url: str, method: str, withhold_token: bool, **kw response.raise_for_status() return response except HTTPStatusError as error: + # 401: try one transparent retry if we can refresh via API key + if error.response.status_code == 401 and not withhold_token and bool(self.settings.api_key): + self.token = self.fetch_token() + response = self.request(url=url, method=method, withhold_token=withhold_token, **kwargs) + response.raise_for_status() + return response + # If JSON is included in the response body, we retrieve it and format it for output. If not, we fall back to # retrieving plain text from the body. 500 errors will not have a JSON response. try: @@ -229,20 +218,20 @@ def request(self, method, url, withhold_token=False, auth=httpx.USE_CLIENT_DEFAU except (MissingTokenError, InvalidTokenError, OAuthError) as error: raise PolarisUnauthorizedError() from error - def login(self, overwrite: bool = False, auto_open_browser: bool = True): - """Login to the Polaris Hub using the OAuth2 protocol. + def login(self, overwrite: bool = False): + """Login to the Polaris Hub. - Warning: Headless authentication - It is currently not possible to login to the Polaris Hub without a browser. - See [this Github issue](https://github.com/polaris-hub/polaris/issues/30) for more info. - - Args: - overwrite: Whether to overwrite the current token if the user is already logged in. - auto_open_browser: Whether to automatically open the browser to visit the authorization URL. + If an API key is configured, a Hub JWT will be fetched and cached; otherwise an error is raised. """ - if overwrite or self.token is None or not self.ensure_active_token(): - self.external_client.interactive_login(overwrite=overwrite, auto_open_browser=auto_open_browser) + if self.settings.api_key: + # Force-fetch and cache a Hub JWT self.token = self.fetch_token() + elif overwrite or self.token is None or not self.ensure_active_token(): + raise PolarisHubError( + message=( + "No API key configured. Please set POLARIS_API_KEY in your environment variables file." + ) + ) logger.info("You are successfully logged in to the Polaris Hub.") diff --git a/polaris/hub/settings.py b/polaris/hub/settings.py index 042b7d59..164b4c84 100644 --- a/polaris/hub/settings.py +++ b/polaris/hub/settings.py @@ -7,29 +7,17 @@ class PolarisHubSettings(BaseSettings): - """Settings for the OAuth2 Polaris Hub API Client. - - Info: Secrecy of these settings - Since the Polaris Hub uses PCKE (Proof Key for Code Exchange) for OAuth2, - these values thus do not have to be kept secret. - See [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) for more info. + """Settings for the Polaris Hub API Client. Attributes: hub_url: The URL to the main page of the Polaris Hub. api_url: The URL to the main entrypoint of the Polaris API. - authorize_url: The URL of the OAuth2 authorization endpoint. - callback_url: The URL to which the user is redirected after authorization. - token_fetch_url: The URL of the OAuth2 token endpoint. - user_info_url: The URL of the OAuth2 user info endpoint. - scopes: The OAuth2 scopes that are requested. - client_id: The OAuth2 client ID. ca_bundle: The path to a CA bundle file for requests. Allows for custom SSL certificates to be used. default_timeout: The default timeout for requests. hub_token_url: The URL of the Polaris Hub token endpoint. A default value is generated based on the Hub URL, and this should not need to be overridden. - username: The username for the Polaris Hub, for the optional password-based authentication. - password: The password for the specified username. + api_key: The API key used for programmatic authentication to the Hub. """ # Configuration of the pydantic model @@ -44,16 +32,7 @@ class PolarisHubSettings(BaseSettings): # Hub authentication settings hub_token_url: HttpUrlString | None = None - username: str | None = None - password: str | None = None - - # External authentication settings - authorize_url: HttpUrlString = "https://clerk.polarishub.io/oauth/authorize" - callback_url: HttpUrlString | None = None - token_fetch_url: HttpUrlString = "https://clerk.polarishub.io/oauth/token" - user_info_url: HttpUrlString = "https://clerk.polarishub.io/oauth/userinfo" - scopes: str = "profile email" - client_id: str = "agQP2xVM6JqMHvGc" + api_key: str | None = None # Networking settings ca_bundle: str | bool | None = None @@ -65,12 +44,6 @@ def validate_api_url(cls, v, info: ValidationInfo): v = urljoin(str(info.data["hub_url"]), "/api") return v - @field_validator("callback_url", mode="before") - def validate_callback_url(cls, v, info: ValidationInfo): - if v is None: - v = urljoin(str(info.data["hub_url"]), "/oauth2/callback") - return v - @field_validator("hub_token_url", mode="before") def populate_hub_token_url(cls, v, info: ValidationInfo): if v is None: From 79cbc0cbcadba278402c993ddfaeca0601a457dd Mon Sep 17 00:00:00 2001 From: Daniel Peng Date: Thu, 21 Aug 2025 11:19:38 -0400 Subject: [PATCH 5/7] Untrack test.yml due to scope perms --- .github/workflows/test.yml | 95 -------------------------------------- 1 file changed, 95 deletions(-) delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 7931cdff..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: test - -on: - push: - branches: [ "main" ] - tags: [ "*" ] - pull_request: - branches: - - "*" - - "!gh-pages" - schedule: - - cron: "0 4 * * MON" - -concurrency: - group: "test-${{ github.ref }}" - cancel-in-progress: true - -jobs: - test-uv: - strategy: - fail-fast: false - matrix: - python-version: [ "3.10", "3.11", "3.12" ] - os: [ "ubuntu-latest", "macos-latest", "windows-latest" ] - - runs-on: ${{ matrix.os }} - timeout-minutes: 30 - - defaults: - run: - shell: bash -l {0} - - name: PyPi os=${{ matrix.os }} - python=${{ matrix.python-version }} - - steps: - - name: Checkout the code - uses: actions/checkout@v4 - - - name: Install uv - uses: astral-sh/setup-uv@v5 - - - name: Install the project - run: uv sync --all-groups --python ${{ matrix.python-version }} - - - name: Run tests - run: uv run pytest - env: - POLARIS_API_KEY: ${{ secrets.POLARIS_API_KEY }} - - - name: Test CLI - run: uv run polaris --help - - - name: Test building the doc - run: uv run mkdocs build - - test-conda: - strategy: - fail-fast: false - matrix: - python-version: [ "3.10", "3.11", "3.12" ] - os: [ "ubuntu-latest", "macos-latest", "windows-latest" ] - - runs-on: ${{ matrix.os }} - timeout-minutes: 30 - - defaults: - run: - shell: bash -l {0} - - name: Conda os=${{ matrix.os }} - python=${{ matrix.python-version }} - - steps: - - name: Checkout the code - uses: actions/checkout@v4 - - - name: Setup mamba - uses: mamba-org/setup-micromamba@v2 - with: - environment-file: env.yml - environment-name: polaris_testing_env - cache-environment: true - cache-downloads: true - create-args: >- - python=${{ matrix.python-version }} - - - name: Install library - run: python -m pip install --no-deps . - - - name: Run pytest - run: pytest - env: - POLARIS_API_KEY: ${{ secrets.POLARIS_API_KEY }} - - - name: Test CLI - run: polaris --help From dd2ef5fc87c4fc96f14d359d792f5db7e3dc6b3c Mon Sep 17 00:00:00 2001 From: Daniel Peng Date: Thu, 21 Aug 2025 11:21:48 -0400 Subject: [PATCH 6/7] add test.yml back --- .github/workflows/test.yml | 97 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..cdcce47a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,97 @@ +name: test + +on: + push: + branches: [ "main" ] + tags: [ "*" ] + pull_request: + branches: + - "*" + - "!gh-pages" + schedule: + - cron: "0 4 * * MON" + +concurrency: + group: "test-${{ github.ref }}" + cancel-in-progress: true + +jobs: + test-uv: + strategy: + fail-fast: false + matrix: + python-version: [ "3.10", "3.11", "3.12" ] + os: [ "ubuntu-latest", "macos-latest", "windows-latest" ] + + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + + defaults: + run: + shell: bash -l {0} + + name: PyPi os=${{ matrix.os }} - python=${{ matrix.python-version }} + + steps: + - name: Checkout the code + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: Install the project + run: uv sync --all-groups --python ${{ matrix.python-version }} + + - name: Run tests + run: uv run pytest + env: + POLARIS_USERNAME: ${{ secrets.POLARIS_USERNAME }} + POLARIS_PASSWORD: ${{ secrets.POLARIS_PASSWORD }} + + - name: Test CLI + run: uv run polaris --help + + - name: Test building the doc + run: uv run mkdocs build + + test-conda: + strategy: + fail-fast: false + matrix: + python-version: [ "3.10", "3.11", "3.12" ] + os: [ "ubuntu-latest", "macos-latest", "windows-latest" ] + + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + + defaults: + run: + shell: bash -l {0} + + name: Conda os=${{ matrix.os }} - python=${{ matrix.python-version }} + + steps: + - name: Checkout the code + uses: actions/checkout@v4 + + - name: Setup mamba + uses: mamba-org/setup-micromamba@v2 + with: + environment-file: env.yml + environment-name: polaris_testing_env + cache-environment: true + cache-downloads: true + create-args: >- + python=${{ matrix.python-version }} + + - name: Install library + run: python -m pip install --no-deps . + + - name: Run pytest + run: pytest + env: + POLARIS_USERNAME: ${{ secrets.POLARIS_USERNAME }} + POLARIS_PASSWORD: ${{ secrets.POLARIS_PASSWORD }} + + - name: Test CLI + run: polaris --help From 809c93f16098d565fa24fe87189dfbc4c637365c Mon Sep 17 00:00:00 2001 From: Daniel Peng Date: Thu, 21 Aug 2025 12:43:23 -0400 Subject: [PATCH 7/7] chore: Update fallback version in pyproject.toml for Python jobs --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cc406599..79d37a5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,7 +122,7 @@ Documentation = "https://polaris-hub.github.io/polaris/" include-package-data = true [tool.setuptools_scm] -fallback_version = "0.0.0.dev1" +fallback_version = "0.0.0.dev0" [tool.setuptools.packages.find] where = ["."]