Skip to content

dependence.update

_Version dataclass

Instances of this class can be be passed as self in a call to packaging.version.Version.__str__, and thereby can facilitate operations to mimic mutability for the aforementioned class.

Source code in src/dependence/update.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@dataclass
class _Version:
    """
    Instances of this class can be be passed as `self` in a call
    to `packaging.version.Version.__str__`, and thereby can facilitate
    operations to mimic mutability for the aforementioned class.
    """

    epoch: int
    release: tuple[int, ...]
    pre: Any
    post: Any
    dev: Any
    local: Any

_update_requirement_specifiers

_update_requirement_specifiers(
    requirement: packaging.requirements.Requirement,
    installed_version_string: str,
) -> None

This function updates specifier version numbers for a requirement to match the installed version of the package

Source code in src/dependence/update.py
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def _update_requirement_specifiers(
    requirement: Requirement, installed_version_string: str
) -> None:
    """
    This function updates specifier version numbers for a requirement
    to match the installed version of the package
    """
    installed_version: Version = parse_version(installed_version_string)
    specifier: Specifier
    updated_specifier_strings: list[str] = []
    for specifier in requirement.specifier:  # type: ignore
        # Only update requirement to match our installed version
        # if the requirement is *inclusive*
        if ("=" in specifier.operator) and ("!" not in specifier.operator):
            specifier_version: Version = parse_version(specifier.version)
            if installed_version.release is None:
                raise ValueError(installed_version)
            if specifier_version.release is None:
                updated_specifier_strings.append(f"{specifier.operator}")
            else:
                greater_or_equal_specificity: bool = len(
                    specifier_version.release
                ) >= len(installed_version.release)
                specifier_version_data: _Version = _Version(
                    epoch=installed_version.epoch,
                    # Truncate the updated version requirement at the same
                    # level of specificity as the old
                    release=installed_version.release[
                        : len(specifier_version.release)
                    ],
                    pre=(
                        installed_version.pre
                        if greater_or_equal_specificity
                        else None
                    ),
                    post=(
                        installed_version.post
                        if greater_or_equal_specificity
                        else None
                    ),
                    dev=(
                        installed_version.dev
                        if greater_or_equal_specificity
                        else None
                    ),
                    local=(
                        installed_version.local
                        if greater_or_equal_specificity
                        else None
                    ),
                )
                version_string: str = Version.__str__(
                    specifier_version_data  # type: ignore
                )
                updated_specifier_strings.append(
                    f"{specifier.operator}{version_string}"
                )
        else:
            updated_specifier_strings.append(str(specifier))
    requirement.specifier = SpecifierSet(",".join(updated_specifier_strings))

_get_updated_requirement_string

_get_updated_requirement_string(
    requirement_string: str, ignore: set[str]
) -> str

This function updates version numbers in a requirement string to match those installed in the current environment

Source code in src/dependence/update.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def _get_updated_requirement_string(
    requirement_string: str, ignore: set[str]
) -> str:
    """
    This function updates version numbers in a requirement string to match
    those installed in the current environment
    """
    # Skip empty requirement strings
    if not is_requirement_string(requirement_string):
        return requirement_string
    requirement: Requirement = Requirement(requirement_string)
    name: str = normalize_name(requirement.name)
    if name in ignore:
        return requirement_string
    try:
        distribution: Distribution = get_installed_distributions()[name]
        _update_requirement_specifiers(requirement, distribution.version)
    except KeyError:
        # If the requirement isn't installed, we can't update the version
        pass
    return str(requirement)

_get_updated_requirements_txt

_get_updated_requirements_txt(
    data: str, ignore: collections.abc.Iterable[str] = ()
) -> str

Return the contents of a requirements.txt file, updated to reflect the currently installed project versions, excluding those specified in ignore.

Parameters:

  • data (str): The contents of a requirements.txt file
  • ignore ([str]): One or more project names to leave as-is
Source code in src/dependence/update.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
def _get_updated_requirements_txt(
    data: str, ignore: Iterable[str] = ()
) -> str:
    """
    Return the contents of a *requirements.txt* file, updated to reflect the
    currently installed project versions, excluding those specified in
    `ignore`.

    Parameters:

    - data (str): The contents of a *requirements.txt* file
    - ignore ([str]): One or more project names to leave as-is
    """
    ignore_set: set[str] = _normalize_ignore_argument(ignore)

    def get_updated_requirement_string(requirement: str) -> str:
        return _get_updated_requirement_string(requirement, ignore=ignore_set)

    return "\n".join(map(get_updated_requirement_string, data.split("\n")))

