Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dc184ce
Created context manager
liiight Aug 2, 2017
8cc412b
Added test
liiight Aug 2, 2017
d0d63d0
Fixed pep8
liiight Aug 2, 2017
384e6f8
Publish test
polyzen Nov 22, 2018
f038a8e
Exclude useless file in dist, #17
lord63 Jan 1, 2019
c6a7d56
Avoid ending output when spinner never starts
lord63 Mar 21, 2026
866dc91
Unify spinner rendering logic
lord63 Mar 21, 2026
60da802
Add spinner regression tests
lord63 Mar 21, 2026
ac4b087
Make spinner lifecycle helpers private
lord63 Mar 21, 2026
4ea76f7
Guard against re-entering a running spinner
lord63 Mar 21, 2026
eb0454c
Rename stop event for clarity
lord63 Mar 21, 2026
4c1e96d
Reset spinner state after stop
lord63 Mar 21, 2026
a2e3ac2
Rename init_spin to _init_spin
lord63 Mar 21, 2026
d2105c9
Deduplicate TTY check in make_spin
lord63 Mar 21, 2026
c73e6c3
Shorten context manager test wait
lord63 Mar 21, 2026
592b003
Harden tty detection
lord63 Mar 21, 2026
8ff29a3
Flush ending output in make_spin
lord63 Mar 21, 2026
8b03e16
Document Spinner context manager usage
lord63 Mar 21, 2026
a59c2f8
Fix no-isatty stdout test double
lord63 Mar 21, 2026
57b28da
Handle non-callable isatty safely
lord63 Mar 21, 2026
753eea8
Stabilize context manager test stdout handling
lord63 Mar 21, 2026
de146ec
Relax flush test timing-sensitive assertions
lord63 Mar 21, 2026
1b67847
Add more tests
lord63 Mar 21, 2026
2a0b42b
Merge pull request #22 from lord63/contex_manager_fixes
lord63 Mar 21, 2026
57a1704
Migrate to uv and github action, drop 2.x support
lord63 Mar 21, 2026
3d54390
Apply PR review: frozen sync, ruff lint, https URL, remove redundant …
Copilot Mar 21, 2026
e21ea5f
Set spinner thread as daemon to prevent hanging process on unexpected…
lord63 Mar 21, 2026
c223308
Modernize examples: drop Python 2 leftovers, add context manager usage
lord63 Mar 21, 2026
9a9bd69
Add comments
lord63 Mar 21, 2026
d2674b6
Release 1.2.0
lord63 Mar 21, 2026
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
30 changes: 30 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: CI

on:
push:
branches: [master, dev]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: uv sync --dev --frozen

- name: Lint
run: uv run ruff check .

- name: Run tests
run: uv run pytest -v test_pyspin.py
19 changes: 0 additions & 19 deletions .travis.yml

This file was deleted.

13 changes: 13 additions & 0 deletions ChangLog → ChangLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
Version numbers are following the Semantic Versioning.

2026.03.21 v1.2.0
- add
- context manager support for Spinner, thanks to @liiight
- fix
- flush ending output in make_spin
- harden TTY detection (support non-callable isatty and bool isatty)
- change
- drop Python 2.x support
- require Python 3.10+
- other
- migrate from Travis CI to GitHub Actions
- migrate from pip/setup.py to uv

2017.04.13 v1.1.1
- fix
- fix incorrect futures requirement, thanks to @xoviat
Expand Down
4 changes: 3 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
include README.md
include README.rst
include LICENSE
include test_pyspin.py
global-exclude *.py[co]
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
test:
@py.test --pep8 -v test_pyspin.py pyspin/
@uv run pytest -v test_pyspin.py

lint:
@uv run ruff check .

create:
@python setup.py sdist bdist_wheel
@uv build

upload:
@python setup.py sdist bdist_wheel upload
@uv publish
24 changes: 18 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ make a spinner by hand:

.. code:: python

from __future__ import print_function

import sys
import time

Expand All @@ -45,8 +43,6 @@ or you can use the decorator pyspin provide:

.. code:: python

from __future__ import print_function

import time

from pyspin.spin import make_spin, Default
Expand All @@ -61,6 +57,22 @@ or you can use the decorator pyspin provide:
download_video()
print("Done!")

You can also use ``Spinner`` as a context manager:

.. code:: python

import time

from pyspin.spin import Default, Spinner

def download_video():
time.sleep(10)

if __name__ == '__main__':
print("I'm going to download a video, and it'll cost much time.")
with Spinner(Default, "Downloading...", "Done!\n"):
download_video()

You can have a look at the example code in the example folder. Run it
via:

Expand All @@ -86,8 +98,8 @@ MIT.

.. |Latest Version| image:: http://img.shields.io/pypi/v/pyspin.svg
:target: https://pypi.python.org/pypi/pyspin
.. |Build Status| image:: https://travis-ci.org/lord63/py-spin.svg
:target: https://travis-ci.org/lord63/py-spin
.. |Build Status| image:: https://github.com/lord63/py-spin/actions/workflows/ci.yml/badge.svg
:target: https://github.com/lord63/py-spin/actions/workflows/ci.yml
.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/pyspin.svg
:target: https://pypi.python.org/pypi/pyspin
.. |pyspin_demo| image:: https://cloud.githubusercontent.com/assets/5268051/7448038/ba152a8c-f241-11e4-86e0-50bc3b33bce5.gif
3 changes: 0 additions & 3 deletions dev-requirements.txt

