diff --git a/README.md b/README.md index d4af61b..288dfc1 100644 --- a/README.md +++ b/README.md @@ -1,144 +1,90 @@

- Python Logo Widgets + Python Logo Widgets
Python Logo Widgets

-

A group of widgets showing the Python logos, that can easily be added to your Python GUI code!

+

Drop-in Tkinter widgets for the Python logo and the "Python Powered" badges.

- - PyPI Build State - - PyTest State - - Pylint State - - CodeQL State - - Gitleaks State - - GitHub Version - + GitHub Version GitHub Issues - GitHub Pull Requests - - Discord Server ID - - Downloads - - GitHub Languages + License

Key Features • - Download • - How To Use • + Installation • + Usage • + DocumentationSupportContributing • - Changelog • - Credits & Contributors + Credits • + License

- Python Logo Widget - Python Powered Height Widget - Python Powered Width Widget + Python Logo Widget + Python Powered Height Widget + Python Powered Width Widget
## Key Features -* High quality images. -* Easy to integrate into a `Tkinter` GUI project. -* Embeddable `tkinter.Frame` widgets — use them in your own windows. -* Standalone demo mode. -* Installable via `pip`. -* Cross platform. +- Three embeddable `tkinter.Frame` subclasses — put them straight into your own windows. +- Images bundled with the package and resolved through `importlib.resources`, so they work however you install it. +- Each widget keeps its own image reference, so nothing renders blank. +- A standalone demo, and backward-compatible wrappers for the original function API. +- Pure standard library — Tkinter only. +- Cross-platform. -## Download - -You can **[download](https://github.com/willtheorangeguy/Python-Logo-Widgets/releases/latest) the source code** to run the scripts from the command line on Windows, macOS and Linux. **This will require [Python](https://www.python.org/downloads/).** - -## How To Use - -### Install +## Installation ```bash pip install Python-Logo-Widgets ``` -### Embed in Your Application +## Usage ```python import tkinter as tk -from python_logo_widgets import LogoWidget, PoweredByLengthWidget, PoweredByWidthWidget +from python_logo_widgets import LogoWidget, PoweredByWidthWidget root = tk.Tk() -root.title("My App") - -# Add any combination of widgets to your window LogoWidget(root, bg="white").pack(pady=10) PoweredByWidthWidget(root).pack(side=tk.BOTTOM) - root.mainloop() ``` -Available widgets: - -| Widget Class | Image | +| Widget | Image | |---|---| -| `LogoWidget` | Python logo | -| `PoweredByLengthWidget` | "Python Powered" tall badge | -| `PoweredByWidthWidget` | "Python Powered" wide badge | +| `LogoWidget` | The Python logo | +| `PoweredByLengthWidget` | "Python Powered", tall | +| `PoweredByWidthWidget` | "Python Powered", wide | -Each widget accepts a `parent` widget and an optional `bg` parameter (default `"black"`). +Each takes a parent and an optional `bg` (default `"black"`). Demo: `python-logo-widgets` or `python -m python_logo_widgets`. -### Run the Demo +## Documentation -```bash -# Via the command line entry point -python-logo-widgets - -# Or as a module -python -m python_logo_widgets -``` - -### Git (Development) - -```bash -# Clone this repository -$ git clone https://github.com/willtheorangeguy/Python-Logo-Widgets - -# Go into the repository -$ cd Python-Logo-Widgets - -# Install in development mode -$ pip install -e . - -# Run the demo -$ python -m python_logo_widgets -``` +Full documentation lives in [`docs/`](docs/README.md): +[Quickstart](docs/quickstart.md) · [Installation](docs/installation.md) · [Configuration](docs/configuration.md) · [Architecture](docs/architecture.md) · [API](docs/api.md) · [Development](docs/development.md) · [FAQ](docs/faq.md) · [Troubleshooting](docs/troubleshooting.md) · [Roadmap](docs/roadmap.md) ## Support -Customizing the logo and widget sizes can be found in [`CUSTOMIZATION`](https://github.com/willtheorangeguy/Python-Logo-Widgets/blob/master/docs/CUSTOMIZATION.md). Hard-coding the widgets into your project can be found in [`USAGE`](https://github.com/willtheorangeguy/Python-Logo-Widgets/blob/master/docs/USAGE.md). More documentation is available in the **[Documentation](https://github.com/willtheorangeguy/Python-Logo-Widgets/tree/main/docs)** and on the **[Wiki](https://github.com/willtheorangeguy/Python-Logo-Widgets/wiki)**. If more support is required, please open a **[GitHub Discussion](https://github.com/willtheorangeguy/Python-Logo-Widgets/discussions/new)** or join our **[Discord](https://discord.gg/eAZZJzhHrW)**. +Open a [GitHub Discussion](https://github.com/willtheorangeguy/Python-Logo-Widgets/discussions/new), file an [issue](https://github.com/willtheorangeguy/Python-Logo-Widgets/issues/new/choose), or join the [Discord](https://discord.gg/eAZZJzhHrW). ## Contributing Please contribute using [GitHub Flow](https://guides.github.com/introduction/flow). Create a branch, add commits, and [open a pull request](https://github.com/willtheorangeguy/Python-Logo-Widgets/compare). -Please read [`CONTRIBUTING`](https://github.com/willtheorangeguy/.github/blob/main/CONTRIBUTING.md) for details on our [`CODE OF CONDUCT`](https://github.com/willtheorangeguy/.github/blob/main/CODE_OF_CONDUCT.md), and the process for submitting pull requests to us. - -## Changelog - -See the [`CHANGELOG`](CHANGELOG.md) file for details. +See the org-wide [Contributing Guide](https://github.com/willtheorangeguy/.github/blob/main/CONTRIBUTING.md) and [Code of Conduct](https://github.com/willtheorangeguy/.github/blob/main/CODE_OF_CONDUCT.md). ## Credits @@ -162,16 +108,14 @@ This software uses the following open source packages, projects, services or web Web - Donate -## Contributors - -* [@willtheorangeguy](https://github.com/willtheorangeguy) - Sponsor on [PayPal](https://paypal.me/wvdg44?country.x=CA&locale.x=en_US) +Sponsor [@willtheorangeguy](https://github.com/willtheorangeguy) on [PayPal](https://paypal.me/wvdg44?country.x=CA&locale.x=en_US). -## You may also like... +## License -* [Running Calculator](https://github.com/willtheorangeguy/Running-Calculator) - A running speed calculator for any unit of distance. -* [PyWorkout](https://github.com/willtheorangeguy/PyWorkout) - A minimal CLI to keep you inspired during your workout! -* [PyAvatar](https://github.com/willtheorangeguy/PyAvatar) - Easily display all of your creative avatars to keep them consistent across websites. +The **code** in this repository is licensed under the [GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.en.html) — see [`LICENSE.md`](LICENSE.md). -## License +> ⚠️ **The bundled images are not covered by that licence.** The Python logo and the "Python Powered" badges are copyright and trademarks of the [Python Software Foundation](https://www.python.org/psf/trademarks/). They are included here under the PSF's [Trademark Usage Policy](https://www.python.org/psf/trademarks/), which is what governs their use — not the GPL. +> +> In practice: displaying the badges to show your software is built with Python is the use the PSF intends and this package makes convenient. **Modifying the marks, or redistributing altered versions, is not permitted**, whatever rights the GPL would otherwise grant over files in this repository. -This project is licensed under the [GNU General Public License](https://www.gnu.org/licenses/gpl-3.0.en.html) - see the [`LICENSE`](LICENSE.md) file for details. See the [Privacy Policy](https://github.com/willtheorangeguy/Python-Logo-Widgets/blob/master/docs/legal/PRIVACY.md) and [Terms and Conditions](https://github.com/willtheorangeguy/Python-Logo-Widgets/blob/master/docs/legal/TERMS.md) for legal information. +[Privacy Policy](docs/legal/privacy.md) · [Terms and Conditions](docs/legal/terms.md) diff --git a/docs/CUSTOMIZATION.md b/docs/CUSTOMIZATION.md deleted file mode 100644 index 2c86da5..0000000 --- a/docs/CUSTOMIZATION.md +++ /dev/null @@ -1,41 +0,0 @@ -# Python Logo Widgets Customization - -## Change Background Color - -Pass the `bg` parameter when creating a widget: - -```python -from python_logo_widgets import LogoWidget - -# White background -LogoWidget(root, bg="white").pack() - -# Custom hex color -LogoWidget(root, bg="#2b2b2b").pack() -``` - -## Pass Additional Frame Options - -All extra keyword arguments are passed through to `tk.Frame`: - -```python -LogoWidget(root, bg="white", padx=10, pady=10, relief="raised", borderwidth=2).pack() -``` - -## Change Image - -To use a custom image, subclass the widget and override the image path: - -```python -import tkinter as tk -from python_logo_widgets.widgets import _load_image - -class CustomLogoWidget(tk.Frame): - def __init__(self, parent, bg="black", **kwargs): - super().__init__(parent, **kwargs) - self._image = tk.PhotoImage(file="path/to/your/image.gif") - self._label = tk.Label(self, image=self._image, bg=bg) - self._label.pack(fill=tk.BOTH, expand=True) -``` - -**Note:** `tkinter.PhotoImage` supports `.gif` and `.png` formats. diff --git a/docs/README.md b/docs/README.md index e6ba192..11e0a2d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,14 +1,64 @@ -# Python Logo Widgets Documentation - -This folder includes all of the docs for the Python Logo Widgets. More can be found on the [wiki](https://github.com/willtheorangeguy/Python-Logo-Widgets/wiki). - -```text -├── docs -| ├── images -| ├── legal -| | ├── PRIVACY.md -| | ├── TERMS.md -| ├── USAGE.md -| ├── CUSTOMIZATION.md -└── README.md +# Python Logo Widgets — Documentation + +Three Tkinter `Frame` subclasses that display the Python logo and the "Python Powered" badges, +with the images bundled so you do not have to ship them yourself. + +``` +python_logo_widgets/ +├── widgets.py LogoWidget, PoweredByLengthWidget, PoweredByWidthWidget +├── _compat.py the original function API, kept working +├── _demo.py the standalone demo +├── __main__.py python -m python_logo_widgets +└── imgs/ logo.gif, length.gif, width.gif ``` + +## Pages + +- [Quickstart](./quickstart.md) — embed one in five lines +- [Installation](./installation.md) — pip, or from source +- [Configuration](./configuration.md) — the `bg` argument and sizing +- [Architecture](./architecture.md) — how the images are found and kept alive +- [API](./api.md) — the three classes and the compatibility functions +- [Development](./development.md) — tests and conventions +- [FAQ](./faq.md) — trademarks, sizing, why GIF +- [Troubleshooting](./troubleshooting.md) — blank widgets, missing images +- [Roadmap](./roadmap.md) — direction and non-goals +- [Known issues](./internal/known-issues.md) — recorded defects + +## Before you redistribute this + +⚠️ **The images are PSF property, not GPL-licensed content.** + +The repository's `LICENSE.md` is GPL v3, which grants recipients the right to modify and +redistribute everything it covers. The Python logo and the "Python Powered" badges are trademarks +of the Python Software Foundation, governed by the +[PSF Trademark Usage Policy](https://www.python.org/psf/trademarks/) — they are not the author's +to place under GPL, and the policy restricts modifying them in ways the GPL explicitly permits. + +Using the badges to indicate that your software is built with Python is the use the PSF intends. +Treating the image files as GPL content you may alter and redistribute is not. + +**The position, decided:** the repository's licence stays GPL v3 for the code, and the images +carry the PSF's own terms. The README states the split, and this page and the +[FAQ](./faq.md) repeat it, so nobody has to infer it from a licence file that does not mention +the marks. + +Recorded in [`internal/known-issues.md`](./internal/known-issues.md) for the record. + +## The legal documents + +`docs/legal/PRIVACY.md` and `docs/legal/TERMS.md` are generated boilerplate describing accounts, +personal data collection, and "the Service". This package has none of those — it opens no +network connection, stores nothing, and reads only its own bundled images. + +Same known-issues file. + +## What it actually does + +Loads three GIFs from the installed package and puts each in a `Label` inside a `Frame`. That is +the whole implementation, and it is about seventy lines. + +The value is in the details it gets right: resources resolved through `importlib.resources` so +they are found however the package is installed, and each image bound to `self._image` so Tkinter +cannot garbage-collect it out from under the label. Both are covered in +[Architecture](./architecture.md). diff --git a/docs/USAGE.md b/docs/USAGE.md deleted file mode 100644 index 2c9e30c..0000000 --- a/docs/USAGE.md +++ /dev/null @@ -1,84 +0,0 @@ -# Python Logo Widgets Usage - -## Install via `pip` - -```bash -pip install Python-Logo-Widgets -``` - -## Embed in Your Application - -Import the widget classes and add them to your `tkinter` window: - -```python -import tkinter as tk -from python_logo_widgets import LogoWidget, PoweredByLengthWidget, PoweredByWidthWidget - -root = tk.Tk() -root.title("My Application") - -# Add any combination of widgets -logo = LogoWidget(root, bg="white") -logo.pack(pady=10) - -powered = PoweredByWidthWidget(root) -powered.pack(side=tk.BOTTOM) - -root.mainloop() -``` - -### Available Widgets - -| Widget Class | Image | Description | -|---|---|---| -| `LogoWidget` | `logo.gif` | The Python logo | -| `PoweredByLengthWidget` | `length.gif` | "Python Powered" tall badge | -| `PoweredByWidthWidget` | `width.gif` | "Python Powered" wide badge | - -### Parameters - -All widgets accept: - -- **`parent`** — The parent `tkinter` widget (required). -- **`bg`** — Background color for the image label (default: `"black"`). -- **`**kwargs`** — Any additional keyword arguments are passed to `tk.Frame`. - -### Layout - -The widgets are `tkinter.Frame` subclasses, so you can use any geometry manager: - -```python -# Using pack -LogoWidget(root).pack(side=tk.TOP, pady=5) - -# Using grid -LogoWidget(root).grid(row=0, column=0, padx=10) - -# Using place -LogoWidget(root).place(x=50, y=50) -``` - -## Standalone Demo - -To see all three widgets in a demo window: - -```bash -# Command line -python-logo-widgets - -# As a module -python -m python_logo_widgets -``` - -## Legacy Standalone Functions - -For backward compatibility, the old standalone functions are still available: - -```python -from python_logo_widgets import logo_widget, length_widget, width_widget - -# Each opens its own window with mainloop() -logo_widget() -``` - -These create their own `Tk()` root window and call `mainloop()`, so they cannot be embedded in an existing application. diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..38c199a --- /dev/null +++ b/docs/api.md @@ -0,0 +1,83 @@ +# Python Logo Widgets — API + +## Widget classes + +All three subclass `tkinter.Frame` and share a signature. + +```python +LogoWidget(parent, bg="black", **kwargs) +PoweredByLengthWidget(parent, bg="black", **kwargs) +PoweredByWidthWidget(parent, bg="black", **kwargs) +``` + +| Parameter | Meaning | +|---|---| +| `parent` | The parent Tkinter widget | +| `bg` | Background of the inner image label; default `"black"` | +| `**kwargs` | Forwarded to `tk.Frame` | + +| Class | Image | +|---|---| +| `LogoWidget` | The Python logo | +| `PoweredByLengthWidget` | "Python Powered", tall | +| `PoweredByWidthWidget` | "Python Powered", wide | + +Being `Frame` subclasses, they support the full widget protocol: + +```python +w = LogoWidget(root, bg="white") +w.pack(side=tk.LEFT, padx=8) +w.grid(row=0, column=1) +w.destroy() +``` + +### Instance attributes + +| Attribute | What | +|---|---| +| `_image` | The `PhotoImage`. Private, but the reference that keeps the image alive | +| `_label` | The `Label` holding it | + +Underscore-prefixed and not part of the supported API. Reassigning `_image` without also updating +`_label` leaves the widget showing the old image or nothing. + +## Compatibility functions + +```python +from python_logo_widgets._compat import logo_widget, length_widget, width_widget + +logo_widget() # opens a window, blocks until closed +``` + +The original API. Each creates its own `Tk()` root, packs the corresponding widget, and calls +`mainloop()` — so they **block** and cannot be embedded. + +Kept for code written against the first release; implemented in terms of the widget classes, so +there is one implementation. New code should use the classes. + +## Entry points + +```bash +python-logo-widgets # console script +python -m python_logo_widgets # module +``` + +Both run the demo in `_demo.py`. + +## Imports + +```python +from python_logo_widgets import LogoWidget, PoweredByLengthWidget, PoweredByWidthWidget +``` + +Importing the package opens no window and loads no image — `PhotoImage` construction happens in +each widget's `__init__`, which requires a live Tk root. Instantiating one before `tk.Tk()` exists +raises. + +## Images + +Resolved with `importlib.resources.files("python_logo_widgets.imgs")`, so they are found however +the package is installed. GIF, because Tkinter reads GIF and PNG only. + +They are PSF trademarks rather than GPL content — see [FAQ](./faq.md) and +[`internal/known-issues.md`](./internal/known-issues.md). diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..08d3857 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,91 @@ +# Python Logo Widgets — Architecture + +Seventy lines of implementation, and two details that make it work. + +``` +python_logo_widgets/ +├── widgets.py three tk.Frame subclasses +├── _compat.py the original function API, wrapping the classes +├── _demo.py the standalone demo +├── __main__.py python -m entry +└── imgs/ logo.gif, length.gif, width.gif +``` + +## The widgets + +All three are the same shape: + +```python +class LogoWidget(tk.Frame): + def __init__(self, parent, bg="black", **kwargs): + super().__init__(parent, **kwargs) + self._image = tk.PhotoImage(file=_load_image("logo.gif")) + self._label = tk.Label(self, image=self._image, bg=bg) + self._label.pack(fill=tk.BOTH, expand=True) +``` + +Subclassing `Frame` rather than exposing a function is what makes them **embeddable** — the +caller places them like any other widget, in any geometry manager, inside any container. + +## Detail one: finding the images + +```python +from importlib.resources import files + +def _load_image(image_name): + return str(files("python_logo_widgets.imgs").joinpath(image_name)) +``` + +Resolved through the package, not by a path relative to the source file or the working directory. +That is why the widgets work identically from a source checkout, a wheel install, and someone +else's application — the three ways a bundled resource usually gets lost. + +`imgs/__init__.py` exists to make the directory an importable package so `files()` can address it. + +One limitation: `str()` on the result assumes the resource has a real filesystem path, which is +true for a normal pip install and not for a zipped package. `importlib.resources.as_file()` is +the supported idiom. See [`internal/known-issues.md`](./internal/known-issues.md). + +## Detail two: keeping the images alive + +```python +self._image = tk.PhotoImage(...) +``` + +Tkinter holds no Python reference to a `PhotoImage`. An image assigned only to a local would be +garbage-collected when `__init__` returns, and the label would render **blank with no error** — +the single most common Tkinter bug, and one that looks like a missing file. + +Binding it to the instance ties the image's lifetime to the widget's, which is exactly right. + +## `_compat.py` + +The original API was three functions that each opened a window: + +```python +def logo_widget(): + root = tk.Tk() + root.title("Python Logo Widget") + LogoWidget(root).pack() + root.mainloop() +``` + +They are kept, implemented in terms of the new classes rather than duplicating them, so old code +keeps working and there is one implementation. Underscore-prefixed to signal they are not the +recommended API. + +## `_demo.py` and `__main__.py` + +The demo window shown by `python-logo-widgets` and `python -m python_logo_widgets`. Separate from +the library so importing the package opens nothing. + +## Why GIF + +Tkinter's `PhotoImage` reads GIF and PNG only. PNG would work; GIF is what is bundled. Anything +else — JPEG, SVG, WebP — needs Pillow, and a widget package that pulled in an imaging library to +show three static badges would not be worth depending on. + +## What is not here + +No scaling, no theming beyond `bg`, no state, no I/O. The package loads three images and draws +them, and its correctness is entirely in the two details above. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..b3a0236 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,63 @@ +# Python Logo Widgets — Configuration + +Almost nothing to configure, by design. One argument. + +## `bg` + +```python +LogoWidget(parent, bg="white") +``` + +Sets the background of the image `Label`. Defaults to `"black"`. + +The badges have transparent regions, and Tkinter fills transparency with the label's background — +so `bg` is effectively "what colour shows through the logo". Match it to the window behind, or +the widget appears as a black rectangle on a light background. + +Any Tkinter colour works: a name (`"white"`), a hex string (`"#f0f0f0"`), or a system colour. + +## Everything else goes to `tk.Frame` + +```python +LogoWidget(parent, bg="white", borderwidth=2, relief=tk.RIDGE) +``` + +`**kwargs` are forwarded to `tk.Frame.__init__`, so padding, borders, and relief work as usual. + +Note the distinction: `bg` is applied to the inner **label**, and the remaining arguments to the +outer **frame**. Passing `background=` as a keyword sets the frame's, not the image's. + +## Sizing + +There is no size parameter. Each widget displays its GIF at native resolution, and the label is +packed with `fill=tk.BOTH, expand=True` so the frame follows the image. + +Tkinter's `PhotoImage` can only scale by integer factors, via `subsample` and `zoom`: + +```python +w = LogoWidget(root) +w._image = w._image.subsample(2) # half size +w._label.configure(image=w._image) +``` + +That reaches into private attributes, and half or a third is all you get. Smooth scaling needs +Pillow, which this package deliberately does not depend on. See [Roadmap](./roadmap.md). + +Supplying differently-sized images is the practical answer — but note the trademark position on +modifying the logos in [FAQ](./faq.md). + +## The images + +| File | Widget | +|---|---| +| `imgs/logo.gif` | `LogoWidget` | +| `imgs/length.gif` | `PoweredByLengthWidget` | +| `imgs/width.gif` | `PoweredByWidthWidget` | + +GIF because Tkinter's `PhotoImage` reads GIF and PNG only. They are resolved through +`importlib.resources`, not by path, so there is no directory to configure. + +## No runtime state + +No config file, no environment variable, no cache, and nothing written to disk. The package +reads its own bundled images and draws them. diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..82c4341 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,73 @@ +# Python Logo Widgets — Development + +## Setup + +```bash +git clone https://github.com/willtheorangeguy/Python-Logo-Widgets +cd Python-Logo-Widgets +pip install -e . +pytest +``` + +No runtime dependencies; `requirements.txt` holds the tooling. + +## Layout + +| File | Responsibility | +|---|---| +| `widgets.py` | The three `Frame` subclasses | +| `_compat.py` | The original function API, wrapping the classes | +| `_demo.py` | The demo window | +| `__main__.py` | `python -m` entry | +| `imgs/` | The three GIFs, plus an `__init__.py` so `importlib.resources` can address them | + +## Tests + +```bash +pytest +pytest tests/test_widgets.py -v +``` + +`test_widgets.py` covers the widget classes; `test_compat.py` the legacy functions. + +Tkinter tests need a display. On a headless Linux runner: + +```bash +xvfb-run -a pytest +``` + +## Conventions + +- **Resources through `importlib.resources`**, never a path relative to `__file__` or the working + directory. It is what makes the package work from a wheel, a checkout, and inside someone + else's application. +- **Bind every `PhotoImage` to the instance.** Tkinter keeps no reference of its own, and a + collected image renders blank with no error. +- **`_compat` wraps the classes**, never duplicates them. One implementation. +- **No runtime dependencies.** Pillow would buy scaling and other formats and is still not worth + being the first dependency of a package that draws three static images. +- **GPL header on every module** — required by the licence. + +## Adding a widget + +Add the GIF to `imgs/`, add a class following the existing three, export it from `__init__.py`, +and add a test. The shape is deliberate; keep it. + +Before adding an image, note the trademark position on the ones already here — see +[`internal/known-issues.md`](./internal/known-issues.md). + +## The `str(files(...))` limitation + +`_load_image` returns `str(files(...).joinpath(name))`, which assumes a real filesystem path. +That holds for pip installs and not for a zipped package. `importlib.resources.as_file()` is the +supported idiom and would need the path used inside a context manager. Same known-issues file. + +## Licence + +GPL v3 for the code. The images are PSF trademarks under separate terms — contributions of code +are GPL; contributions of trademarked artwork are a different question entirely. + +## Recording defects + +Bugs found while working here go in [`internal/known-issues.md`](./internal/known-issues.md) +rather than being fixed in passing, unless fixing them is the job you are on. diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..2dcabd5 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,78 @@ +# Python Logo Widgets — FAQ + +### Can I use the Python logo in my application? + +The PSF's [Trademark Usage Policy](https://www.python.org/psf/trademarks/) governs that, not this +package's licence. Broadly, the "Python Powered" badges exist to indicate that software is built +with Python, and that use is what they are for. Modifying the marks, or using them in ways that +suggest PSF endorsement, is not. + +This package makes the badges convenient to display. It does not — and cannot — grant you rights +to them. + +### The repository is GPL. Does that cover the images? + +**No.** `LICENSE.md` is GPL v3 and covers the code. The Python logo and the "Python Powered" +badges are copyright and trademarks of the Python Software Foundation, included here under the +PSF's [Trademark Usage Policy](https://www.python.org/psf/trademarks/) — which is what governs +them. + +So: the GPL's permission to modify and redistribute applies to the source, not to the marks. +Display them; do not alter them or ship altered versions. + +The licence file is deliberately left as it is; the split is stated in the README, the +[documentation index](./README.md), and here. + +### Why is there a privacy policy? + +Boilerplate, and it does not describe this package. `docs/legal/PRIVACY.md` talks about accounts, +personal data, and "the Service"; this is an offline widget library that opens no connection and +stores nothing. + +Recorded in the same file. Read the code — it is seventy lines. + +### My widget shows a black box. + +`bg` defaults to `"black"`, and the badges have transparent regions that take the label's +background colour. Pass `bg` matching your window: + +```python +LogoWidget(root, bg="white") +``` + +### The widget is blank. + +Unlikely with these classes — each binds its `PhotoImage` to `self._image` specifically to +prevent it. If you see it anyway, you have probably reassigned `_image` without updating +`_label`. See [Troubleshooting](./troubleshooting.md). + +### How do I resize a widget? + +There is no size parameter. `PhotoImage` scales only by integer factors (`subsample`, `zoom`), +which means half or a third and nothing between. Smooth scaling needs Pillow, which this package +deliberately does not depend on. + +Supplying your own image is the practical route — subject to the trademark point above. + +### Why GIF and not PNG or SVG? + +Tkinter's `PhotoImage` reads GIF and PNG only. PNG would work equally well; SVG and everything +else needs Pillow or a converter. + +### Can I embed these in a `ttk` layout? + +Yes. They are `tk.Frame` subclasses, and `ttk` containers accept them like any other widget. + +### Do the old functions still work? + +Yes — `logo_widget()`, `length_widget()`, `width_widget()` in `python_logo_widgets._compat`. They +open their own window and block, so they cannot be embedded. New code should use the classes. + +### Does it need internet, or write anything? + +Neither. It reads three files from inside its own package. + +### Why depend on this at all rather than copying the GIFs? + +Because the images resolve correctly however the package is installed, and each widget keeps its +image alive — two things that are easy to get wrong by hand, and that fail silently when you do. diff --git a/docs/images/logo.png b/docs/images/logo.png deleted file mode 100644 index 926ad86..0000000 Binary files a/docs/images/logo.png and /dev/null differ diff --git a/docs/images/pythonlogo.png b/docs/images/pythonlogo.png deleted file mode 100644 index 8a591b9..0000000 Binary files a/docs/images/pythonlogo.png and /dev/null differ diff --git a/docs/images/pythonpoweredheight.png b/docs/images/pythonpoweredheight.png deleted file mode 100644 index 3218d5f..0000000 Binary files a/docs/images/pythonpoweredheight.png and /dev/null differ diff --git a/docs/images/pythonpoweredwidth.png b/docs/images/pythonpoweredwidth.png deleted file mode 100644 index 542fa7c..0000000 Binary files a/docs/images/pythonpoweredwidth.png and /dev/null differ diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..4ab308d --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,67 @@ +# Python Logo Widgets — Installation + +## Requirements + +| | | +|---|---| +| Python | 3.x with Tkinter | +| Dependencies | None | + +Tkinter is bundled on Windows and macOS. On Linux: + +```bash +sudo apt install python3-tk # Debian, Ubuntu +sudo dnf install python3-tkinter # Fedora +``` + +## Install + +```bash +pip install Python-Logo-Widgets +``` + +The images are packaged inside `python_logo_widgets/imgs/`, so there is nothing to copy +alongside and no path to configure. + +## From source + +```bash +git clone https://github.com/willtheorangeguy/Python-Logo-Widgets +cd Python-Logo-Widgets +pip install -e . +python -m python_logo_widgets +``` + +## Verify + +```bash +python-logo-widgets +``` + +A window with the Python logo. If it opens empty, see [Troubleshooting](./troubleshooting.md) — +though the widget classes are written specifically to prevent the usual cause. + +```python +from python_logo_widgets import LogoWidget, PoweredByLengthWidget, PoweredByWidthWidget +``` + +## Adding it to your project + +``` +python-logo-widgets +``` + +in your `requirements.txt` or `pyproject.toml` dependencies. It adds no transitive dependencies +of its own. + +Note the licence position before you do: the code is GPL v3, which is a real obligation for +anything you link it into, and the images are PSF trademarks under separate terms. See +[FAQ](./faq.md). + +## Uninstall + +```bash +pip uninstall Python-Logo-Widgets +``` + +Nothing is written outside the package — no cache, no config, no data. diff --git a/docs/internal/known-issues.md b/docs/internal/known-issues.md new file mode 100644 index 0000000..75f337d --- /dev/null +++ b/docs/internal/known-issues.md @@ -0,0 +1,61 @@ +# Known Issues — Python-Logo-Widgets + +Concrete defects and gaps found while writing this repository's documentation in +August 2026. **Nothing here was changed** — each one needs a code, configuration, or +licensing decision rather than a documentation one. + +Ordered by severity. See [`docs/roadmap.md`](../roadmap.md) for the narrative version, +which also covers deliberate non-goals. + + +**3 open:** 1 medium, 2 low. + +## 1. The privacy policy and terms describe a service this package is not + +**Severity:** Medium +**Where:** `docs/legal/PRIVACY.md`, `docs/legal/TERMS.md` + +**What:** Both are generated boilerplate dated 29 August 2022. `PRIVACY.md` opens 'This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service' and defines Account, Affiliate, and Personal Data. The package is an offline Tkinter widget library: it opens no network connection, creates no account, stores nothing, and reads only three GIFs from inside its own installation. The README links both from its License section. + +**Why it matters:** A privacy policy is a statement about data handling, and this one asserts handling that does not occur -- accounts, personal data, disclosure to affiliates. That is misleading in the direction people do not expect documents to be misleading: it makes a package that collects nothing look like one that collects something, and a reader doing due diligence on dependencies has to read seventy lines of source to establish that the legal document is wrong about its own subject. Boilerplate is worse than absence here. + +**Suggested fix:** Delete both, or replace them with two sentences: this package collects no data, makes no network requests, and writes nothing to disk. If terms are wanted for the PyPI listing, they should describe a library rather than a Service with Accounts. + +## 2. The GPL licence file does not mention the PSF marks it ships alongside + +**Severity:** Low +**Where:** `LICENSE.md`, `README.md` (warning added in this pass), `python_logo_widgets/imgs/*.gif` + +**What:** `LICENSE.md` is the GNU GPL v3 with no carve-out, and the three bundled GIFs are the Python logo and the two 'Python Powered' badges -- copyright and trademarks of the Python Software Foundation, governed by the [PSF Trademark Usage Policy](https://www.python.org/psf/trademarks/). The package is published to PyPI, so the images are redistributed with it. Read alone, the licence file implies the GPL's modify-and-redistribute permission extends to the marks; it does not. + +**Why it matters:** **Resolved -- the owner has decided the licence stays as it is, with a warning added instead.** The remaining exposure is that `LICENSE.md` is the file a redistributor reads first and it still says nothing about the marks, so the correction lives only in the README and these docs. That is a documentation-shaped mitigation for a licence-shaped ambiguity, which is why this stays on the list rather than being closed outright. + +**Suggested fix:** Done in this pass: the README carries a prominent warning that the images are PSF property and not covered by the GPL, repeated in the documentation index and the FAQ. If it ever becomes worth strengthening, a short `NOTICE` file beside `LICENSE.md` -- naming the marks, the PSF, and the trademark policy -- would put the same statement where a redistributor actually looks, without altering the licence itself. + +## 3. str(files(...)) assumes a filesystem path, so the package breaks when zipped + +**Severity:** Low +**Where:** `python_logo_widgets/widgets.py` -> `_load_image` + +**What:** `_load_image` returns `str(files("python_logo_widgets.imgs").joinpath(image_name))` and hands the result to `tk.PhotoImage(file=...)`. `files()` returns a `Traversable`, which only has a real filesystem path when the package is installed unzipped. For a zipimported package -- a zipapp, some frozen builds, an egg -- `str()` produces something that is not a readable path and `PhotoImage` fails. + +**Why it matters:** The rest of this module is careful about exactly this class of problem: using `importlib.resources` at all, rather than a path relative to `__file__`, is what makes the widgets work from a wheel and from inside someone else's application. This is the last step of that reasoning left incomplete, and it fails in the packaging scenarios a widget library is most likely to be swept into -- someone bundling their Tkinter app for distribution. + +**Suggested fix:** Use `importlib.resources.as_file()`, which materialises a real path for the duration of a context manager: + + with as_file(files("python_logo_widgets.imgs") / image_name) as path: + image = tk.PhotoImage(file=str(path)) + +The `PhotoImage` must be constructed inside the block, since the temporary file may be removed on exit. + + +--- + +## Also, across every repository + +**`.bandit` is present on disk but untracked in git.** Verified in PyWorkout, treklogger, +skyscanner-cli, booking-cli, piggy, and aibot — the config file exists locally in each but +`git ls-files` does not know about it, so none of it reached GitHub. + +The August 2026 security sweep therefore looks complete locally and landed nowhere. Worth +checking across all 44 repositories it covered. diff --git a/docs/legal/PRIVACY.md b/docs/legal/privacy.md similarity index 100% rename from docs/legal/PRIVACY.md rename to docs/legal/privacy.md diff --git a/docs/legal/TERMS.md b/docs/legal/terms.md similarity index 100% rename from docs/legal/TERMS.md rename to docs/legal/terms.md diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..4b2c1e4 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,70 @@ +# Python Logo Widgets — Quickstart + +## Install + +```bash +pip install Python-Logo-Widgets +``` + +Nothing else — Tkinter ships with Python. + +## Embed one + +```python +import tkinter as tk +from python_logo_widgets import LogoWidget, PoweredByWidthWidget + +root = tk.Tk() +root.title("My App") + +LogoWidget(root, bg="white").pack(pady=10) +PoweredByWidthWidget(root).pack(side=tk.BOTTOM) + +root.mainloop() +``` + +Each widget is a `tk.Frame`, so it behaves like any other container — `pack`, `grid`, or `place` +it wherever you like. + +## The three widgets + +| Class | Image | Typical use | +|---|---|---| +| `LogoWidget` | The Python logo | A splash or about screen | +| `PoweredByLengthWidget` | "Python Powered", tall | A sidebar | +| `PoweredByWidthWidget` | "Python Powered", wide | A footer | + +## Background colour + +```python +LogoWidget(root, bg="white") # default is "black" +``` + +`bg` sets the background of the image label. The badges have transparent regions, so matching +`bg` to your window is usually what you want — the default black suits a dark window and looks +like a box on a light one. + +Any other keyword arguments go to `tk.Frame`. + +## See them + +```bash +python-logo-widgets +python -m python_logo_widgets +``` + +## The older function API + +```python +from python_logo_widgets._compat import logo_widget +logo_widget() # opens its own window and blocks +``` + +Kept working for code written against the original release. New code should use the widget +classes — they embed, and these do not. + +## Before you ship it + +The images are PSF trademarks rather than GPL content. Using them to show your program is built +with Python is the intended use; see [FAQ](./faq.md) and +[`internal/known-issues.md`](./internal/known-issues.md). diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..34381d0 --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,59 @@ +# Python Logo Widgets — Roadmap + +Direction, not a schedule. Defects are in +[`internal/known-issues.md`](./internal/known-issues.md). + +## Where it is + +Three embeddable widgets, images bundled and resolved correctly, references held properly, the +original function API preserved, and tests for both. It does what it says. + +## Settled + +**The licence position on the images.** The repository stays GPL v3 for the code, with the Python +logo and "Python Powered" badges carrying the PSF's own copyright and trademark terms. That split +is now stated in the README, the documentation index, and the FAQ rather than left to be inferred +from a licence file that does not mention the marks. + +## Considered + +**Replacing the boilerplate legal documents.** A privacy policy describing account creation and +personal data collection, for a package that opens no connection, is worse than none — it implies +data handling that does not exist. + +**`importlib.resources.as_file()`** instead of `str(files(...))`, so the package works when +zipped. + +**PNG instead of GIF.** Tkinter reads both, and PNG handles transparency better — which is the +one visual rough edge, since `bg` currently has to be matched by hand. + +**A size argument.** Only integer scaling is available without Pillow, so this means either +bundling several sizes or accepting a dependency. Worth deciding rather than leaving as an +absence. + +## Non-goals + +**Pillow, or any runtime dependency.** A package that draws three static images should not pull +an imaging library into everything that uses it. That constraint is why the implementation is +seventy lines. + +**More logos.** The Python marks are the point. A general badge widget is a different package, +and one without this one's trademark complications. + +**Anything beyond display.** No animation, no theming engine, no state. `bg` and the standard +`Frame` arguments are the whole surface, deliberately. + +**Granting rights to the trademarks.** Not possible, and worth saying plainly: this package makes +the badges convenient to display and confers nothing. The PSF's policy governs. + +**Porting to another toolkit.** Qt and GTK have their own resource and image handling; the value +here is specific to Tkinter's `PhotoImage` pitfalls. + +## Contributing + +Issues and pull requests welcome — see the +[Contributing Guide](https://github.com/willtheorangeguy/.github/blob/main/CONTRIBUTING.md) or +the [Discord](https://discord.gg/eAZZJzhHrW). Code contributions are GPL v3. + +Please do not open a pull request against the trademark question; that is the repository owner's +call. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..52933d6 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,97 @@ +# Python Logo Widgets — Troubleshooting + +## The widget is a black rectangle + +`bg` defaults to `"black"`, and the badges have transparent areas that show the label's +background. Match it to your window: + +```python +LogoWidget(root, bg="white") +``` + +## The widget is blank + +The classic Tkinter cause is a garbage-collected `PhotoImage` — but these classes bind it to +`self._image` precisely to prevent that, so it should not happen through normal use. + +If it does, check you have not reassigned `_image` without updating `_label`: + +```python +w._image = w._image.subsample(2) +w._label.configure(image=w._image) # both, or the label keeps the old one +``` + +## `RuntimeError: Too early to create image` + +You instantiated a widget before creating a `Tk()` root. `PhotoImage` needs a live interpreter: + +```python +root = tk.Tk() # first +LogoWidget(root) # then +``` + +## `ModuleNotFoundError: No module named 'tkinter'` + +Separate package on most Linux distributions: + +```bash +sudo apt install python3-tk # Debian, Ubuntu +sudo dnf install python3-tkinter # Fedora +``` + +## `FileNotFoundError` for a bundled image + +The package's `imgs/` directory did not install. Reinstall: + +```bash +pip install --force-reinstall Python-Logo-Widgets +``` + +If you are running from a source checkout, confirm `python_logo_widgets/imgs/` contains +`logo.gif`, `length.gif`, `width.gif`, and `__init__.py` — the last is what makes the directory +addressable by `importlib.resources`. + +## It fails inside a zipped package or a frozen build + +`_load_image` returns `str(files(...).joinpath(name))`, which assumes the resource has a real +filesystem path. A zipimported package has none. Recorded in +[`internal/known-issues.md`](./internal/known-issues.md); `importlib.resources.as_file()` is the +fix. + +Normal pip installs are unzipped, so this only affects unusual packaging. + +## The demo does nothing + +```bash +python-logo-widgets +python -m python_logo_widgets +``` + +Both should open a window. If neither does, check the install placed the console script on your +`PATH` — `python -m` works regardless. + +## Tests fail on a headless machine + +Tkinter needs a display: + +```bash +xvfb-run -a pytest +``` + +## The image is too large for my layout + +There is no size parameter, and `PhotoImage` scales only by integer factors. See +[Configuration](./configuration.md) — and note the trademark position on modifying the marks in +[FAQ](./faq.md). + +## `pack` and `grid` conflict + +Each widget uses `pack` **internally**, for its own label. That does not constrain how you place +the widget itself — mixing managers in one container is the error, and these keep theirs inside +their own frame. + +## Still stuck + +[Open an issue](https://github.com/willtheorangeguy/Python-Logo-Widgets/issues/new/choose) or ask +on the [Discord](https://discord.gg/eAZZJzhHrW), with your OS, Python version, and how you +installed it.