Skip to content

Commit 70b4ebc

Browse files
support multiple albums #738 #438 (#1222)
1 parent 77d8dfa commit 70b4ebc

File tree

9 files changed

+374
-242
lines changed

9 files changed

+374
-242
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- feat: support downloading from multiple albums [#738](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/738) [#438](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/438)
6+
57
## 1.30.0 (2025-08-17)
68

79
- feat: resume interrupted downloads [#968](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/968) [#793](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/793)

docs/reference.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ This is a list of all options available for command line interface (CLI) of the
2929
(album-parameter)=
3030
`--album X`
3131

32-
: Specifies what Album to download.
32+
: Specifies what Album(s) to download.
3333

3434
When not specified, the whole asset collection is considered.
3535

36-
```{note}
37-
Only one album can be downloaded by `icloudpd`
36+
```{versionchanged} 1.31.0
37+
Option may be specified multiple times to download from different albums
3838
```
3939

4040
(list-albums-parameter)=

src/foundation/core/__init__.py

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
from typing import Callable, Tuple, TypeVar
1+
from itertools import chain, tee, zip_longest
2+
from typing import Callable, Iterable, Tuple, TypeGuard, TypeVar
23

34
_T_contra = TypeVar("_T_contra", contravariant=True)
45
_T2_contra = TypeVar("_T2_contra", contravariant=True)
56
_T3_contra = TypeVar("_T3_contra", contravariant=True)
67
_T_co = TypeVar("_T_co", covariant=True)
8+
_T2_co = TypeVar("_T2_co", covariant=True)
79
_T_inv = TypeVar("_T_inv")
10+
_T2_inv = TypeVar("_T2_inv")
811

912

1013
def compose(
@@ -201,3 +204,123 @@ def pipe2(
201204
True
202205
"""
203206
return expand2(pipe(compact2(f), g))
207+
208+
209+
def arrow(
210+
func1: Callable[[_T_contra], _T_co],
211+
func2: Callable[[_T2_contra], _T2_co],
212+
inp: Tuple[_T_contra, _T2_contra],
213+
) -> Tuple[_T_co, _T2_co]:
214+
"""applies different functions for elements of the tuple like Control.Arrow ***"""
215+
216+
return (func1(inp[0]), func2(inp[1]))
217+
218+
219+
def partial_1_1(
220+
f: Callable[[_T_contra, _T2_contra], _T_co], p1: _T_contra
221+
) -> Callable[[_T2_contra], _T_co]:
222+
"""
223+
partial application of one parameter. Safe for static type-checking
224+
225+
Equiv: functools.partial
226+
"""
227+
228+
def inter_(value: _T2_contra) -> _T_co:
229+
return f(p1, value)
230+
231+
return inter_
232+
233+
234+
def partial_2_1(
235+
f: Callable[[_T_contra, _T2_contra, _T3_contra], _T_co], p1: _T_contra, p2: _T2_contra
236+
) -> Callable[[_T3_contra], _T_co]:
237+
"""
238+
partial application of one parameter. Safe for static type-checking
239+
240+
Equiv: functools.partial
241+
"""
242+
243+
def inter_(value: _T3_contra) -> _T_co:
244+
return f(p1, p2, value)
245+
246+
return inter_
247+
248+
249+
def filter_(f: Callable[[_T_contra], bool], p1: Iterable[_T_contra]) -> Iterable[_T_contra]:
250+
"""
251+
typed filter
252+
253+
Equiv: functools.filter
254+
"""
255+
256+
return filter(f, p1)
257+
258+
259+
def filter_guarded(
260+
f: Callable[[_T_contra], TypeGuard[_T2_contra]], p1: Iterable[_T_contra]
261+
) -> Iterable[_T2_contra]:
262+
"""
263+
typed filter for guarded output
264+
265+
Equiv: functools.filter
266+
"""
267+
268+
return filter(f, p1)
269+
270+
271+
def map_(f: Callable[[_T_contra], _T_co], p1: Iterable[_T_contra]) -> Iterable[_T_co]:
272+
"""
273+
typed map
274+
275+
Equiv: functools.map
276+
"""
277+
278+
return map(f, p1)
279+
280+
281+
def tee_(inp: Iterable[_T_contra]) -> Tuple[Iterable[_T_contra], Iterable[_T_contra]]:
282+
"""
283+
duplicate iterable
284+
>>> inp = [1, 2, 3]
285+
>>> a, b = tee_(inp)
286+
>>> list(a)
287+
[1, 2, 3]
288+
>>> list(b)
289+
[1, 2, 3]
290+
"""
291+
result = tee(inp)
292+
return result[0], result[1]
293+
294+
295+
def zip_longest_(
296+
inp: Tuple[Iterable[_T_inv], Iterable[_T2_inv]],
297+
) -> Iterable[Tuple[_T_inv | None, _T2_inv | None]]:
298+
"""
299+
zip tuple of iterables into iterable of tuples
300+
>>> inp = ([1, 2], [4, 5, 6])
301+
>>> list(zip_longest_(inp))
302+
[(1, 4), (2, 5), (None, 6)]
303+
"""
304+
return zip_longest(inp[0], inp[1])
305+
306+
307+
def unzip(
308+
inp: Iterable[Tuple[_T_inv, _T2_inv]],
309+
) -> Tuple[Iterable[_T_inv], Iterable[_T2_inv]]:
310+
"""
311+
unzip iterable of tuples
312+
>>> a, b = unzip([(1, 2), (3, 4), (5, 6)])
313+
>>> list(a)
314+
[1, 3, 5]
315+
>>> list(b)
316+
[2, 4, 6]
317+
"""
318+
fst_i: Callable[[Iterable[Tuple[_T_inv, _T2_inv]]], Iterable[_T_inv]] = partial_1_1(map_, fst)
319+
snd_i: Callable[[Iterable[Tuple[_T_inv, _T2_inv]]], Iterable[_T2_inv]] = partial_1_1(map_, snd)
320+
split = partial_2_1(arrow, fst_i, snd_i)
321+
func = compose(split, tee_)
322+
return func(inp)
323+
324+
325+
def chain_from_iterable(inp: Iterable[Iterable[_T_inv]]) -> Iterable[_T_inv]:
326+
return chain.from_iterable(inp)

0 commit comments

Comments
 (0)