This file was deleted.

49 changes: 25 additions & 24 deletions example/example_spin.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,43 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
# Style showcase — runs each built-in spinner style so you can see what it looks like.

from __future__ import absolute_import, print_function

import sys
import time

from pyspin import spin

STYLES = [
("Default", spin.Default),
("Box1", spin.Box1),
("Box2", spin.Box2),
("Box3", spin.Box3),
("Box4", spin.Box4),
("Box5", spin.Box5),
("Box6", spin.Box6),
("Box7", spin.Box7),
("Spin1", spin.Spin1),
("Spin2", spin.Spin2),
("Spin3", spin.Spin3),
("Spin4", spin.Spin4),
("Spin5", spin.Spin5),
("Spin6", spin.Spin6),
("Spin7", spin.Spin7),
("Spin8", spin.Spin8),
("Spin9", spin.Spin9),
Comment on lines +10 to +25
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

STYLES uses alignment spacing after commas (e.g., ("Box1", spin.Box1)), which will be flagged by ruff (E241: multiple spaces after ',') since CI runs ruff check .. Please remove the extra alignment spaces (or run an auto-formatter) so the examples pass linting.

Suggested change
("Box1", spin.Box1),
("Box2", spin.Box2),
("Box3", spin.Box3),
("Box4", spin.Box4),
("Box5", spin.Box5),
("Box6", spin.Box6),
("Box7", spin.Box7),
("Spin1", spin.Spin1),
("Spin2", spin.Spin2),
("Spin3", spin.Spin3),
("Spin4", spin.Spin4),
("Spin5", spin.Spin5),
("Spin6", spin.Spin6),
("Spin7", spin.Spin7),
("Spin8", spin.Spin8),
("Spin9", spin.Spin9),
("Box1", spin.Box1),
("Box2", spin.Box2),
("Box3", spin.Box3),
("Box4", spin.Box4),
("Box5", spin.Box5),
("Box6", spin.Box6),
("Box7", spin.Box7),
("Spin1", spin.Spin1),
("Spin2", spin.Spin2),
("Spin3", spin.Spin3),
("Spin4", spin.Spin4),
("Spin5", spin.Spin5),
("Spin6", spin.Spin6),
("Spin7", spin.Spin7),
("Spin8", spin.Spin8),
("Spin9", spin.Spin9),

Copilot uses AI. Check for mistakes.
]


def show(name, frames):
s = spin.Spinner(frames)
print(name)
for i in range(50):
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop variable i is unused here; ruff/Pyflakes will flag this as F841 (assigned to but never used) in CI. Use _ (or otherwise use i) to avoid failing lint.

Suggested change
for i in range(50):
for _ in range(50):

Copilot uses AI. Check for mistakes.
print(u"\r{0}".format(s.next()), end="")
sys.stdout.flush()
print("\r{0}".format(s.next()), end="", flush=True)
time.sleep(0.1)
print('\n')


def main():
show("Default", spin.Default)
show("Box1", spin.Box1)
show("Box2", spin.Box2)
show("Box3", spin.Box3)
show("Box4", spin.Box4)
show("Box5", spin.Box5)
show("Box6", spin.Box6)
show("Box7", spin.Box7)
show("Spin1", spin.Spin1)
show("Spin2", spin.Spin2)
show("Spin3", spin.Spin3)
show("Spin4", spin.Spin4)
show("Spin5", spin.Spin5)
show("Spin6", spin.Spin6)
show("Spin7", spin.Spin7)
show("Spin8", spin.Spin8)
show("Spin9", spin.Spin9)
for name, frames in STYLES:
show(name, frames)


if __name__ == '__main__':
Expand Down
27 changes: 16 additions & 11 deletions example/usage_example.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import absolute_import, print_function
#!/usr/bin/env python3

import time

from pyspin.spin import make_spin, Spin1
from pyspin.spin import Default, Spinner, make_spin, Spin1


@make_spin(Spin1, "Downloading...")
def demo():
# Context manager — recommended usage.
def download_video():
time.sleep(5)


if __name__ == '__main__':
print("Assume we're downloading a video")
print("It would cost much time.")
demo()
print("Download success!")
print("Downloading a video with context manager:")
with Spinner(Default, "Downloading...", "Done!\n"):
download_video()

# Decorator — alternative usage.
@make_spin(Spin1, "Downloading...")
def download_video_with_decorator():
time.sleep(5)

print("Downloading a video with decorator:")
download_video_with_decorator()
print("Done!")
34 changes: 34 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[project]
name = "pyspin"
version = "1.2.0"
description = "Little terminal spinner lib."
readme = "README.rst"
license = { text = "MIT" }
authors = [{ name = "lord63", email = "lord63.j@gmail.com" }]
requires-python = ">=3.10"
keywords = ["terminal", "spin", "spinner"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]

[project.urls]
Homepage = "https://github.com/lord63/py-spin"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[dependency-groups]
dev = ["pytest>=8.0", "ruff"]

[tool.hatch.build.targets.wheel]
packages = ["pyspin"]
2 changes: 1 addition & 1 deletion pyspin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"""

__title__ = "pyspin"
__version__ = '1.1.1'
__version__ = '1.2.0'
__author__ = "lord63"
__license__ = "MIT"
__copyright__ = "Copyright 2015 lord63"
Loading
Loading