fotolab 0.31.4__tar.gz → 0.31.7__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.
Files changed (55) hide show
  1. fotolab-0.31.7/.pre-commit-config.yaml +51 -0
  2. {fotolab-0.31.4 → fotolab-0.31.7}/CHANGELOG.md +20 -0
  3. {fotolab-0.31.4 → fotolab-0.31.7}/PKG-INFO +3 -9
  4. {fotolab-0.31.4 → fotolab-0.31.7}/README.md +2 -8
  5. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/subcommands/__init__.py +2 -1
  6. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/subcommands/animate.py +3 -1
  7. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/subcommands/border.py +6 -2
  8. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/subcommands/contrast.py +9 -3
  9. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/subcommands/halftone.py +25 -19
  10. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/subcommands/info.py +3 -1
  11. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/subcommands/montage.py +3 -1
  12. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/subcommands/resize.py +3 -1
  13. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/subcommands/watermark.py +15 -5
  14. {fotolab-0.31.4 → fotolab-0.31.7}/noxfile.py +14 -4
  15. {fotolab-0.31.4 → fotolab-0.31.7}/pyproject.toml +1 -5
  16. {fotolab-0.31.4 → fotolab-0.31.7}/uv.lock +219 -324
  17. fotolab-0.31.4/.pre-commit-config.yaml +0 -120
  18. {fotolab-0.31.4 → fotolab-0.31.7}/.coveragerc +0 -0
  19. {fotolab-0.31.4 → fotolab-0.31.7}/.gitignore +0 -0
  20. {fotolab-0.31.4 → fotolab-0.31.7}/.python-version +0 -0
  21. {fotolab-0.31.4 → fotolab-0.31.7}/CONTRIBUTING.md +0 -0
  22. {fotolab-0.31.4 → fotolab-0.31.7}/LICENSE.md +0 -0
  23. {fotolab-0.31.4 → fotolab-0.31.7}/docs/Makefile +0 -0
  24. {fotolab-0.31.4 → fotolab-0.31.7}/docs/make.bat +0 -0
  25. {fotolab-0.31.4 → fotolab-0.31.7}/docs/source/CHANGELOG.md +0 -0
  26. {fotolab-0.31.4 → fotolab-0.31.7}/docs/source/CONTRIBUTING.md +0 -0
  27. {fotolab-0.31.4 → fotolab-0.31.7}/docs/source/LICENSE.md +0 -0
  28. {fotolab-0.31.4 → fotolab-0.31.7}/docs/source/README.md +0 -0
  29. {fotolab-0.31.4 → fotolab-0.31.7}/docs/source/_static/logo.jpg +0 -0
  30. {fotolab-0.31.4 → fotolab-0.31.7}/docs/source/conf.py +0 -0
  31. {fotolab-0.31.4 → fotolab-0.31.7}/docs/source/index.rst +0 -0
  32. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/__init__.py +0 -0
  33. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/__main__.py +0 -0
  34. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/cli.py +0 -0
  35. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/subcommands/auto.py +0 -0
  36. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/subcommands/env.py +0 -0
  37. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/subcommands/rotate.py +0 -0
  38. {fotolab-0.31.4 → fotolab-0.31.7}/fotolab/subcommands/sharpen.py +0 -0
  39. {fotolab-0.31.4 → fotolab-0.31.7}/generate +0 -0
  40. {fotolab-0.31.4 → fotolab-0.31.7}/tests/__init__.py +0 -0
  41. {fotolab-0.31.4 → fotolab-0.31.7}/tests/conftest.py +0 -0
  42. {fotolab-0.31.4 → fotolab-0.31.7}/tests/test_animate_subcommand.py +0 -0
  43. {fotolab-0.31.4 → fotolab-0.31.7}/tests/test_auto_subcommand.py +0 -0
  44. {fotolab-0.31.4 → fotolab-0.31.7}/tests/test_border_subcommand.py +0 -0
  45. {fotolab-0.31.4 → fotolab-0.31.7}/tests/test_contrast_subcommand.py +0 -0
  46. {fotolab-0.31.4 → fotolab-0.31.7}/tests/test_env_subcommand.py +0 -0
  47. {fotolab-0.31.4 → fotolab-0.31.7}/tests/test_halftone_subcommand.py +0 -0
  48. {fotolab-0.31.4 → fotolab-0.31.7}/tests/test_help_flag.py +0 -0
  49. {fotolab-0.31.4 → fotolab-0.31.7}/tests/test_info_subcommand.py +0 -0
  50. {fotolab-0.31.4 → fotolab-0.31.7}/tests/test_montage_subcommand.py +0 -0
  51. {fotolab-0.31.4 → fotolab-0.31.7}/tests/test_quiet_flag.py +0 -0
  52. {fotolab-0.31.4 → fotolab-0.31.7}/tests/test_resize_subcommand.py +0 -0
  53. {fotolab-0.31.4 → fotolab-0.31.7}/tests/test_rotate_subcommand.py +0 -0
  54. {fotolab-0.31.4 → fotolab-0.31.7}/tests/test_sharpen_subcommand.py +0 -0
  55. {fotolab-0.31.4 → fotolab-0.31.7}/tests/test_watermark_subcommand.py +0 -0
