mullion 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,50 @@
1
+ name: Defect
2
+ description: An image comes out wrong, or a documented behaviour is not the behaviour.
3
+ labels: ["bug"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: >-
8
+ Every defect this library addresses is silent — a wrong answer renders
9
+ as a *plausible picture*. So the input is the report. Without it there
10
+ is nothing to reproduce, because every implementation in this space
11
+ produces a plausible picture for some input and the whole question is
12
+ which one.
13
+ - type: textarea
14
+ id: input
15
+ attributes:
16
+ label: The input
17
+ description: >-
18
+ Attach the file, or paste the code that builds it. A constructing
19
+ snippet is often better than a file — see tests/conftest.py, where
20
+ every fixture is built that way so a reader can see what makes it
21
+ awkward.
22
+ render: python
23
+ validations:
24
+ required: true
25
+ - type: textarea
26
+ id: call
27
+ attributes:
28
+ label: The call you made
29
+ description: The exact call, with its arguments.
30
+ render: python
31
+ validations:
32
+ required: true
33
+ - type: textarea
34
+ id: expected
35
+ attributes:
36
+ label: What you expected, and what you got
37
+ description: >-
38
+ Pixel values beat adjectives. `image.getpixel((0, 0))` returning
39
+ `(0, 0, 0)` where you wanted `(255, 255, 255)` is a report that can be
40
+ turned into a test in one step; "the background looks wrong" is not.
41
+ validations:
42
+ required: true
43
+ - type: input
44
+ id: versions
45
+ attributes:
46
+ label: Versions
47
+ description: Mullion, Pillow and Python.
48
+ placeholder: "mullion 0.1.0, Pillow 12.3.0, Python 3.13"
49
+ validations:
50
+ required: true
@@ -0,0 +1,47 @@
1
+ name: Change
2
+ description: You want Mullion to do something it does not.
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: >-
8
+ Please read [what is out of
9
+ scope](https://github.com/sigrix-io/mullion#what-it-is-not) first.
10
+ The scope is narrow on purpose and a change that widens it will be
11
+ turned down however good it is — an application takes this library on
12
+ because it is small.
13
+ - type: textarea
14
+ id: problem
15
+ attributes:
16
+ label: What goes wrong today
17
+ description: >-
18
+ Describe the situation, not the feature. The most useful reports here
19
+ are "an image arrived in a shape you do not handle and was silently
20
+ accepted", which is the thesis of the whole library.
21
+ validations:
22
+ required: true
23
+ - type: textarea
24
+ id: workaround
25
+ attributes:
26
+ label: What you do instead right now
27
+ description: >-
28
+ If the answer is a few lines of Pillow in your own codebase, paste
29
+ them. That is the strongest possible case: it is what every caller
30
+ would otherwise be writing separately, which is exactly the argument
31
+ for it living here.
32
+ render: python
33
+ validations:
34
+ required: false
35
+ - type: dropdown
36
+ id: scope
37
+ attributes:
38
+ label: Does this handle one image, or several?
39
+ description: >-
40
+ The line is stated in the README and CONTRIBUTING: this library handles
41
+ one image, and composing several is the application's job.
42
+ options:
43
+ - One image
44
+ - Several images
45
+ - Not sure
46
+ validations:
47
+ required: true
@@ -0,0 +1,41 @@
1
+ name: I depend on this
2
+ description: Not a defect. Tell us you are building on 0.x so we do not break you silently.
3
+ labels: ["dependent"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: >-
8
+ [VERSIONING.md](https://github.com/sigrix-io/mullion/blob/main/VERSIONING.md)
9
+ says plainly that any `0.x` release may break any other. This form is
10
+ the other half of that bargain: the practical difference between an
11
+ announced breaking change and a surprising one is knowing somebody is
12
+ out there.
13
+ - type: input
14
+ id: version
15
+ attributes:
16
+ label: Which version are you pinned to?
17
+ placeholder: "mullion~=0.1.0"
18
+ validations:
19
+ required: true
20
+ - type: checkboxes
21
+ id: surface
22
+ attributes:
23
+ label: Which parts do you use?
24
+ description: >-
25
+ This is what tells us which change would actually cost you something.
26
+ options:
27
+ - label: "`normalize` / `open_bytes` / `open_path`"
28
+ - label: "`clean_background` and its defaults"
29
+ - label: "`page` geometry and DPI"
30
+ validations:
31
+ required: true
32
+ - type: textarea
33
+ id: context
34
+ attributes:
35
+ label: Anything else worth knowing
36
+ description: >-
37
+ What you are building, whether you have pinned the defaults, whether a
38
+ change in *output* would be a problem for you or merely a diff. No
39
+ obligation to name your project.
40
+ validations:
41
+ required: false
@@ -0,0 +1,23 @@
1
+ # The three forms alongside this file are the three kinds CONTRIBUTING.md says
2
+ # are triaged differently. A blank issue would let the classification be
3
+ # skipped, which is the thing that does not currently work.
4
+ blank_issues_enabled: false
5
+
6
+ contact_links:
7
+ - name: Report a security vulnerability
8
+ url: https://github.com/sigrix-io/mullion/security/policy
9
+ about: >-
10
+ Do not open a public issue. Email security@sigrix.io — acknowledged
11
+ within 72 hours. This library decodes untrusted bytes; an input that
12
+ makes it misbehave is in scope.
13
+ - name: Raise a code of conduct concern
14
+ url: https://github.com/sigrix-io/mullion/blob/main/CODE_OF_CONDUCT.md
15
+ about: >-
16
+ Email conduct@sigrix.io. A different address and a different queue from
17
+ security reports; neither is a fallback for the other.
18
+ - name: A bug in Pillow itself
19
+ url: https://github.com/python-pillow/Pillow/issues
20
+ about: >-
21
+ Mullion is a thin layer over Pillow. If the wrong answer is Pillow's,
22
+ report it there — unless something here makes it worse or reachable, in
23
+ which case file a defect and say so.
@@ -0,0 +1,37 @@
1
+ # Keeps the two things this repository pins from ageing quietly.
2
+ #
3
+ # The ruff version in ci.yml is pinned so that a local `ruff format` and the CI
4
+ # check can never disagree. That trade has a second half which is easy to leave
5
+ # unbuilt: a pin only stays correct if something bumps it, and left alone the
6
+ # formatter drifts away from whatever contributors have installed — the same
7
+ # failure pinning exists to prevent, arriving from the other direction and with
8
+ # a green check the whole way.
9
+ #
10
+ # The test matrix is what makes this safe rather than merely automatic. A bump
11
+ # that breaks anything arrives as a red check on a pull request nobody has
12
+ # merged, and because `ci-passed` is required on `main`, it cannot land without
13
+ # passing.
14
+ #
15
+ # Two GitHub behaviours worth knowing: this file is read from the default
16
+ # branch, so editing it on a branch changes nothing until it merges; and
17
+ # Dependabot needs the dependency graph enabled on the repository, which is not
18
+ # something this file can assert.
19
+ #
20
+ # Deliberately no auto-merge. Whether these should merge themselves when green
21
+ # is a judgement about how much unattended merging is wanted, and it belongs on
22
+ # the record rather than defaulted into by a config nobody revisits.
23
+ version: 2
24
+ updates:
25
+ # Both the runtime floor and the dev extras.
26
+ - package-ecosystem: "pip"
27
+ directory: "/"
28
+ schedule:
29
+ interval: "weekly"
30
+
31
+ # Turns an action deprecation into a pull request, rather than a warning at
32
+ # the top of a log nobody reads until the runtime is withdrawn and checkout
33
+ # starts failing on whichever pull request happens to be open that day.
34
+ - package-ecosystem: "github-actions"
35
+ directory: "/"
36
+ schedule:
37
+ interval: "weekly"
@@ -0,0 +1,37 @@
1
+ ## What this changes
2
+
3
+ <!-- One or two sentences. The diff shows what changed; say why. -->
4
+
5
+ ## Does it change output?
6
+
7
+ <!--
8
+ The question that matters most in a library that produces pixels. Delete the
9
+ rows that do not apply.
10
+
11
+ - [ ] No — refactor, docs, tests or tooling only.
12
+ - [ ] Yes, and it is a fix — the previous output was wrong. Say for which
13
+ inputs, so the changelog entry can name them.
14
+ - [ ] Yes, and it is a default moving. CONTRIBUTING asks for the images:
15
+ what got better, and what got worse. There is no threshold that is right
16
+ for everything.
17
+ -->
18
+
19
+ ## Have you watched the new tests fail?
20
+
21
+ <!--
22
+ CONTRIBUTING calls this the one house rule. Every defect this library addresses
23
+ is silent, so a test can assert the right thing about the right pixel and still
24
+ be worthless — break the implementation deliberately, confirm the test that
25
+ names the claim goes red, then put it back.
26
+
27
+ Say which mutation you tried and which test caught it.
28
+ -->
29
+
30
+ ## Checks
31
+
32
+ - [ ] `python -m pytest`
33
+ - [ ] `python -m ruff check src/ tests/`
34
+ - [ ] `python -m ruff format --check src/ tests/`
35
+ - [ ] `python -m mypy --strict src/mullion`
36
+ - [ ] Scope: this still handles *one* image (see CONTRIBUTING)
37
+ - [ ] `CHANGELOG.md` updated under `Unreleased`, if this is user-visible
@@ -0,0 +1,121 @@
1
+ # Lint, then test, then prove the package builds and installs.
2
+ #
3
+ # The ruff steps run before pytest deliberately: a formatting slip should fail
4
+ # the job in ten seconds rather than after the suite. They are two commands
5
+ # rather than one because `check` and `format --check` answer different
6
+ # questions and only one of them is about style.
7
+ name: CI
8
+
9
+ on:
10
+ push:
11
+ branches: [main]
12
+ pull_request:
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ concurrency:
18
+ group: ci-${{ github.ref }}
19
+ cancel-in-progress: true
20
+
21
+ jobs:
22
+ lint:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v5
26
+ - uses: actions/setup-python@v6
27
+ with:
28
+ python-version: "3.13"
29
+ # Pinned, so that a local `ruff format` and this check can never disagree
30
+ # about formatter behaviour. Dependabot bumps it; the tests gate the bump.
31
+ - run: python -m pip install ruff==0.16.5
32
+ - run: python -m ruff check src/ tests/
33
+ - run: python -m ruff format --check src/ tests/
34
+
35
+ test:
36
+ runs-on: ubuntu-latest
37
+ strategy:
38
+ fail-fast: false
39
+ matrix:
40
+ python-version: ["3.11", "3.12", "3.13"]
41
+ steps:
42
+ - uses: actions/checkout@v5
43
+ - uses: actions/setup-python@v6
44
+ with:
45
+ python-version: ${{ matrix.python-version }}
46
+ - run: python -m pip install -e ".[dev]"
47
+ - run: python -m pytest
48
+
49
+ # ``py.typed`` ships in the wheel and pyproject declares ``Typing :: Typed``,
50
+ # which together tell every consumer's type checker to trust these
51
+ # annotations instead of treating the package as untyped. Nothing verified
52
+ # them until #3, and a wrong annotation is the quietest defect a library can
53
+ # ship: it does not raise, it just makes somebody else's mypy run confidently
54
+ # green about the wrong thing. Pinned for the same reason ruff is — a
55
+ # floating checker turns an unrelated release into a red build on a commit
56
+ # that changed nothing.
57
+ types:
58
+ runs-on: ubuntu-latest
59
+ steps:
60
+ - uses: actions/checkout@v5
61
+ - uses: actions/setup-python@v6
62
+ with:
63
+ python-version: "3.13"
64
+ - run: python -m pip install -e ".[dev]"
65
+ - run: python -m mypy --strict src/mullion
66
+
67
+ # A floor nobody tests is a guess. pyproject declares pillow>=10.0, so
68
+ # something has to actually run against 10.0 — otherwise the first user on an
69
+ # older Pillow discovers the real floor for us, in production.
70
+ oldest-pillow:
71
+ runs-on: ubuntu-latest
72
+ steps:
73
+ - uses: actions/checkout@v5
74
+ - uses: actions/setup-python@v6
75
+ with:
76
+ python-version: "3.11"
77
+ - run: python -m pip install -e ".[dev]"
78
+ - run: python -m pip install "pillow==10.0.*"
79
+ - run: python -m pytest
80
+
81
+ build:
82
+ runs-on: ubuntu-latest
83
+ steps:
84
+ - uses: actions/checkout@v5
85
+ - uses: actions/setup-python@v6
86
+ with:
87
+ python-version: "3.13"
88
+ - run: python -m pip install build twine
89
+ - run: python -m build
90
+ # Catches a README that renders on GitHub and not on PyPI, which is
91
+ # otherwise found at the moment of publishing and not before.
92
+ - run: python -m twine check dist/*
93
+ # The wheel is what users get. Installing it from a clean directory is
94
+ # the only thing that proves py.typed and the package layout shipped —
95
+ # an editable install reads the source tree and would pass either way.
96
+ - name: The built wheel imports and carries its type marker
97
+ run: |
98
+ python -m venv /tmp/wheelcheck
99
+ /tmp/wheelcheck/bin/python -m pip install --quiet dist/*.whl
100
+ cd /tmp
101
+ /tmp/wheelcheck/bin/python -c "
102
+ import mullion, pathlib
103
+ print('version', mullion.__version__)
104
+ root = pathlib.Path(mullion.__file__).parent
105
+ assert (root / 'py.typed').exists(), 'py.typed did not ship'
106
+ for name in mullion.__all__:
107
+ assert hasattr(mullion, name), name
108
+ print(len(mullion.__all__), 'exports resolve')
109
+ "
110
+
111
+ # The aggregate gate. One check to require on main, so adding a job above
112
+ # does not mean editing a branch protection rule to match.
113
+ ci-passed:
114
+ if: always()
115
+ needs: [lint, types, test, oldest-pillow, build]
116
+ runs-on: ubuntu-latest
117
+ steps:
118
+ - name: Every job succeeded
119
+ run: |
120
+ echo '${{ toJSON(needs) }}'
121
+ [ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "false" ]
@@ -0,0 +1,148 @@
1
+ # Publish a tagged release to PyPI. Nothing else in this repository publishes,
2
+ # and nothing in this file runs on a branch — a release is a tag, deliberately,
3
+ # so that publishing is an act with a name rather than a side effect of a merge.
4
+ #
5
+ # Trusted publishing (OIDC) rather than an API token: the runner exchanges a
6
+ # short-lived identity for an upload, so there is no long-lived secret in this
7
+ # repository to store, leak or rotate. The trade is that PyPI has to be told
8
+ # which workflow in which environment it trusts, which is configuration that
9
+ # lives there and not here — see the note on `environment:` below.
10
+ name: Release
11
+
12
+ on:
13
+ push:
14
+ tags: ["v*"]
15
+
16
+ # Read-only by default. Only the publish job widens this, and only to the one
17
+ # permission the OIDC exchange needs.
18
+ permissions:
19
+ contents: read
20
+
21
+ # Deliberately NOT `cancel-in-progress`, which is what ci.yml sets. Cancelling
22
+ # a CI run wastes a runner; cancelling a run that is mid-upload can leave a
23
+ # release half-landed, and a PyPI version number can never be reused — not even
24
+ # after deleting the file. There is nothing to gain here by being fast.
25
+ concurrency:
26
+ group: release-${{ github.ref }}
27
+ cancel-in-progress: false
28
+
29
+ jobs:
30
+ # Everything that can say no, said before anything leaves the runner.
31
+ build:
32
+ runs-on: ubuntu-latest
33
+ # A fork can push a tag to itself; it must not be able to build the
34
+ # artifacts this repository's trust policy would then be asked to publish.
35
+ if: github.repository == 'sigrix-io/mullion'
36
+ steps:
37
+ # checkout and setup-python track ci.yml's pins on purpose, rather than
38
+ # the newest majors: this workflow runs once per release, at the moment
39
+ # when discovering an action's breaking change is most expensive, so it
40
+ # should run on the versions the rest of the repository has exercised.
41
+ # Dependabot moves both files together.
42
+ - uses: actions/checkout@v5
43
+ - uses: actions/setup-python@v6
44
+ with:
45
+ python-version: "3.13"
46
+
47
+ - run: python -m pip install build twine
48
+ - run: python -m build
49
+
50
+ # hatchling reads the version out of pyproject.toml; the tag is typed by
51
+ # hand. Nothing makes them agree. Tagging v0.2.0 without bumping
52
+ # pyproject builds 0.1.0 and would publish it under a tag saying
53
+ # otherwise — or, if 0.1.0 is already on PyPI, fail at the upload with a
54
+ # duplicate-file error naming neither cause. Ask here instead, where the
55
+ # answer is still cheap.
56
+ - name: The tag names the version that was actually built
57
+ run: |
58
+ set -euo pipefail
59
+ tag="${GITHUB_REF_NAME#v}"
60
+ # mullion-0.1.0-py3-none-any.whl -> 0.1.0. A wheel filename uses "-"
61
+ # as its field separator, so a PEP 440 version can never contain one.
62
+ built="$(basename "$(ls dist/*.whl)" | cut -d- -f2)"
63
+ echo "tag=${tag} built=${built}"
64
+ if [ "${tag}" != "${built}" ]; then
65
+ echo "::error::tag ${GITHUB_REF_NAME} does not name the packaged version ${built}. Bump pyproject.toml, or delete the tag and retag."
66
+ exit 1
67
+ fi
68
+
69
+ # A release nobody wrote down is one a dependent cannot evaluate, which
70
+ # is the whole promise VERSIONING.md makes while this is 0.x. Keep a
71
+ # Changelog puts the version in a heading, so this is an exact check
72
+ # rather than a guess at prose.
73
+ - name: The changelog has an entry for this version
74
+ run: |
75
+ set -euo pipefail
76
+ version="${GITHUB_REF_NAME#v}"
77
+ if ! grep -qF "## [${version}]" CHANGELOG.md; then
78
+ echo "::error::CHANGELOG.md has no '## [${version}]' heading. Move the Unreleased entries under it before tagging."
79
+ exit 1
80
+ fi
81
+
82
+ # Catches a README that renders on GitHub and not on PyPI, which is
83
+ # otherwise found at the moment of publishing and not before.
84
+ - run: python -m twine check dist/*
85
+
86
+ # ci.yml proves the *source tree* passes, via an editable install. This
87
+ # proves the *artifact* does, which is a different question and the only
88
+ # one that still matters once the bytes are immutable on PyPI: a file
89
+ # missing from the wheel is invisible to every editable-install run.
90
+ - name: The built wheel installs, imports, and passes the suite
91
+ run: |
92
+ set -euo pipefail
93
+ python -m venv /tmp/releasecheck
94
+ /tmp/releasecheck/bin/python -m pip install --quiet dist/*.whl pytest
95
+ # From /tmp, so nothing in the checkout can satisfy the import.
96
+ cd /tmp
97
+ /tmp/releasecheck/bin/python -c "
98
+ import mullion, pathlib
99
+ root = pathlib.Path(mullion.__file__).parent
100
+ assert (root / 'py.typed').exists(), 'py.typed did not ship'
101
+ for name in mullion.__all__:
102
+ assert hasattr(mullion, name), name
103
+ print(mullion.__version__, '-', len(mullion.__all__), 'exports resolve')
104
+ "
105
+ cd "${GITHUB_WORKSPACE}"
106
+ # src-layout means ./mullion does not exist, so the import below can
107
+ # only resolve to the wheel in the venv. Asserted rather than assumed:
108
+ # a suite that quietly re-tested the source tree would look identical.
109
+ /tmp/releasecheck/bin/python -c "
110
+ import mullion
111
+ assert 'site-packages' in mullion.__file__, mullion.__file__
112
+ "
113
+ /tmp/releasecheck/bin/python -m pytest tests/
114
+
115
+ - uses: actions/upload-artifact@v7
116
+ with:
117
+ name: dist
118
+ path: dist/
119
+ if-no-files-found: error
120
+
121
+ # Separate from the build so that a failure above cannot half-publish: this
122
+ # job does not start unless every check passed, and it builds nothing itself.
123
+ publish:
124
+ needs: build
125
+ runs-on: ubuntu-latest
126
+ environment:
127
+ # PyPI's trusted-publisher policy names this environment alongside the
128
+ # repository and the workflow filename, so the three have to match what
129
+ # is configured there. It is also where to add a required reviewer if a
130
+ # release should ever need a second pair of eyes.
131
+ name: pypi
132
+ url: https://pypi.org/p/mullion
133
+ permissions:
134
+ # The only elevated permission in this file, scoped to the one job that
135
+ # needs it. This mints the OIDC token PyPI exchanges for an upload, and
136
+ # it is also what makes the action attach PEP 740 attestations.
137
+ id-token: write
138
+ steps:
139
+ - uses: actions/download-artifact@v8
140
+ with:
141
+ name: dist
142
+ path: dist/
143
+
144
+ # Pinned to the commit rather than the tag. A tag is mutable by whoever
145
+ # owns the action, and this is the one step in this repository holding a
146
+ # credential worth stealing. Dependabot bumps the pin and rewrites the
147
+ # trailing comment with it.
148
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,24 @@
1
+ # Python bytecode. CONTRIBUTING.md asks every contributor to run pytest and
2
+ # ruff before pushing, so these appear during a normal contribution rather than
3
+ # only on a maintainer's machine. Without this entry one routine `git add -A`
4
+ # commits build artefacts nobody wants to review.
5
+ __pycache__/
6
+ *.py[cod]
7
+
8
+ # An editable install writes here, and `python -m build` writes the other two.
9
+ *.egg-info/
10
+ build/
11
+ dist/
12
+
13
+ # Installing without touching the system Python means a virtual environment.
14
+ # Both conventional names are listed so whichever a contributor reaches for
15
+ # stays untracked.
16
+ .venv/
17
+ venv/
18
+
19
+ # Test and tooling caches.
20
+ .pytest_cache/
21
+ .ruff_cache/
22
+ .mypy_cache/
23
+ .coverage
24
+ htmlcov/
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+
3
+ All notable changes are recorded here. The format is
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
+ follows [VERSIONING.md](VERSIONING.md) — read it before pinning, because
6
+ pre-1.0 means what it says.
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Fixed
11
+
12
+ - The annotations `py.typed` promises are now verified. The wheel ships
13
+ `src/mullion/py.typed` and the package declares `Typing :: Typed`, which
14
+ together tell a consumer's type checker to trust these annotations — but
15
+ nothing had ever run one, and `mypy --strict` reported nine errors on the
16
+ first release. All nine were `Image.load()`, typed `PixelAccess | None` and
17
+ indexed without narrowing. Nothing raised: a wrong annotation just makes
18
+ somebody else's type checker confidently green about the wrong thing.
19
+ ([#3](https://github.com/sigrix-io/mullion/issues/3))
20
+
21
+ ### Changed
22
+
23
+ - `mullion.background`'s two internal pixel readers now assert the image mode
24
+ they were written for instead of assuming it, so a caller handing one the
25
+ wrong mode fails immediately rather than reading a shape nothing checks.
26
+ No public API changed.
27
+
28
+ ## [0.1.0]
29
+
30
+ First release.
31
+
32
+ ### Added
33
+
34
+ - `mullion.source` — `normalize()`, and the `open_bytes()` / `open_path()`
35
+ adapters over it. Applies the EXIF orientation tag and resolves transparency
36
+ in all three shapes it arrives in: `RGBA`, `LA`, and palette `P` carrying a
37
+ `transparency` key in `info`. `keep_alpha` chooses between compositing onto a
38
+ matte and preserving the channel.
39
+ - `mullion.background` — `clean_background()`, border-seeded so that a
40
+ near-white region enclosed by the subject is never mistaken for the
41
+ background around it. `looks_like_white_background()` answers the same
42
+ verdict without doing the work, and `keeps_alpha()` names the one coupling
43
+ between the open call and the clean call.
44
+ - `mullion.page` — `resolve_page_geometry()` and `named_page_geometry()`,
45
+ returning pixels and the DPI they were derived at as one value.
@@ -0,0 +1,25 @@
1
+ # Every change to this repository requires review from a maintainer.
2
+ #
3
+ # Merging is discretionary and stays with the maintainers — see
4
+ # CONTRIBUTING.md. Contributions are welcome; governance is not open.
5
+ #
6
+ # The owner is a team rather than a person so that reviewer load can be shared
7
+ # without editing this file for every change in personnel, and so the
8
+ # repository does not advertise a single point of failure to anyone deciding
9
+ # whether to depend on it. It may hold one person; the indirection is the point.
10
+
11
+ * @sigrix-io/mullion-maintainers
12
+
13
+ # The published surface. Everything under src/ is a compatibility commitment
14
+ # the moment someone pins to a release, so it is called out separately from the
15
+ # repository default even though the owner is currently the same.
16
+ #
17
+ # tests/ belongs here for a reason worth stating: the defects this library
18
+ # exists for are silent — a wrong answer renders as a plausible picture — so
19
+ # the tests are the only thing that can see them. A weakened test is a
20
+ # weakened guarantee, arriving as a green run.
21
+
22
+ /src/ @sigrix-io/mullion-maintainers
23
+ /tests/ @sigrix-io/mullion-maintainers
24
+ /pyproject.toml @sigrix-io/mullion-maintainers
25
+ /VERSIONING.md @sigrix-io/mullion-maintainers