_get_updated_setup_cfg

_get_updated_setup_cfg(
    data: str,
    ignore: collections.abc.Iterable[str] = (),
    all_extra_name: str = "",
) -> str

Return the contents of a setup.cfg file, updated to reflect the currently installed project versions, excluding those specified in ignore.

Parameters:

  • data (str): The contents of a setup.cfg file
  • ignore ([str]): One or more project names to leave as-is
  • all_extra_name (str): An (optional) extra name which will consolidate requirements from all other extras
Source code in src/dependence/update.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def _get_updated_setup_cfg(
    data: str, ignore: Iterable[str] = (), all_extra_name: str = ""
) -> str:
    """
    Return the contents of a *setup.cfg* file, updated to reflect the
    currently installed project versions, excluding those specified in
    `ignore`.

    Parameters:

    - data (str): The contents of a *setup.cfg* file
    - ignore ([str]): One or more project names to leave as-is
    - all_extra_name (str): An (optional) extra name which will
      consolidate requirements from all other extras
    """
    ignore_set: set[str] = _normalize_ignore_argument(ignore)

    def get_updated_requirement_string(requirement: str) -> str:
        return _get_updated_requirement_string(requirement, ignore=ignore_set)

    # Parse
    parser: ConfigParser = ConfigParser()
    parser.read_string(data)
    # Update
    if ("options" in parser) and ("install_requires" in parser["options"]):
        parser["options"]["install_requires"] = "\n".join(
            map(  # type: ignore
                get_updated_requirement_string,
                parser["options"]["install_requires"].split("\n"),
            )
        )
    if "options.extras_require" in parser:
        extras_require: SectionProxy = parser["options.extras_require"]
        all_extra_requirements: list[str] = []
        extra_name: str
        extra_requirements_string: str
        extra_requirements: list[str]
        for extra_name, extra_requirements_string in extras_require.items():
            if extra_name != all_extra_name:
                extra_requirements = list(
                    map(
                        get_updated_requirement_string,
                        extra_requirements_string.split("\n"),
                    )
                )
                if all_extra_name:
                    all_extra_requirements += extra_requirements
                extras_require[extra_name] = "\n".join(extra_requirements)
        # If a name was specified for an all-encompasing extra,
        # we de-duplicate and update or create that extra
        if all_extra_name:
            # We pre-pend an empty requirement string in order to]
            # force new-line creation at the beginning of the extra
            extras_require[all_extra_name] = "\n".join(
                iter_distinct(["", *all_extra_requirements])
            )
    # Return as a string
    setup_cfg: str
    setup_cfg_io: IO[str]
    with StringIO() as setup_cfg_io:
        parser.write(setup_cfg_io)
        setup_cfg_io.seek(0)
        setup_cfg = re.sub(r"[ ]+(\n|$)", r"\1", setup_cfg_io.read()).strip()
        return f"{setup_cfg}\n"

_get_updated_tox_ini

_get_updated_tox_ini(
    data: str, ignore: collections.abc.Iterable[str] = ()
) -> str

Return the contents of a tox.ini file, updated to reflect the currently installed project versions, excluding those specified in ignore.

Parameters:

  • data (str): The contents of a tox.ini file
  • ignore ([str]): One or more project names to leave as-is
