From 3c4133913ea265abab74875bfb19438920c0519f Mon Sep 17 00:00:00 2001 From: Lucas Hardt Date: Thu, 25 Sep 2025 21:54:17 +0200 Subject: [PATCH 1/5] Replace black and isort with ruff --- .github/workflows/lint.yml | 20 +++++++++++++++ .github/workflows/test.yml | 52 ++------------------------------------ .pre-commit-config.yaml | 16 +++++------- pyproject.toml | 36 +++++++++++++++----------- 4 files changed, 50 insertions(+), 74 deletions(-) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..8e72296b --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,20 @@ +name: lint + +on: + push: + pull_request: + types: [ opened, reopened, synchronize ] + +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Get Ruff Version + id: ruff-version + run: echo "version=$(grep -Po '(?<=ruff==)[0-9]+\.[0-9]+\.[0-9]+' pyproject.toml)" >> $GITHUB_OUTPUT + + - uses: astral-sh/ruff-action@v3 + with: + version: ${{ steps.ruff-version.outputs.version }} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 778b7874..40259601 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,5 @@ # This workflow will ensure that the pushed contents to the repo -# are not majorly breaking, and they comply with black standards. +# are not majorly breaking. name: Pytests @@ -34,52 +34,4 @@ jobs: shell: bash env: TEST_API_KEY: ${{ secrets.TEST_API_KEY }} - run: python -m pytest --cov=fortnite_api --import-mode=importlib -vs tests/ - - black: - name: Black Formatting Check - runs-on: ubuntu-latest - - # Checkout the repository - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python 3.9 - uses: actions/setup-python@v6 - with: - python-version: "3.9" - cache: "pip" # Cache the pip packages to speed up the workflow - - - name: Install Dependencies and Project - run: | - python -m pip install -U pip setuptools - pip install -U -r requirements.txt - pip install -e .[dev] - - - name: Run Black Check - run: black --check --diff --verbose fortnite_api - - isort: - name: Isort Formatting Check - runs-on: ubuntu-latest - - # Checkout the repository - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python 3.9 - uses: actions/setup-python@v6 - with: - python-version: "3.9" - cache: "pip" # Cache the pip packages to speed up the workflow - - - name: Install Dependencies and Project - run: | - python -m pip install -U pip setuptools - pip install -U -r requirements.txt - pip install -e .[dev] - - - name: Run Isort Check - run: isort --check --diff fortnite_api \ No newline at end of file + run: python -m pytest --cov=fortnite_api --import-mode=importlib -vs tests/ \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1d619bd5..b32a9530 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,10 @@ # Black formatter pre commit hook repos: - - repo: https://github.com/psf/black - rev: 24.10.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.13.2 hooks: - - id: black - - - repo: https://github.com/pycqa/isort - rev: 5.13.2 - hooks: - - id: isort - name: isort (python) \ No newline at end of file + # Run the linter. + - id: ruff-check + args: [ --fix ] + # Run the formatter. + - id: ruff-format \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f084c7f3..faed0b1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ docs = [ 'furo', 'sphinx-copybutton', ] -dev = ['black', 'isort', 'discord.py', 'pyright', 'pre-commit'] +dev = ['ruff==v0.13.2', 'discord.py', 'pyright', 'pre-commit'] speed = ['orjson'] [project.urls] @@ -87,13 +87,6 @@ asyncio_mode = "strict" testpaths = ["tests"] addopts = "--import-mode=importlib" -# Black formatting - -[tool.black] -line-length = 125 -skip-string-normalization = true -force-exclude = "LICENSE|requirements.txt|pyproject.toml|README.md" - # Pyright configuration [tool.pyright] @@ -105,11 +98,24 @@ reportPrivateUsage = "none" exclude = ["**/__pycache__", "build", "dist", "docs"] include = ["fortnite_api/", "tests/", "examples/"] -# Isort configuration +# Ruff formatting + +[tool.ruff] +# Allow lines to be as long as 120. +line-length = 120 + +[tool.ruff.format] +# Prefer single quotes over double quotes. +quote-style = "preserve" + +[tool.ruff.lint] +select = [ + "I", # Isort + "UP", # Pyupgrade +] + +[tool.ruff.lint.isort] +combine-as-imports = true -[tool.isort] -profile = "black" -combine_as_imports = true -combine_star = true -line_length = 125 -src_paths = ["fortnite_api/", "tests/", "examples/"] +[tool.ruff.lint.pyupgrade] +keep-runtime-typing = true From 1655999065f0d8bd6d26597a0f527a2242d5f11e Mon Sep 17 00:00:00 2001 From: Lucas Hardt Date: Thu, 25 Sep 2025 21:54:59 +0200 Subject: [PATCH 2/5] format code with ruff --- docs/extensions/outdated_code_blocks.py | 4 ++-- fortnite_api/client.py | 10 +++++++++- fortnite_api/cosmetics/track.py | 4 +++- fortnite_api/cosmetics/variants/bean.py | 4 +++- fortnite_api/cosmetics/variants/lego.py | 4 +++- fortnite_api/http.py | 3 +-- fortnite_api/new_display_asset.py | 3 ++- fortnite_api/proxies.py | 4 +++- fortnite_api/stats.py | 4 +++- tests/test_asset.py | 3 --- tests/test_async_methods.py | 2 -- tests/test_beta.py | 2 -- tests/test_ratelimits.py | 1 - tests/test_sync_methods.py | 1 - 14 files changed, 29 insertions(+), 20 deletions(-) diff --git a/docs/extensions/outdated_code_blocks.py b/docs/extensions/outdated_code_blocks.py index 742048fd..af61a255 100644 --- a/docs/extensions/outdated_code_blocks.py +++ b/docs/extensions/outdated_code_blocks.py @@ -118,7 +118,6 @@ class OutdatedCodeBlock(CodeBlock): } def run(self) -> list[Node]: - # Create the main node that holds the code block and warning root = OutdatedCodeBlockNode() @@ -144,7 +143,8 @@ def setup(app: Sphinx): app.add_node(OutdatedCodeBlockNode, html=(visit_outdated_code_block_node, depart_outdated_code_block_node)) # type: ignore app.add_node(OutdatedCodeBlockWarning, html=(visit_outdated_code_block_warning, depart_outdated_code_block_warning)) # type: ignore app.add_node( # type: ignore - OutdatedCodeBlockWarningText, html=(visit_outdated_code_block_warning_text, depart_outdated_code_block_warning_text) + OutdatedCodeBlockWarningText, + html=(visit_outdated_code_block_warning_text, depart_outdated_code_block_warning_text), ) app.add_directive('outdated-code-block', OutdatedCodeBlock) diff --git a/fortnite_api/client.py b/fortnite_api/client.py index 4d554912..5db9c21f 100644 --- a/fortnite_api/client.py +++ b/fortnite_api/client.py @@ -37,7 +37,15 @@ from .aes import Aes from .all import CosmeticsAll from .banner import Banner, BannerColor -from .cosmetics import CosmeticBr, CosmeticCar, CosmeticInstrument, CosmeticLegoKit, CosmeticTrack, VariantBean, VariantLego +from .cosmetics import ( + CosmeticBr, + CosmeticCar, + CosmeticInstrument, + CosmeticLegoKit, + CosmeticTrack, + VariantBean, + VariantLego, +) from .creator_code import CreatorCode from .enums import * from .errors import BetaAccessNotEnabled, BetaUnknownException, MissingAPIKey diff --git a/fortnite_api/cosmetics/track.py b/fortnite_api/cosmetics/track.py index fedaa486..6fdad359 100644 --- a/fortnite_api/cosmetics/track.py +++ b/fortnite_api/cosmetics/track.py @@ -152,7 +152,9 @@ def __init__(self, *, data: dict[str, Any], http: HTTPClientT) -> None: self.bpm: int = data['bpm'] self.duration: int = data['duration'] - self.difficulty: CosmeticTrackDifficulty[HTTPClientT] = CosmeticTrackDifficulty(data=data['difficulty'], http=http) + self.difficulty: CosmeticTrackDifficulty[HTTPClientT] = CosmeticTrackDifficulty( + data=data['difficulty'], http=http + ) self.gameplay_tags: list[str] = get_with_fallback(data, 'gameplayTags', list) self.genres: list[str] = get_with_fallback(data, 'genres', list) self.album_art: Asset[HTTPClientT] = Asset(http=http, url=data['albumArt']) diff --git a/fortnite_api/cosmetics/variants/bean.py b/fortnite_api/cosmetics/variants/bean.py index aa428579..6e066b67 100644 --- a/fortnite_api/cosmetics/variants/bean.py +++ b/fortnite_api/cosmetics/variants/bean.py @@ -89,7 +89,9 @@ def fetch_cosmetic_br( ) -> Coroutine[Any, Any, CosmeticBr]: ... @overload - def fetch_cosmetic_br(self: VariantBean[SyncHTTPClient], *, language: Optional[GameLanguage] = None) -> CosmeticBr: ... + def fetch_cosmetic_br( + self: VariantBean[SyncHTTPClient], *, language: Optional[GameLanguage] = None + ) -> CosmeticBr: ... def fetch_cosmetic_br( self, *, language: Optional[GameLanguage] = None diff --git a/fortnite_api/cosmetics/variants/lego.py b/fortnite_api/cosmetics/variants/lego.py index 42c65d70..783f4a40 100644 --- a/fortnite_api/cosmetics/variants/lego.py +++ b/fortnite_api/cosmetics/variants/lego.py @@ -87,7 +87,9 @@ def fetch_cosmetic_br( ) -> Coroutine[Any, Any, CosmeticBr]: ... @overload - def fetch_cosmetic_br(self: VariantLego[SyncHTTPClient], *, language: Optional[GameLanguage] = None) -> CosmeticBr: ... + def fetch_cosmetic_br( + self: VariantLego[SyncHTTPClient], *, language: Optional[GameLanguage] = None + ) -> CosmeticBr: ... def fetch_cosmetic_br( self, *, language: Optional[GameLanguage] = None diff --git a/fortnite_api/http.py b/fortnite_api/http.py index 4147cc7b..a90ba306 100644 --- a/fortnite_api/http.py +++ b/fortnite_api/http.py @@ -89,11 +89,10 @@ def key(self) -> str: class HTTPMixin(abc.ABC): - def __init__(self, *, token: Optional[str] = None) -> None: self.token: Optional[str] = token - self.user_agent = 'FortniteApi (https://github.com/Fortnite-API/py-wrapper {0}) Python/{1[0]}.{1[1]}'.format( + self.user_agent = 'FortniteApi (https://github.com/Fortnite-API/py-wrapper {0}) Python/{1[0]}.{1[1]}'.format( # noqa: UP032 __version__, sys.version_info ) diff --git a/fortnite_api/new_display_asset.py b/fortnite_api/new_display_asset.py index bf7e17e5..f055be5b 100644 --- a/fortnite_api/new_display_asset.py +++ b/fortnite_api/new_display_asset.py @@ -246,7 +246,8 @@ def __init__(self, *, data: dict[str, Any], http: HTTPClientT) -> None: self.id: str = data["id"] self.cosmetic_id: Optional[str] = data.get("cosmeticId") self.material_instances: list[MaterialInstance[HTTPClientT]] = [ - MaterialInstance(data=instance, http=http) for instance in get_with_fallback(data, "materialInstances", list) + MaterialInstance(data=instance, http=http) + for instance in get_with_fallback(data, "materialInstances", list) ] self.render_images: list[RenderImage[HTTPClientT]] = [ RenderImage(data=instance, http=http) for instance in get_with_fallback(data, "renderImages", list) diff --git a/fortnite_api/proxies.py b/fortnite_api/proxies.py index 41a166ba..c1a45770 100644 --- a/fortnite_api/proxies.py +++ b/fortnite_api/proxies.py @@ -56,7 +56,9 @@ class TransformerListProxy(Generic[T, K_co, V_co], list[T]): to ensure that the data is always in a consistent state. """ - def __init__(self, raw_data: Iterable[dict[K_co, V_co]], /, transform_data: Callable[[dict[K_co, V_co]], T]) -> None: + def __init__( + self, raw_data: Iterable[dict[K_co, V_co]], /, transform_data: Callable[[dict[K_co, V_co]], T] + ) -> None: self._transform_data: Callable[[dict[K_co, V_co]], T] = transform_data super().__init__(cast(list[T], raw_data)) diff --git a/fortnite_api/stats.py b/fortnite_api/stats.py index 9bea521c..544bbf3c 100644 --- a/fortnite_api/stats.py +++ b/fortnite_api/stats.py @@ -325,7 +325,9 @@ def __init__(self, *, data: dict[str, Any], http: HTTPClientT) -> None: self.user: Account[HTTPClientT] = Account(data=_user, http=http) _battle_pass = data.get("battlePass") - self.battle_pass: Optional[BrBattlePass[HTTPClientT]] = _battle_pass and BrBattlePass(data=_battle_pass, http=http) + self.battle_pass: Optional[BrBattlePass[HTTPClientT]] = _battle_pass and BrBattlePass( + data=_battle_pass, http=http + ) _image = data.get("image") self.image: Optional[Asset[HTTPClientT]] = _image and Asset(http=http, url=_image) diff --git a/tests/test_asset.py b/tests/test_asset.py index 15297dc8..d3293de1 100644 --- a/tests/test_asset.py +++ b/tests/test_asset.py @@ -33,7 +33,6 @@ def test_sync_asset_reading(): with fortnite_api.SyncClient() as client: - mock_asset = fortnite_api.Asset(http=client.http, url=V_BUCK_ICON_URL) # Read the asset and ensure it is bytes @@ -44,7 +43,6 @@ def test_sync_asset_reading(): @pytest.mark.asyncio async def test_async_asset_reading(): async with fortnite_api.Client() as client: - mock_asset = fortnite_api.Asset(http=client.http, url=V_BUCK_ICON_URL) # Read the asset and ensure it is bytes @@ -54,7 +52,6 @@ async def test_async_asset_reading(): def test_asset(): with fortnite_api.SyncClient() as client: - mock_asset = fortnite_api.Asset(http=client.http, url=V_BUCK_ICON_URL) assert mock_asset.url == V_BUCK_ICON_URL diff --git a/tests/test_async_methods.py b/tests/test_async_methods.py index 02cf0e5a..445a3652 100644 --- a/tests/test_async_methods.py +++ b/tests/test_async_methods.py @@ -558,7 +558,6 @@ async def test_async_fetch_playlist_by_id(api_key: str): @pytest.mark.asyncio async def test_async_beta_fetch_new_display_assets(api_key: str): - # Ensure you cannot call this without beta=True with pytest.raises(fn_api.BetaAccessNotEnabled): await fn_api.Client().beta_fetch_new_display_assets() @@ -586,7 +585,6 @@ async def test_async_beta_fetch_new_display_assets(api_key: str): @pytest.mark.asyncio async def test_async_beta_fetch_material_instances(api_key: str): - # Ensure you cannot call this without beta=True with pytest.raises(fn_api.BetaAccessNotEnabled): await fn_api.Client().beta_fetch_material_instances() diff --git a/tests/test_beta.py b/tests/test_beta.py index 7a34cc7f..13372bf8 100644 --- a/tests/test_beta.py +++ b/tests/test_beta.py @@ -46,7 +46,6 @@ async def test_async_cannot_call_beta_method(): # A mock of the SyncClient beta function that raises an error class MockSyncFortniteAPI(fortnite_api.SyncClient): - @beta_method def beta_mock_call(self): raise ValueError('Mock error') @@ -54,7 +53,6 @@ def beta_mock_call(self): # A mock of the Client beta function that raises an error class MockFortniteAPI(fortnite_api.Client): - @beta_method async def beta_mock_call(self): raise ValueError('Mock error') diff --git a/tests/test_ratelimits.py b/tests/test_ratelimits.py index 0bcb3f82..af385370 100644 --- a/tests/test_ratelimits.py +++ b/tests/test_ratelimits.py @@ -95,7 +95,6 @@ async def test_async_rate_limit_handling(async_client: HTTPClient): # Make a request route = Route('GET', 'https://example.com') with pytest.raises(RateLimited) as excinfo: - # This will try 5 times to request, and each time get a 429 response. After it # should raise the RateLimited error. Any subsequent requests with the same route # should immediately raise the RateLimited error. diff --git a/tests/test_sync_methods.py b/tests/test_sync_methods.py index 4e1924c1..e07c24a1 100644 --- a/tests/test_sync_methods.py +++ b/tests/test_sync_methods.py @@ -317,7 +317,6 @@ def test_sync_fetch_playlist_by_id(api_key: str): def test_sync_beta_fetch_new_display_assets(api_key: str): - # Ensure you cannot call this without beta=True with pytest.raises(fn_api.BetaAccessNotEnabled): fn_api.SyncClient().beta_fetch_new_display_assets() From fe9752d99c7f20f94b7d62ac65258136306f518f Mon Sep 17 00:00:00 2001 From: Lucas Hardt Date: Thu, 25 Sep 2025 21:56:01 +0200 Subject: [PATCH 3/5] Add ruff format commit in blame ignore --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..3f48a954 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# format code with ruff +1655999065f0d8bd6d26597a0f527a2242d5f11e \ No newline at end of file From 86e9f3e944951e299d4aff74af01e6a7cb8fe383 Mon Sep 17 00:00:00 2001 From: Lucas Hardt Date: Thu, 25 Sep 2025 22:00:30 +0200 Subject: [PATCH 4/5] fix ruff version in dev requirements --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index faed0b1e..16bf0388 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ docs = [ 'furo', 'sphinx-copybutton', ] -dev = ['ruff==v0.13.2', 'discord.py', 'pyright', 'pre-commit'] +dev = ['ruff==0.13.2', 'discord.py', 'pyright', 'pre-commit'] speed = ['orjson'] [project.urls] From 02cd906926c6f1ddec448a4c67bcdba63f25d637 Mon Sep 17 00:00:00 2001 From: Lucas Hardt Date: Thu, 25 Sep 2025 22:06:01 +0200 Subject: [PATCH 5/5] Potential fix for code scanning alert no. 5: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8e72296b..79c80b28 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,6 +7,8 @@ on: jobs: ruff: + permissions: + contents: read runs-on: ubuntu-latest steps: - uses: actions/checkout@v5