@@ -0,0 +1,51 @@
1
+ # See https://pre-commit.com for more information
2
+ # See https://pre-commit.com/hooks.html for more hooks
3
+ repos:
4
+ - repo: https://github.com/pre-commit/pre-commit-hooks
5
+ rev: v5.0.0
6
+ hooks:
7
+ - id: check-case-conflict
8
+ - id: check-merge-conflict
9
+ - id: check-toml
10
+ - id: check-yaml
11
+ - id: debug-statements
12
+ - id: detect-private-key
13
+ - id: end-of-file-fixer
14
+ - id: mixed-line-ending
15
+ - id: trailing-whitespace
16
+
17
+ - repo: https://github.com/abravalheri/validate-pyproject
18
+ rev: v0.24.1
19
+ hooks:
20
+ - id: validate-pyproject
21
+ name: validate-pyproject
22
+
23
+ - repo: https://github.com/codespell-project/codespell
24
+ rev: v2.4.1
25
+ hooks:
26
+ - id: codespell
27
+ args:
28
+ - --ignore-words-list=astroid
29
+
30
+ - repo: https://github.com/pre-commit/mirrors-prettier
31
+ rev: v4.0.0-alpha.8
32
+ hooks:
33
+ - id: prettier
34
+ exclude: (Pipfile.lock)
35
+
36
+ - repo: https://github.com/astral-sh/ruff-pre-commit
37
+ rev: v0.12.2
38
+ hooks:
39
+ - id: ruff-check
40
+ args:
41
+ - --fix
42
+ - --exit-non-zero-on-fix
43
+ - id: ruff-format
44
+ args:
45
+ - --line-length=79
46
+
47
+ - repo: https://github.com/pre-commit/mirrors-mypy
48
+ rev: v1.16.1
49
+ hooks:
50
+ - id: mypy
51
+ exclude: docs/
@@ -7,6 +7,26 @@ and this project adheres to [0-based versioning](https://0ver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## v0.31.7 (2025-07-06)
11
+
12
+ - Bump deps and `pre-commit` for `ruff`
13
+ - Enable `ruff format` and code format
14
+ - Resync to update the bumped version to `uv.lock`
15
+
16
+ ## v0.31.6 (2025-06-29)
17
+
18
+ - Bump deps and `pre-commit` hook for `ruff`
19
+ - Refine halftone dot color logic and type annotations
20
+ - Remove unused `flake8` related deps
21
+ - Update example in comment
22
+ - Update installation steps using `uv` tool
23
+
24
+ ## v0.31.5 (2025-06-22)
25
+
26
+ - Add missing example for `nox` release job
27
+ - Bump deps and `pre-commit` hooks
28
+ - Remove unused linting `pre-commit` hooks
29
+
10
30
  ## v0.31.4 (2025-06-15)
11
31
 
12
32
  - Fix `deps` job in `nox` session due to migration to `uv`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fotolab
3
- Version: 0.31.4
3
+ Version: 0.31.7
4
4
  Summary: A console program that manipulate images.
5
5
  Keywords: photography,photo
6
6
  Author-email: Kian-Meng Ang <kianmeng@cpan.org>
@@ -31,19 +31,13 @@ A console program to manipulate photos.
31
31
  Stable version From PyPI using `uv`:
32
32
 
33
33
  ```console
34
- uv pip install fotolab
34
+ uv tool install fotolab
35
35
  ```
36
36
 
37
37
  Upgrade to latest stable version:
38
38
 
39
39
  ```console
40
- uv pip install fotolab --upgrade
41
- ```
42
-
43
- Latest development version from GitHub:
44
-
45
- ```console
46
- uv pip install -e git+https://github.com/kianmeng/fotolab.git
40
+ uv tool upgrade fotolab
47
41
  ```
48
42
 
49
43
  ## Usage
@@ -7,19 +7,13 @@ A console program to manipulate photos.
7
7
  Stable version From PyPI using `uv`:
8
8
 
9
9
  ```console
10
- uv pip install fotolab
10
+ uv tool install fotolab
11
11
  ```
12
12
 
13
13
  Upgrade to latest stable version:
14
14
 
15
15
  ```console
16
- uv pip install fotolab --upgrade
17
- ```
18
-
19
- Latest development version from GitHub:
20
-
21
- ```console
22
- uv pip install -e git+https://github.com/kianmeng/fotolab.git
16
+ uv tool upgrade fotolab
23
17
  ```
24
18
 
25
19
  ## Usage
@@ -24,7 +24,8 @@ def build_subparser(subparsers):
24
24
  iter_namespace = pkgutil.iter_modules(__path__, __name__ + ".")
25
25
 
26
26
  subcommands = {
27
- name: importlib.import_module(name) for finder, name, ispkg in iter_namespace
27
+ name: importlib.import_module(name)
28
+ for finder, name, ispkg in iter_namespace
28
29
  }
29
30
 
30
31
  for subcommand in subcommands.values():
@@ -123,7 +123,9 @@ def build_subparser(subparsers) -> None:
123
123
  type=int,
124
124
  default=4,
125
125
  choices=range(0, 7),
126
- help=("set WEBP encoding method (0=fast, 6=slow/best, default: '%(default)s')"),
126
+ help=(
127
+ "set WEBP encoding method (0=fast, 6=slow/best, default: '%(default)s')"
128
+ ),
127
129
  metavar="METHOD",
128
130
  )