Source code in src/dependence/update.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
def _get_updated_tox_ini(data: str, ignore: Iterable[str] = ()) -> str:
    """
    Return the contents of a **tox.ini** file, updated to reflect the
    currently installed project versions, excluding those specified in
    `ignore`.

    Parameters:

    - data (str): The contents of a **tox.ini** file
    - ignore ([str]): One or more project names to leave as-is
    """
    ignore_set: set[str] = _normalize_ignore_argument(ignore)

    def get_updated_requirement_string(requirement: str) -> str:
        prefix: str | None = None
        if ":" in requirement:
            prefix, requirement = requirement.split(":", maxsplit=1)
        requirement = _get_updated_requirement_string(
            requirement, ignore=ignore_set
        )
        if prefix is not None:
            requirement = f"{prefix}: {requirement.lstrip()}"
        return requirement

    # Parse
    parser: ConfigParser = ConfigParser()
    parser.read_string(data)

    def update_section_options(section_name: str, option_name: str) -> None:
        if parser.has_option(section_name, option_name):
            parser.set(
                section_name,
                option_name,
                "\n".join(
                    map(
                        get_updated_requirement_string,
                        parser.get(section_name, option_name).split("\n"),
                    )
                ),
            )

    def update_section(section_name: str) -> None:
        update_section_options(section_name, "deps")
        if section_name == "tox":
            update_section_options(section_name, "requires")

    # Update
    list(map(update_section, parser.sections()))
    # Return as a string
    tox_ini: str
    tox_ini_io: IO[str]
    with StringIO() as tox_ini_io:
        parser.write(tox_ini_io)
        tox_ini_io.seek(0)
        tox_ini = re.sub(r"[ ]+(\n|$)", r"\1", tox_ini_io.read()).strip()
        return f"{tox_ini}\n"

_get_updated_pyproject_toml

_get_updated_pyproject_toml(
    data: str,
    ignore: collections.abc.Iterable[str] = (),
    all_extra_name: str = "",
    include_pointers: tuple[str, ...] = (),
    exclude_pointers: tuple[str, ...] = (),
) -> str

Return the contents of a pyproject.toml file, updated to reflect the currently installed project versions, excluding those specified in ignore.

Parameters:

  • data (str) –

    The contents of a pyproject.toml file

  • ignore (collections.abc.Iterable[str], default: () ) –

    One or more project names to leave as-is

  • all_extra_name (str, default: '' ) –

    An (optional) extra name which will consolidate requirements from all other extras

  • include_pointers (tuple[str, ...], default: () ) –

    A tuple of JSON pointers indicating elements to include (defaults to all elements).

  • exclude_pointers (tuple[str, ...], default: () ) –

    A tuple of JSON pointers indicating elements to exclude (defaults to no exclusions).

Returns:

  • str

    The contents of the updated pyproject.toml file.

Source code in src/dependence/update.py
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
def _get_updated_pyproject_toml(
    data: str,
    ignore: Iterable[str] = (),
    all_extra_name: str = "",
    include_pointers: tuple[str, ...] = (),
    exclude_pointers: tuple[str, ...] = (),
) -> str:
    """
    Return the contents of a *pyproject.toml* file, updated to reflect the
    currently installed project versions, excluding those specified in
    `ignore`.

    Parameters:
        data: The contents of a *pyproject.toml* file
        ignore: One or more project names to leave as-is
        all_extra_name: An (optional) extra name which will
            consolidate requirements from all other extras
        include_pointers: A tuple of JSON pointers indicating elements to
            include (defaults to all elements).
        exclude_pointers: A tuple of JSON pointers indicating elements to
            exclude (defaults to no exclusions).

    Returns:
        The contents of the updated pyproject.toml file.
    """
    # Parse pyproject.toml
    original_pyproject: dict[str, Any] = tomli.loads(data)
    updated_pyproject: dict[str, Any] = deepcopy(original_pyproject)
    # Find and update requirements
    _update_document_requirements(
        updated_pyproject,
        ignore=ignore,
        include_pointers=include_pointers,
        exclude_pointers=exclude_pointers,
    )
    # Update consolidated optional requirements
    project_optional_dependencies: dict[str, list[str]] = (
        updated_pyproject.get("project", {}).get("optional-dependencies", {})
    )
    # Update an extra indicated to encompass all other extras
    if project_optional_dependencies and all_extra_name:
        key: str
        dependencies: list[str]
        project_optional_dependencies[all_extra_name] = list(
            iter_distinct(
                chain(
                    *(
                        dependencies
                        for key, dependencies in (
                            project_optional_dependencies.items()
                        )
                        if key != all_extra_name
                    )
                )
            )
        )
    # Only dump the data if something was updated
    if original_pyproject != updated_pyproject:
        return tomli_w.dumps(updated_pyproject)
    return data

_get_updated_toml

_get_updated_toml(
    data: str,
    ignore: collections.abc.Iterable[str] = (),
    include_pointers: tuple[str, ...] = (),
    exclude_pointers: tuple[str, ...] = (),
) -> str

