Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,18 @@ jobs:
- name: Install ruff
run: python -m pip install ruff==0.16.9

# Scope is explicit rather than `.` so the docstring baseline in
# pyproject.toml covers exactly these paths, and so generated or vendored
# trees cannot silently widen what CI lints. `test/` is included because
# the per-file-ignores baseline lists tiers for it; without it those tiers
# are dead configuration.
- name: Run tests and linters
run: |
poetry run ruff check photoshop
poetry run ruff format --check photoshop
poetry run ruff check photoshop test scripts
poetry run ruff format --check photoshop test scripts

# llms.txt and llms-full.txt are generated. The Release workflow opens a
# pull request when they drift, but that only reports — this is what makes
# a stale file fail the build.
- name: Check generated llms files are current
run: python scripts/generate_llms.py --check
18 changes: 13 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,22 @@ jobs:

# ── llms.txt / llms-full.txt — regenerate before the release is cut ───────
# Generated files, never hand-edited. Runs ahead of release-please so the
# version it stamps into llms.txt is the one about to be released. When the
# result differs from what is committed it opens (or updates) its own PR
# instead of pushing to main, so the content is reviewable like any other
# change and never silently rewrites the release branch.
# files are up to date on the branch the release is cut from. When the result
# differs from what is committed it opens (or updates) its own PR instead of
# pushing to main, so the content is reviewable like any other change and
# never silently rewrites the release branch.
#
# The version stamped into llms.txt is the last *released* one: the generator
# reads the newest tag with `git describe --tags --abbrev=0`, and no tag for
# the pending release exists yet this early in the run. So each release is
# followed by one docs PR carrying the new version — harmless, because
# preflight treats `docs:` as release-neutral and does not cut a release for
# it.
#
# Non-blocking by design: `release-please` uses `always()` below, so a failure
# here reports red but does not hold the release hostage. The ordering is what
# matters, not the gate.
# matters, not the gate. The `Python package` workflow is what makes stale
# generated files fail the build.
llms-txt:
needs: [preflight]
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,5 @@ with Session(action='new_document') as ps:

- [Changelog](https://github.com/loonghao/photoshop-python-api/blob/main/CHANGELOG.md) — release history.
- [Issue tracker](https://github.com/loonghao/photoshop-python-api/issues) — known problems and questions.

drift injected
43 changes: 31 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,24 +92,41 @@ extend-exclude = [
# in CI, so only pycodestyle/pyflakes rules ever fired. Quote rules are enabled
# here because the codebase already satisfies them.
#
# The docstring rules (D) are enabled but baselined per directory below: every
# finding that exists today is recorded in `per-file-ignores`, so CI stays green
# on the current tree while any NEW docstring debt is caught immediately. The
# baseline is meant to shrink — pick a tier, fix it, delete its entry.
# The docstring rules (D) are enabled, with the debt that existed when they were
# switched on recorded per directory below. See the baseline block for what that
# does and does not catch — the short version is that a rule listed for a file
# stays silent for that file, including new violations of it.
select = ["E", "W", "F", "Q", "I", "UP", "D"]

# Selecting the whole D family makes ruff drop the mutually exclusive half of two
# pairs on its own: D203 loses to D211, and D213 loses to D212. Both are reported
# as warnings at startup, which is expected and not something to "fix".
# The google convention selects a narrower set of pydocstyle rules than the
# default (pep257): 665 findings tree-wide under google versus 864 under pep257,
# 507 of them inside the CI lint scope. It also resolves the two mutually
# exclusive rule pairs (D203/D211 and D212/D213), so ruff emits no warnings.
[tool.ruff.lint.pydocstyle]
convention = "google"

# ── Docstring baseline ──────────────────────────────────────────────────────
# 864 findings existed when D was switched on. Each entry is a directory tier
# plus the rules that tier currently violates; the number is the finding count
# at baseline time. Deleting a rule from an entry makes that tier's existing
# debt fail CI until it is fixed, which is the intended ratchet.
# 665 findings existed when D was switched on (507 inside the CI lint scope).
# Each entry is a directory tier plus the rules that tier currently violates;
# the number is the finding count at baseline time.
#
# Read this before touching anything here. `per-file-ignores` is a UNION of
# every pattern that matches a file, not a "most specific pattern wins" lookup:
#
# - A rule listed for a file is ignored for that file, so NEW violations of
# that same rule are also ignored. This baseline stops the tree from going
# red; it does not stop new debt in already-baselined rules. Only violations
# of rules that are NOT listed for a file get caught.
# - Deleting a rule from an entry only tightens anything when no other
# matching entry still lists it. Every file under photoshop/ also matches
# "photoshop/**", so removing a rule from a narrower tier while the widest
# tier still lists it is a no-op. Tighten a rule by removing it from the
# widest tier that covers the files you just fixed.
[tool.ruff.lint.per-file-ignores]
# Ordered narrowest to widest, but order carries no meaning to ruff: matching
# entries are unioned, so the widest entry below is the effective floor for
# everything under photoshop/.
#
# Enum constant tables: one IntEnum class per Photoshop constant, 129 findings.
"photoshop/api/enumerations.py" = ["D101"]
# Save-option wrappers, 112 findings.
Expand All @@ -124,7 +141,9 @@ convention = "google"
"test/**" = ["D100", "D101", "D102", "D103", "D104", "D105", "D107", "D202", "D401", "D403"]
# The COM wrapper API, 443 findings.
"photoshop/api/**" = ["D100", "D101", "D102", "D105", "D107", "D205", "D209", "D212", "D214", "D301", "D400", "D401", "D403", "D404", "D405", "D413", "D414", "D415", "D417"]
# Package root, 8 findings.
# Package root, 8 findings. Effective floor for the whole package: a rule listed
# here is ignored for every file under photoshop/, whichever tier above also
# lists it. Remove a rule here once no file under photoshop/ violates it.
"photoshop/**" = ["D100", "D102", "D104", "D105", "D205", "D401", "D403"]

[tool.ruff.lint.flake8-quotes]
Expand Down
16 changes: 12 additions & 4 deletions scripts/generate_llms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
inline so a model can consume it in one request without fetching anything.

Both files are generated, never hand-edited. The ``llms-txt`` job in
``.github/workflows/llms-txt.yml`` regenerates them and opens a pull request
``.github/workflows/release.yml`` regenerates them and opens a pull request
whenever the result differs from what is committed.

Run it locally with::

python scripts/generate_llms.py

Pass ``--check`` to exit non-zero when the committed files are stale, which
is how the CI job decides whether a pull request is needed.
Pass ``--check`` to exit non-zero when the committed files are stale. The
``Python package`` workflow runs that on every pull request, so drift fails
the build; the ``llms-txt`` job only opens a pull request to report it.
"""

# Import future modules
Expand Down Expand Up @@ -175,7 +176,14 @@ def public_modules() -> list[str]:


def version() -> str:
"""Return the current package version from git, falling back to pyproject."""
"""Return the newest released version, falling back to pyproject.

This reads the most recent tag, which is the version that has *already*
been published rather than the one a pending release is about to cut: the
``llms-txt`` job runs ahead of release-please, so no tag for the upcoming
version exists yet. The generated files therefore carry the last released
version until the release lands and the job runs again.
"""
try:
result = subprocess.run(
["git", "describe", "--tags", "--abbrev=0"],
Expand Down
1 change: 0 additions & 1 deletion test/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@

# Import future modules
from __future__ import annotations
6 changes: 1 addition & 5 deletions test/manual_test/manual_test_application.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
""""""


# Import future modules
from __future__ import annotations

Expand Down Expand Up @@ -51,10 +50,7 @@ def test_get_background_color(self):

def test_set_background_color(self, photoshop_app):
self.app.backgroundColor.rgb.green = 0
assert (
self.app.backgroundColor.rgb.green
== photoshop_app.backgroundColor.rgb.green
)
assert self.app.backgroundColor.rgb.green == photoshop_app.backgroundColor.rgb.green

def test_build(self):
assert self.app.build == "21.0 (20191018.r.37 2019/10/18: 614690fb487)"
Expand Down
2 changes: 1 addition & 1 deletion test/manual_test/manual_test_enum_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
print(prefs.textFontSize)
prefs.textFontSize = FontSize.Small
print(prefs.textFontSize)
#app.eval_javascript("alert(app.preferences.textFontSize)")
# app.eval_javascript("alert(app.preferences.textFontSize)")
8 changes: 2 additions & 6 deletions test/manual_test/manual_test_path_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,8 @@ def setup(self):
yield
self.session.close()

def _create_path(
self, name: str, point_anchors: Sequence[tuple[float, float]]
) -> PathItem:
point_infos: list[PathPointInfo] = [
PathPointInfo(anchor=anchor) for anchor in point_anchors
]
def _create_path(self, name: str, point_anchors: Sequence[tuple[float, float]]) -> PathItem:
point_infos: list[PathPointInfo] = [PathPointInfo(anchor=anchor) for anchor in point_anchors]

last_anchor = point_anchors[-1]
left = (last_anchor[0] + 1, last_anchor[1])
Expand Down
1 change: 1 addition & 0 deletions test/manual_test/manual_test_solid_color.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
""""""

# Import third-party modules
import pytest

Expand Down
5 changes: 1 addition & 4 deletions test/manual_test/manual_test_text_item.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
""""""


# Import future modules
from __future__ import annotations

Expand All @@ -21,9 +20,7 @@ class TestTextItem:
@pytest.fixture(autouse=True)
def setup(self, psd_file):
"""Setup for current test."""
self.session = Session(
file_path=psd_file("textitem"), action="open", auto_close=True
)
self.session = Session(file_path=psd_file("textitem"), action="open", auto_close=True)
self.session.run_action()
doc = self.session.active_document
layer = doc.activeLayer
Expand Down
Loading