129
131
 
@@ -77,7 +77,9 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
77
77
  dest="width_right",
78
78
  type=int,
79
79
  default=0,
80
- help=("set the width of right border in pixels (default: '%(default)s')"),
80
+ help=(
81
+ "set the width of right border in pixels (default: '%(default)s')"
82
+ ),
81
83
  metavar="WIDTH",
82
84
  )
83
85
 
@@ -87,7 +89,9 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
87
89
  dest="width_bottom",
88
90
  type=int,
89
91
  default=0,
90
- help=("set the width of bottom border in pixels (default: '%(default)s')"),
92
+ help=(
93
+ "set the width of bottom border in pixels (default: '%(default)s')"
94
+ ),
91
95
  metavar="WIDTH",
92
96
  )
93
97
 
@@ -31,7 +31,9 @@ def _validate_cutoff(value: str) -> float:
31
31
  try:
32
32
  f_value = float(value)
33
33
  except ValueError as e:
34
- raise argparse.ArgumentTypeError(f"invalid float value: '{value}'") from e
34
+ raise argparse.ArgumentTypeError(
35
+ f"invalid float value: '{value}'"
36
+ ) from e
35
37
  if not 0 <= f_value <= 50:
36
38
  raise argparse.ArgumentTypeError(
37
39
  f"cutoff value {f_value} must be between 0 and 50"
@@ -41,7 +43,9 @@ def _validate_cutoff(value: str) -> float:
41
43
 
42
44
  def build_subparser(subparsers: argparse._SubParsersAction) -> None:
43
45
  """Build the subparser."""
44
- contrast_parser = subparsers.add_parser("contrast", help="contrast an image.")
46
+ contrast_parser = subparsers.add_parser(
47
+ "contrast", help="contrast an image."
48
+ )
45
49
 
46
50
  contrast_parser.set_defaults(func=run)
47
51
 
@@ -99,6 +103,8 @@ def run(args: argparse.Namespace) -> None:
99
103
 
100
104
  for image_filename in args.image_filenames:
101
105
  original_image = Image.open(image_filename)
102
- contrast_image = ImageOps.autocontrast(original_image, cutoff=args.cutoff)
106
+ contrast_image = ImageOps.autocontrast(
107
+ original_image, cutoff=args.cutoff
108
+ )
103
109
 
104
110
  save_image(args, contrast_image, image_filename, "contrast")
@@ -18,7 +18,7 @@
18
18
  import argparse
19
19
  import logging
20
20
  import math
21
- from typing import NamedTuple
21
+ from typing import NamedTuple, Union
22
22
 
23
23
  from PIL import Image, ImageDraw
24
24
 
@@ -37,7 +37,9 @@ class HalftoneCell(NamedTuple):
37
37
 
38
38
  def build_subparser(subparsers) -> None:
39
39
  """Build the subparser."""
40
- halftone_parser = subparsers.add_parser("halftone", help="halftone an image")
40
+ halftone_parser = subparsers.add_parser(
41
+ "halftone", help="halftone an image"
42
+ )
41
43
 
42
44
  halftone_parser.set_defaults(func=run)
43
45
 
@@ -129,22 +131,22 @@ def _draw_halftone_dot(
129
131
  source_image: Image.Image,
130
132
  cell: HalftoneCell,
131
133
  grayscale: bool,
132
- fill_color_dot,
134
+ fill_color_dot: int,
133
135
  ) -> None:
134
136
  """Calculate properties and draw a single halftone dot."""
135
- # Calculate center point of current cell
137
+ # calculate center point of current cell
136
138
  img_width, img_height = source_image.size
137
139
 
138
- # Calculate center point of current cell and clamp to image bounds
140
+ # calculate center point of current cell and clamp to image bounds
139
141
  x = min(int(cell.col * cell.cellsize + cell.cellsize / 2), img_width - 1)
140
142
  y = min(int(cell.row * cell.cellsize + cell.cellsize / 2), img_height - 1)
141
143
 
142
- # Ensure coordinates are non-negative (shouldn't happen with current logic,
144
+ # ensure coordinates are non-negative (shouldn't happen with current logic,
143
145
  # but safe)
144
146
  x = max(0, x)
145
147
  y = max(0, y)
146
148
 
147
- # Get pixel value (brightness or color) from the source image using clamped
149
+ # get pixel value (brightness or color) from the source image using clamped
148
150
  # coordinates
149
151
  pixel_value = source_image.getpixel((x, y))
150
152
 
@@ -152,17 +154,20 @@ def _draw_halftone_dot(
152
154
  brightness = pixel_value
153
155
  dot_fill = fill_color_dot # Use white for grayscale dots
154
156
  else:
155
- # Calculate brightness (luminance) from the RGB color
157
+ # calculate brightness (luminance) from the RGB color
156
158
  brightness = int(
157
- 0.299 * pixel_value[0] + 0.587 * pixel_value[1] + 0.114 * pixel_value[2]
159
+ 0.299 * pixel_value[0]
160
+ + 0.587 * pixel_value[1]
161
+ + 0.114 * pixel_value[2]
158
162
  )
159
163
  dot_fill = pixel_value # Use original color for color dots
160
164
 
161
- # Calculate dot radius relative to cell size based on brightness Max radius
162
- # is half the cell size. Scale by brightness (0-255).
165
+ # calculate dot radius relative to cell size based on brightness max radius
166
+ # is half the cell size
167
+ # scale by brightness (0-255).
163
168
  dot_radius = (brightness / 255.0) * (cell.cellsize / 2)
164
169
 
165
- # Draw the dot
170
+ # draw the dot
166
171
  draw.ellipse(
167
172
  [
168
173
  x - dot_radius,
@@ -190,29 +195,30 @@ def create_halftone_image(
190
195
  Returns:
191
196
  Image.Image: The halftone converted image
192
197
  """
198
+ output_mode: str
199
+ fill_color_black: Union[int, tuple[int, int, int]]
200
+ fill_color_dot_for_grayscale: int
201
+
193
202
  if grayscale:
194
203
  source_image = original_image.convert("L")
195
204
  output_mode = "L"
196
205
  fill_color_black = 0
197
- fill_color_dot = 255
206
+ fill_color_dot_for_grayscale = 255
198
207
  else:
199
208
  source_image = original_image.convert("RGB")
200
209
  output_mode = "RGB"
201
210
  fill_color_black = (0, 0, 0)
202
- fill_color_dot = None # Will be set inside the loop for color images
211
+ fill_color_dot_for_grayscale = 0
203
212
 
204
213
  width, height = original_image.size
205
214
 
206
- # Create a new image for the output, initialized to black
215
+ # create a new image for the output, initialized to black
207
216
  halftone_image = Image.new(output_mode, (width, height), fill_color_black)
208
217
  draw = ImageDraw.Draw(halftone_image)
209
218
 
210
219
  cellsize = width / cell_count
211
220
  rowtotal = math.ceil(height / cellsize)
212
221
 
213
- # Determine the fill color for dots once if grayscale
214
- grayscale_fill_color_dot = fill_color_dot if grayscale else None
215
-
216
222
  for row in range(rowtotal):
217
223
  for col in range(cell_count):
218
224
  cell = HalftoneCell(col=col, row=row, cellsize=cellsize)
@@ -221,7 +227,7 @@ def create_halftone_image(
221
227
  source_image,
222
228
  cell,
223
229
  grayscale,
224
- grayscale_fill_color_dot,
230
+ fill_color_dot_for_grayscale,
225
231
  )
226
232
 
227
233
  return halftone_image
@@ -107,7 +107,9 @@ def extract_exif_tags(image: Image.Image, sort: bool = False) -> dict:
107
107
  if exif:
108
108
  info = {ExifTags.TAGS.get(tag_id): exif.get(tag_id) for tag_id in exif}
109
109
 
110
- filtered_info = {key: value for key, value in info.items() if key is not None}
110
+ filtered_info = {
111
+ key: value for key, value in info.items() if key is not None
112
+ }
111
113
  if sort:
112
114
  filtered_info = dict(sorted(filtered_info.items()))
113
115
 
@@ -27,7 +27,9 @@ log = logging.getLogger(__name__)
27
27
 
28
28
  def build_subparser(subparsers) -> None:
29
29
  """Build the subparser."""
30
- montage_parser = subparsers.add_parser("montage", help="montage a list of image")
30
+ montage_parser = subparsers.add_parser(
31
+ "montage", help="montage a list of image"
32
+ )
31
33
 
32
34
  montage_parser.set_defaults(func=run)
33
35
 
@@ -59,7 +59,9 @@ def build_subparser(subparsers) -> None:
59
59
  "--canvas-color",
60
60
  default="black",
61
61
  dest="canvas_color",
62
- help=("the color of the extended larger canvas(default: '%(default)s')"),
62
+ help=(
63
+ "the color of the extended larger canvas(default: '%(default)s')"
64
+ ),
63
65
  )
64
66
 
65
67
  if "-c" in sys.argv or "--canvas" in sys.argv:
@@ -109,7 +109,9 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
109
109
  dest="outline_width",
110
110
  type=int,
111
111
  default=2,
112
- help=("set the outline width of the watermark text (default: '%(default)s')"),
112
+ help=(
113
+ "set the outline width of the watermark text (default: '%(default)s')"
114
+ ),
113
115
  metavar="OUTLINE_WIDTH",
114
116
  )
115
117
 
@@ -119,7 +121,9 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
119
121
  dest="outline_color",
120
122
  type=str,
121
123
  default="black",
122
- help=("set the outline color of the watermark text (default: '%(default)s')"),
124
+ help=(
125
+ "set the outline color of the watermark text (default: '%(default)s')"
126
+ ),
123
127
  metavar="OUTLINE_COLOR",
124
128
  )
125
129
 
@@ -197,7 +201,9 @@ def run(args: argparse.Namespace) -> None:
197
201
  if image.format == "GIF":
198
202
  watermark_gif_image(image, image_filename, args)
199
203
  else:
200
- watermarked_image: Image.Image = watermark_non_gif_image(image, args)
204
+ watermarked_image: Image.Image = watermark_non_gif_image(
205
+ image, args
206
+ )
201
207
  save_image(args, watermarked_image, image_filename, "watermark")
202
208
 
203
209
 
@@ -333,12 +339,16 @@ def calc_font_size(image: Image.Image, args: argparse.Namespace) -> int:
333
339
  return new_font_size
334
340
 
335
341
 
336
- def calc_font_outline_width(image: Image.Image, args: argparse.Namespace) -> int:
342
+ def calc_font_outline_width(
343
+ image: Image.Image, args: argparse.Namespace
344
+ ) -> int:
337
345
  """Calculate the font padding based on the width of the image."""
338
346
  width, _height = image.size
339
347
  new_font_outline_width: int = args.outline_width
340
348
  if width > 600:
341
- new_font_outline_width = math.floor(FONT_OUTLINE_WIDTH_ASPECT_RATIO * width)
349
+ new_font_outline_width = math.floor(
350
+ FONT_OUTLINE_WIDTH_ASPECT_RATIO * width
351
+ )
342
352
 
343
353
  log.debug("new font outline width: %d", new_font_outline_width)
344
354
  return new_font_outline_width
@@ -36,7 +36,7 @@ def lint(session: nox.Session) -> None:
36
36
 
37
37
  For running selected task within pre-commit:
38
38
 
39
- nox -s lint -- pylint
39
+ nox -s lint -- ruff
40
40
  """
41
41
  _uv_install(session)
42
42
  session.run("pre-commit", "run", "--all-files", *session.posargs)
@@ -46,7 +46,9 @@ def lint(session: nox.Session) -> None:
46
46
  def test(session: nox.Session) -> None:
47
47
  """Run test."""
48
48
  _uv_install(session)
49
- session.run("uv", "run", "pytest", "--numprocesses", "auto", *session.posargs)
49
+ session.run(
50
+ "uv", "run", "pytest", "--numprocesses", "auto", *session.posargs
51
+ )
50
52
 
51
53
 
52
54
  @nox.session(python="3.13")
@@ -70,7 +72,9 @@ def cov(session: nox.Session) -> None:
70
72
  def doc(session: nox.Session) -> None:
71
73
  """Build doc with sphinx."""
72
74
  _uv_install(session)
73
- session.run("sphinx-build", "docs/source/", "docs/build/html", *session.posargs)
75
+ session.run(
76
+ "sphinx-build", "docs/source/", "docs/build/html", *session.posargs
77
+ )
74
78
 
75
79
 
76
80
  @nox.session(python="3.13")
@@ -120,6 +124,7 @@ def release(session: nox.Session) -> None:
120
124
  nox -s release -- major
121
125
  nox -s release -- minor
122
126
  nox -s release -- micro (default)
127
+ nos -s release -- -h
123
128
  """
124
129
  _uv_install(session)
125
130
 
@@ -135,13 +140,18 @@ def release(session: nox.Session) -> None:
135
140
  args = parser.parse_args(args=session.posargs)
136
141
 
137
142
  session.run("uv", "version", "--bump", args.semver)
138
- after_version = session.run("uv", "version", "--short", silent=True).strip()
143
+ after_version = session.run(
144
+ "uv", "version", "--short", silent=True
145
+ ).strip()
139
146
 
140
147
  date = datetime.date.today().strftime("%Y-%m-%d")
141
148
  before_header = "## [Unreleased]\n\n"
142
149
  after_header = f"## [Unreleased]\n\n## v{after_version} ({date})\n\n"
143
150
  _search_and_replace("CHANGELOG.md", before_header, after_header)
144
151
 
152
+ # resync to update the bumped version to uv.lock
153
+ _uv_install(session)
154
+
145
155
  session.run(
146
156
  "git",
147
157
  "commit",
@@ -20,7 +20,7 @@ classifiers = [
20
20
  "Programming Language :: Python :: 3.13",
21
21
  "Programming Language :: Python",
22
22
  ]
23
- version = "0.31.4"
23
+ version = "0.31.7"
24
24
  dynamic = ["description"]
25
25
  keywords = ["photography", "photo"]
26
26
  dependencies = [
@@ -38,10 +38,6 @@ fotolab = "fotolab.cli:main"
38
38
  [dependency-groups]
39
39
  dev = [
40
40
  "exceptiongroup",
41
- "flake8-docstrings",
42
- "flake8-print",
43
- "flake8-pytest-style",
44
- "flake8-simplify",
45
41
  "flit",
46
42
  "importlib-metadata",
47
43
  "myst-parser",