Return the contents of a TOML file, updated to reflect the currently installed project versions, excluding those specified in ignore.

Note: This functions identically to get_updated_pyproject_toml, but does not consolidate optional dependencies.

Parameters:

  • data (str) –

    The contents of a TOML file

  • ignore (collections.abc.Iterable[str], default: () ) –

    One or more package names to leave as-is

  • include_pointers (tuple[str, ...], default: () ) –

    A tuple of JSON pointers indicating elements to include (defaults to all elements).

  • exclude_pointers (tuple[str, ...], default: () ) –

    A tuple of JSON pointers indicating elements to exclude (defaults to no exclusions).

Returns:

  • str

    The contents of the updated TOML file.

Source code in src/dependence/update.py
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
def _get_updated_toml(
    data: str,
    ignore: Iterable[str] = (),
    include_pointers: tuple[str, ...] = (),
    exclude_pointers: tuple[str, ...] = (),
) -> str:
    """
    Return the contents of a TOML file, updated to reflect the
    currently installed project versions, excluding those specified in
    `ignore`.

    Note: This functions identically to `get_updated_pyproject_toml`, but
    does not consolidate optional dependencies.

    Parameters:
        data: The contents of a TOML file
        ignore: One or more package names to leave as-is
        include_pointers: A tuple of JSON pointers indicating elements to
            include (defaults to all elements).
        exclude_pointers: A tuple of JSON pointers indicating elements to
            exclude (defaults to no exclusions).

    Returns:
        The contents of the updated TOML file.
    """
    # Parse pyproject.toml
    original_pyproject: dict[str, Any] = tomli.loads(data)
    updated_pyproject: dict[str, Any] = deepcopy(original_pyproject)
    # Find and update requirements
    _update_document_requirements(
        updated_pyproject,
        ignore=ignore,
        include_pointers=include_pointers,
        exclude_pointers=exclude_pointers,
    )
    # Only dump the data if something was updated
    if original_pyproject != updated_pyproject:
        return tomli_w.dumps(updated_pyproject)
    return data

update

update(
    paths: collections.abc.Iterable[str],
    ignore: collections.abc.Iterable[str] = (),
    all_extra_name: str = "",
    include_pointers: tuple[str, ...] = (),
    exclude_pointers: tuple[str, ...] = (),
) -> None

Update requirement versions in the specified files.

Parameters:

  • path

    One or more local paths to a setup.cfg, setup.cfg, and/or requirements.txt files

  • ignore (collections.abc.Iterable[str], default: () ) –

    One or more project names to ignore (leave as-is)

  • all_extra_name (str, default: '' ) –

    If provided, an extra which consolidates the requirements for all other extras will be added/updated to setup.cfg or setup.cfg (this argument is ignored for requirements.txt files)

  • include_pointers (tuple[str, ...], default: () ) –

    A tuple of JSON pointers indicating elements to include (defaults to all elements).

  • exclude_pointers (tuple[str, ...], default: () ) –

    A tuple of JSON pointers indicating elements to exclude (defaults to no exclusions).

Source code in src/dependence/update.py
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
def update(
    paths: Iterable[str],
    ignore: Iterable[str] = (),
    all_extra_name: str = "",
    include_pointers: tuple[str, ...] = (),
    exclude_pointers: tuple[str, ...] = (),
) -> None:
    """
    Update requirement versions in the specified files.

    Parameters:
        path: One or more local paths to a setup.cfg,
            setup.cfg, and/or requirements.txt files
        ignore: One or more project names to ignore (leave as-is)
        all_extra_name: If provided, an extra which consolidates
            the requirements for all other extras will be added/updated to
            setup.cfg or setup.cfg (this argument is ignored for
            requirements.txt files)
        include_pointers: A tuple of JSON pointers indicating elements to
            include (defaults to all elements).
        exclude_pointers: A tuple of JSON pointers indicating elements to
            exclude (defaults to no exclusions).
    """
    if isinstance(paths, str):
        paths = (paths,)

    def update_(path: str) -> None:
        _update(
            path,
            ignore=ignore,
            all_extra_name=all_extra_name,
            include_pointers=include_pointers,
            exclude_pointers=exclude_pointers,
        )

    deque(map(update_, paths), maxlen=0)