• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pybuilder / pybuilder / 23672534566

27 Mar 2026 11:58PM UTC coverage: 82.55% (-0.4%) from 82.991%
23672534566

push

github

web-flow
Release 0.13.19 [release] (#943)

## Summary

- Update CLAUDE.md with venv infrastructure, logs/reports, release
procedure, `-vX` docs
- Bump actions/checkout v4 to v6

## Changes included in 0.13.19

- #941 Full extras and markers support for dependencies
- #942 Fix lib64 site-packages not added to sys.path on Fedora/RHEL
- #938 Drop Python 3.9 support
- Python 3.14 support

1388 of 1856 branches covered (74.78%)

Branch coverage included in aggregate %.

5533 of 6528 relevant lines covered (84.76%)

8.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.6
/src/main/python/pybuilder/core.py
1
#   -*- coding: utf-8 -*-
2
#
3
#   This file is part of PyBuilder
4
#
5
#   Copyright 2011-2020 PyBuilder Team
6
#
7
#   Licensed under the Apache License, Version 2.0 (the "License");
8
#   you may not use this file except in compliance with the License.
9
#   You may obtain a copy of the License at
10
#
11
#       http://www.apache.org/licenses/LICENSE-2.0
12
#
13
#   Unless required by applicable law or agreed to in writing, software
14
#   distributed under the License is distributed on an "AS IS" BASIS,
15
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
#   See the License for the specific language governing permissions and
17
#   limitations under the License.
18

19
"""
20
    The PyBuilder core module.
21
    Contains the most important classes and syntax used in a
22
    build.py project descriptor.
23
"""
24
import fnmatch
10✔
25
import os
10✔
26
import string
10✔
27
from os.path import isdir, isfile, basename, relpath, sep
10✔
28

29
import itertools
10✔
30
import logging
10✔
31
import re
10✔
32
import sys
10✔
33
from datetime import datetime
10✔
34

35
try:
10✔
36
    from datetime import UTC
10✔
37
except ImportError:
2✔
38
    from datetime import timezone
2✔
39

40
    UTC = timezone.utc
2✔
41

42
# Plugin install_dependencies_plugin can reload pip_common and pip_utils. Do not use from ... import ...
43
from pybuilder.errors import MissingPropertyException, UnspecifiedPluginNameException
10✔
44
from pybuilder.utils import as_list, np, ap, jp
10✔
45
from pybuilder.python_utils import OrderedDict
10✔
46

47
PATH_SEP_RE = re.compile(r"[/\\]")
10✔
48

49
INITIALIZER_ATTRIBUTE = "_pybuilder_initializer"
10✔
50
FINALIZER_ATTRIBUTE = "_pybuilder_finalizer"
10✔
51

52
ENVIRONMENTS_ATTRIBUTE = "_pybuilder_environments"
10✔
53

54
NAME_ATTRIBUTE = "_pybuilder_name"
10✔
55
ACTION_ATTRIBUTE = "_pybuilder_action"
10✔
56
ONLY_ONCE_ATTRIBUTE = "_pybuilder_action_only_once"
10✔
57
TEARDOWN_ATTRIBUTE = "_pybuilder_action_teardown"
10✔
58
BEFORE_ATTRIBUTE = "_pybuilder_before"
10✔
59
AFTER_ATTRIBUTE = "_pybuilder_after"
10✔
60

61
TASK_ATTRIBUTE = "_pybuilder_task"
10✔
62
DEPENDS_ATTRIBUTE = "_pybuilder_depends"
10✔
63
DEPENDENTS_ATTRIBUTE = "_pybuilder_dependents"
10✔
64

65
DESCRIPTION_ATTRIBUTE = "_pybuilder_description"
10✔
66

67

68
def init(*possible_callable, **additional_arguments):
10✔
69
    """
70
    Decorator for functions that wish to perform initialization steps.
71
    The decorated functions are called "initializers".
72

73
    Initializers are executed after all plugins and projects have been loaded
74
    but before any task is executed.
75

76
    Initializers may take an additional named argument "environments" which should contain a string or list of strings
77
    naming the environments this initializer applies for.
78

79
    Examples:
80

81
    @init
82
    def some_initializer(): pass
83

84
    @init()
85
    def some_initializer(): pass
86

87
    @init(environments="spam")
88
    def some_initializer(): pass
89

90
    @init(environments=["spam", "eggs"])
91
    def some_initializer(): pass
92
    """
93

94
    def do_decoration(callable_):
10✔
95
        setattr(callable_, INITIALIZER_ATTRIBUTE, True)
10✔
96

97
        if "environments" in additional_arguments:
10✔
98
            setattr(callable_, ENVIRONMENTS_ATTRIBUTE, as_list(additional_arguments["environments"]))
10✔
99

100
        return callable_
10✔
101

102
    if possible_callable:
10✔
103
        return do_decoration(possible_callable[0])
10✔
104

105
    return do_decoration
10✔
106

107

108
def finalize(*possible_callable, **additional_arguments):
10✔
109
    """
110
    Decorator for functions that wish to perform finalization steps.
111
    The decorated functions are called "finalizers".
112

113
    Finalizers are executed after all tasks have been executed, at the very end of the
114

115
    Finalizers may take an additional named argument "environments" which should contain a string or list of strings
116
    naming the environments this finalizer applies for.
117

118
    Examples:
119

120
    @finalize
121
    def some_finalizer(): pass
122

123
    @finalize()
124
    def some_finalizer(): pass
125

126
    @finalize(environments="spam")
127
    def some_finalizer(): pass
128

129
    @finalize(environments=["spam", "eggs"])
130
    def some_finalizer(): pass
131
    """
132

133
    def do_decoration(callable_):
10✔
134
        setattr(callable_, FINALIZER_ATTRIBUTE, True)
10✔
135

136
        if "environments" in additional_arguments:
10!
137
            setattr(callable_, ENVIRONMENTS_ATTRIBUTE, as_list(additional_arguments["environments"]))
10✔
138

139
        return callable_
10✔
140

141
    if possible_callable:
10!
142
        return do_decoration(possible_callable[0])
×
143

144
    return do_decoration
10✔
145

146

147
def task(callable_or_string=None, description=None):
10✔
148
    """
149
    Decorator for functions that should be used as tasks. Tasks are the main
150
    building blocks of projects.
151
    You can use this decorator either plain (no argument) or with
152
    a string argument, which overrides the default name.
153
    """
154
    if isinstance(callable_or_string, str):
10✔
155
        def set_name_and_task_attribute(callable_):
10✔
156
            setattr(callable_, TASK_ATTRIBUTE, True)
10✔
157
            setattr(callable_, NAME_ATTRIBUTE, callable_or_string)
10✔
158
            if description:
10✔
159
                setattr(callable_, DESCRIPTION_ATTRIBUTE, description)
10✔
160
            return callable_
10✔
161

162
        return set_name_and_task_attribute
10✔
163
    else:
164
        if not description:
10✔
165
            if callable_or_string is not None:
10✔
166
                setattr(callable_or_string, TASK_ATTRIBUTE, True)
10✔
167
                setattr(callable_or_string, NAME_ATTRIBUTE, callable_or_string.__name__)
10✔
168
                return callable_or_string
10✔
169
            else:
170
                def set_task_and_description_attribute(callable_):
10✔
171
                    setattr(callable_, TASK_ATTRIBUTE, True)
10✔
172
                    setattr(callable_, NAME_ATTRIBUTE, callable_.__name__)
10✔
173
                    return callable_
10✔
174

175
                return set_task_and_description_attribute
10✔
176
        else:
177
            def set_task_and_description_attribute(callable_):
10✔
178
                setattr(callable_, TASK_ATTRIBUTE, True)
10✔
179
                setattr(callable_, NAME_ATTRIBUTE, callable_.__name__)
10✔
180
                setattr(callable_, DESCRIPTION_ATTRIBUTE, description)
10✔
181
                return callable_
10✔
182

183
            return set_task_and_description_attribute
10✔
184

185

186
class description(object):
10✔
187
    def __init__(self, description):
10✔
188
        self._description = description
10✔
189

190
    def __call__(self, callable_):
10✔
191
        setattr(callable_, DESCRIPTION_ATTRIBUTE, self._description)
10✔
192
        return callable_
10✔
193

194

195
class depends(object):
10✔
196
    def __init__(self, *depends):
10✔
197
        self._depends = depends
10✔
198

199
    def __call__(self, callable_):
10✔
200
        setattr(callable_, DEPENDS_ATTRIBUTE, self._depends)
10✔
201
        return callable_
10✔
202

203

204
class dependents(object):
10✔
205
    def __init__(self, *dependents):
10✔
206
        self._dependents = dependents
10✔
207

208
    def __call__(self, callable_):
10✔
209
        setattr(callable_, DEPENDENTS_ATTRIBUTE, self._dependents)
10✔
210
        return callable_
10✔
211

212

213
class optional(object):
10✔
214
    def __init__(self, *names):
10✔
215
        self._names = names
10✔
216

217
    def __call__(self):
10✔
218
        return self._names
10✔
219

220

221
class BaseAction(object):
10✔
222
    def __init__(self, attribute, only_once, tasks, teardown=False):
10✔
223
        self.tasks = tasks
10✔
224
        self.attribute = attribute
10✔
225
        self.only_once = only_once
10✔
226
        self.teardown = teardown
10✔
227

228
    def __call__(self, callable_):
10✔
229
        setattr(callable_, ACTION_ATTRIBUTE, True)
10✔
230
        setattr(callable_, self.attribute, self.tasks)
10✔
231
        if self.only_once:
10✔
232
            setattr(callable_, ONLY_ONCE_ATTRIBUTE, True)
10✔
233
        if self.teardown:
10✔
234
            setattr(callable_, TEARDOWN_ATTRIBUTE, True)
10✔
235
        return callable_
10✔
236

237

238
class before(BaseAction):
10✔
239
    def __init__(self, tasks, only_once=False):
10✔
240
        super(before, self).__init__(BEFORE_ATTRIBUTE, only_once, tasks)
10✔
241

242

243
class after(BaseAction):
10✔
244
    def __init__(self, tasks, only_once=False, teardown=False):
10✔
245
        super(after, self).__init__(AFTER_ATTRIBUTE, only_once, tasks, teardown)
10✔
246

247

248
def use_bldsup(build_support_dir="bldsup"):
10✔
249
    """Specify a local build support directory for build specific extensions.
250

251
    use_plugin(name) and import will look for python modules in BUILD_SUPPORT_DIR.
252

253
    WARNING: The BUILD_SUPPORT_DIR must exist and must have an __init__.py file in it.
254
    """
255
    assert isdir(build_support_dir), "use_bldsup('{0}'): The {0} directory must exist!".format(
×
256
        build_support_dir)
257
    init_file = jp(build_support_dir, "__init__.py")
×
258
    assert isfile(init_file), "use_bldsup('{0}'): The {1} file must exist!".format(build_support_dir, init_file)
×
259
    sys.path.insert(0, build_support_dir)
×
260

261

262
def use_plugin(name, version=None, plugin_module_name=None):
10✔
263
    from pybuilder.reactor import Reactor
10✔
264
    reactor = Reactor.current_instance()
10✔
265
    if reactor is not None:
10✔
266
        reactor.require_plugin(name, version, plugin_module_name)
10✔
267

268

269
class Author(object):
10✔
270
    def __init__(self, name, email=None, roles=None):
10✔
271
        self.name = name
10✔
272
        self.email = email
10✔
273
        self.roles = roles or []
10✔
274

275

276
class Dependency(object):
10✔
277
    """
278
    Defines a dependency to another module. Use the
279
        depends_on
280
    method from class Project to add a dependency to a project.
281
    """
282

283
    def __init__(self, name, version=None, url=None, declaration_only=False, eager_update=None,
10✔
284
                 extra=None, markers=None):
285
        from pybuilder import pip_common
10✔
286
        if version:
10✔
287
            try:
10✔
288
                version = ">=" + str(pip_common.Version(version))
10✔
289
                self.version_not_a_spec = True
10✔
290
            except pip_common.InvalidVersion:
10✔
291
                try:
10✔
292
                    version = str(pip_common.SpecifierSet(version))
10✔
293
                except pip_common.InvalidSpecifier:
10✔
294
                    raise ValueError("'%s' must be either PEP 0440 version or a version specifier set" % version)
10✔
295

296
        extras = None
10✔
297
        try:
10✔
298
            req = pip_common.Requirement(name)
10✔
299
            name = req.name
10✔
300
            extras = list(req.extras) if req.extras else None
10✔
301
            version = version or str(req.specifier) or None
10✔
302
            url = url or req.url
10✔
303
            markers = markers or (str(req.marker) if req.marker else None)
10✔
304
        except pip_common.InvalidRequirement:
10✔
305
            pass
10✔
306

307
        self.name = name
10✔
308
        self.extras = extras
10✔
309
        self.version = version
10✔
310
        self.url = url
10✔
311
        self.declaration_only = declaration_only
10✔
312
        self.eager_update = eager_update
10✔
313
        self.extra = extra
10✔
314
        self.markers = markers
10✔
315

316
    def __eq__(self, other):
10✔
317
        if not isinstance(other, Dependency):
10✔
318
            return False
10✔
319
        return (self.name == other.name and self.version == other.version
10✔
320
                and self.url == other.url and self.extra == other.extra)
321

322
    def __ne__(self, other):
10✔
323
        return not (self == other)
10✔
324

325
    def __hash__(self):
10✔
326
        return 13 * hash(self.name) + 17 * hash(self.version) + 19 * hash(self.extra)
10✔
327

328
    def __lt__(self, other):
10✔
329
        if not isinstance(other, Dependency):
10✔
330
            return True
10✔
331
        return self.name < other.name
10✔
332

333
    def __str__(self):
10✔
334
        return self.name
10✔
335

336
    def __unicode__(self):
10✔
337
        return str(self)
×
338

339
    def __repr__(self):
10✔
340
        return (self.name +
10✔
341
                (("," + self.version) if self.version else "") +
342
                (("," + self.url) if self.url else "") +
343
                (" (declaration only)" if self.declaration_only else ""))
344

345

346
class RequirementsFile(object):
10✔
347
    """
348
    Represents all dependencies in a requirements file (requirements.txt).
349
    """
350

351
    def __init__(self, filename, declaration_only=False):
10✔
352
        self.name = filename
10✔
353
        self.version = None
10✔
354
        self.declaration_only = declaration_only
10✔
355

356
    def __eq__(self, other):
10✔
357
        if not isinstance(other, RequirementsFile):
10!
358
            return False
×
359
        return self.name == other.name
10✔
360

361
    def __ne__(self, other):
10✔
362
        return not (self == other)
10✔
363

364
    def __lt__(self, other):
10✔
365
        if not isinstance(other, RequirementsFile):
10✔
366
            return False
10✔
367
        return self.name < other.name
10✔
368

369
    def __hash__(self):
10✔
370
        return 42 * hash(self.name)
10✔
371

372
    def __str__(self):
10✔
373
        return self.name
10✔
374

375
    def __repr__(self):
10✔
376
        return self.name
×
377

378

379
class PluginDef:
10✔
380
    PYPI_PLUGIN_PROTOCOL = "pypi:"
10✔
381
    VCS_PLUGIN_PROTOCOL = "vcs:"
10✔
382

383
    def __init__(self, name, version=None, plugin_module_name=None):
10✔
384
        pip_package = pip_package_version = pip_package_url = None
10✔
385

386
        if name.startswith(PluginDef.PYPI_PLUGIN_PROTOCOL):
10✔
387
            pip_package = name.replace(PluginDef.PYPI_PLUGIN_PROTOCOL, "")
10✔
388
            if version:
10✔
389
                pip_package_version = str(version)
10✔
390
            plugin_module_name = plugin_module_name or pip_package
10✔
391
        elif name.startswith(PluginDef.VCS_PLUGIN_PROTOCOL):
10✔
392
            pip_package_url = name.replace(PluginDef.VCS_PLUGIN_PROTOCOL, "")
10✔
393
            if not plugin_module_name:
10!
394
                raise UnspecifiedPluginNameException(name)
×
395
            pip_package = pip_package_url
10✔
396

397
        self._dep = None
10✔
398
        if pip_package or pip_package_version or pip_package_url:
10✔
399
            self._dep = Dependency(pip_package, pip_package_version, pip_package_url)
10✔
400
        self._val = (name, version, plugin_module_name)
10✔
401

402
    @property
10✔
403
    def name(self):
10✔
404
        return self._val[0]
10✔
405

406
    @property
10✔
407
    def version(self):
10✔
408
        return self._val[1]
10✔
409

410
    @property
10✔
411
    def plugin_module_name(self):
10✔
412
        return self._val[2]
10✔
413

414
    @property
10✔
415
    def dependency(self):
10✔
416
        return self._dep
10✔
417

418
    def __repr__(self):
10✔
419
        return "PluginDef [name=%r, version=%r, plugin_module_name=%r]" % (self.name,
×
420
                                                                           self.version,
421
                                                                           self.plugin_module_name)
422

423
    def __str__(self):
10✔
424
        return "%s%s%s" % (self.name, " version %s" % self.version if self.version else "",
10✔
425
                           ", module name '%s'" % self.plugin_module_name if self.plugin_module_name else "")
426

427
    def __eq__(self, other):
10✔
428
        return isinstance(other, PluginDef) and other._val == self._val
10✔
429

430
    def __hash__(self):
10✔
431
        return self._val.__hash__()
10✔
432

433

434
class Project(object):
10✔
435
    """
436
    Descriptor for a project to be built. A project has a number of attributes
437
    as well as some convenience methods to access these properties.
438
    """
439

440
    def __init__(self, basedir, version="1.0.dev0", name=None, offline=False, no_venvs=False):
10✔
441
        self.name = name
10✔
442
        self._version = None
10✔
443
        self._dist_version = None
10✔
444
        self.offline = offline
10✔
445
        self.no_venvs = no_venvs
10✔
446
        self.version = version
10✔
447
        self.basedir = ap(basedir)
10✔
448
        if not self.name:
10✔
449
            self.name = basename(basedir)
10✔
450

451
        self.default_task = None
10✔
452

453
        self.summary = ""
10✔
454
        self.description = ""
10✔
455

456
        self.author = ""
10✔
457
        self.authors = []
10✔
458
        self.maintainer = ""
10✔
459
        self.maintainers = []
10✔
460

461
        self.license = ""
10✔
462
        self.url = ""
10✔
463
        self.urls = {}
10✔
464

465
        self._requires_python = ""
10✔
466
        self._obsoletes = []
10✔
467
        self._explicit_namespaces = []
10✔
468
        self._properties = {"verbose": False}
10✔
469
        self._install_dependencies = set()
10✔
470
        self._build_dependencies = set()
10✔
471
        self._plugin_dependencies = set()
10✔
472
        self._manifest_included_files = []
10✔
473
        self._manifest_included_directories = []
10✔
474
        self._package_data = OrderedDict()
10✔
475
        self._files_to_install = []
10✔
476
        self._preinstall_script = None
10✔
477
        self._postinstall_script = None
10✔
478
        self._environments = ()
10✔
479

480
    def __str__(self):
10✔
481
        return "[Project name=%s basedir=%s]" % (self.name, self.basedir)
×
482

483
    @property
10✔
484
    def version(self):
10✔
485
        return self._version
10✔
486

487
    @version.setter
10✔
488
    def version(self, value):
10✔
489
        self._version = value
10✔
490
        if value.endswith('.dev'):
10!
491
            value += datetime.now(UTC).strftime("%Y%m%d%H%M%S")
×
492
        self._dist_version = value
10✔
493

494
    @property
10✔
495
    def requires_python(self):
10✔
496
        return self._requires_python
10✔
497

498
    @requires_python.setter
10✔
499
    def requires_python(self, value):
10✔
500
        from pybuilder import pip_common
10✔
501
        spec_set = pip_common.SpecifierSet(value)
10✔
502
        self._requires_python = str(spec_set)
10✔
503

504
    @property
10✔
505
    def obsoletes(self):
10✔
506
        return self._obsoletes
10✔
507

508
    @obsoletes.setter
10✔
509
    def obsoletes(self, value):
10✔
510
        self._obsoletes = as_list(value)
×
511

512
    @property
10✔
513
    def explicit_namespaces(self):
10✔
514
        return self._explicit_namespaces
10✔
515

516
    @explicit_namespaces.setter
10✔
517
    def explicit_namespaces(self, value):
10✔
518
        self._explicit_namespaces = as_list(value)
10✔
519

520
    @property
10✔
521
    def dist_version(self):
10✔
522
        return self._dist_version
10✔
523

524
    def validate(self):
10✔
525
        """
526
        Validates the project returning a list of validation error messages if the project is not valid.
527
        Returns an empty list if the project is valid.
528
        """
529
        result = self.validate_dependencies()
10✔
530

531
        return result
10✔
532

533
    def validate_dependencies(self):
10✔
534
        result = []
10✔
535

536
        build_dependencies_found = {}
10✔
537

538
        for dependency in self.build_dependencies:
10✔
539
            if dependency.name in build_dependencies_found:
10✔
540
                if build_dependencies_found[dependency.name] == 1:
10✔
541
                    result.append("Build dependency '%s' has been defined multiple times." % dependency.name)
10✔
542
                build_dependencies_found[dependency.name] += 1
10✔
543
            else:
544
                build_dependencies_found[dependency.name] = 1
10✔
545

546
        runtime_dependencies_found = {}
10✔
547

548
        for dependency in self.dependencies:
10✔
549
            if dependency.name in runtime_dependencies_found:
10✔
550
                if runtime_dependencies_found[dependency.name] == 1:
10✔
551
                    result.append("Runtime dependency '%s' has been defined multiple times." % dependency.name)
10✔
552
                runtime_dependencies_found[dependency.name] += 1
10✔
553
            else:
554
                runtime_dependencies_found[dependency.name] = 1
10✔
555
            if dependency.name in build_dependencies_found:
10✔
556
                result.append("Runtime dependency '%s' has also been given as build dependency." % dependency.name)
10✔
557

558
        for extra_name, deps in self.extras_dependencies.items():
10✔
559
            extra_deps_found = {}
10✔
560
            for dependency in deps:
10✔
561
                if dependency.name in extra_deps_found:
10✔
562
                    if extra_deps_found[dependency.name] == 1:
10!
563
                        result.append("Extra '%s' dependency '%s' has been defined multiple times." %
10✔
564
                                      (extra_name, dependency.name))
565
                    extra_deps_found[dependency.name] += 1
10✔
566
                else:
567
                    extra_deps_found[dependency.name] = 1
10✔
568

569
        return result
10✔
570

571
    @property
10✔
572
    def properties(self):
10✔
573
        result = self._properties
10✔
574
        result["basedir"] = self.basedir
10✔
575
        return result
10✔
576

577
    @property
10✔
578
    def dependencies(self):
10✔
579
        return list(sorted(d for d in self._install_dependencies if getattr(d, 'extra', None) is None))
10✔
580

581
    @property
10✔
582
    def build_dependencies(self):
10✔
583
        return list(sorted(self._build_dependencies))
10✔
584

585
    @property
10✔
586
    def plugin_dependencies(self):
10✔
587
        return list(sorted(self._plugin_dependencies))
10✔
588

589
    def depends_on(self, name, version=None, url=None, declaration_only=False, eager_update=None,
10✔
590
                   extra=None, markers=None):
591
        self._install_dependencies.add(Dependency(name, version, url, declaration_only,
10✔
592
                                                  eager_update=eager_update, extra=extra, markers=markers))
593

594
    def build_depends_on(self, name, version=None, url=None, declaration_only=False, eager_update=None, markers=None):
10✔
595
        self._build_dependencies.add(Dependency(name, version, url, declaration_only,
10✔
596
                                                eager_update=eager_update, markers=markers))
597

598
    def depends_on_requirements(self, file, declaration_only=False):
10✔
599
        self._install_dependencies.add(RequirementsFile(file, declaration_only=declaration_only))
10✔
600

601
    def build_depends_on_requirements(self, file):
10✔
602
        self._build_dependencies.add(RequirementsFile(file))
10✔
603

604
    def plugin_depends_on(self, name, version=None, url=None, declaration_only=False, eager_update=None, markers=None):
10✔
605
        self._plugin_dependencies.add(Dependency(name, version, url, declaration_only,
10✔
606
                                                 eager_update=eager_update, markers=markers))
607

608
    @property
10✔
609
    def extras_dependencies(self):
10✔
610
        result = OrderedDict()
10✔
611
        for dep in sorted(self._install_dependencies):
10✔
612
            extra = getattr(dep, 'extra', None)
10✔
613
            if extra is not None:
10✔
614
                result.setdefault(extra, []).append(dep)
10✔
615
        return result
10✔
616

617
    @property
10✔
618
    def environments(self):
10✔
619
        return self._environments
10✔
620

621
    @property
10✔
622
    def setup_preinstall_script(self):
10✔
623
        return self._preinstall_script
10✔
624

625
    def pre_install_script(self, script):
10✔
626
        self._preinstall_script = script
10✔
627

628
    @property
10✔
629
    def setup_postinstall_script(self):
10✔
630
        return self._postinstall_script
10✔
631

632
    def post_install_script(self, script):
10✔
633
        self._postinstall_script = script
10✔
634

635
    @property
10✔
636
    def manifest_included_files(self):
10✔
637
        return self._manifest_included_files
10✔
638

639
    @property
10✔
640
    def manifest_included_directories(self):
10✔
641
        return self._manifest_included_directories
10✔
642

643
    def _manifest_include(self, glob_pattern):
10✔
644
        if not glob_pattern or glob_pattern.strip() == "":
10✔
645
            raise ValueError("Missing glob_pattern argument.")
10✔
646

647
        self._manifest_included_files.append(glob_pattern)
10✔
648

649
    def _manifest_include_directory(self, directory, patterns_list):
10✔
650
        if not directory or directory.strip() == "":
10✔
651
            raise ValueError("Missing directory argument.")
10✔
652

653
        patterns_list = map(lambda s: s.strip(), patterns_list)
10✔
654
        patterns_list = tuple(filter(bool, patterns_list))
10✔
655
        if len(patterns_list) == 0:
10✔
656
            raise ValueError("Missing patterns_list argument.")
10✔
657

658
        directory_to_include = (directory, patterns_list)
10✔
659
        self._manifest_included_directories.append(directory_to_include)
10✔
660

661
    @property
10✔
662
    def package_data(self):
10✔
663
        return self._package_data
10✔
664

665
    def include_file(self, package_name, filename):
10✔
666
        package_name = package_name or ""
10✔
667

668
        if not filename or filename.strip() == "":
10✔
669
            raise ValueError("Missing argument filename.")
10✔
670

671
        full_filename = np(jp(package_name.replace(".", sep), filename))
10✔
672
        self._manifest_include(full_filename)
10✔
673

674
        self._add_package_data(package_name, filename)
10✔
675

676
    def include_directory(self, package_path, patterns_list, package_root=""):
10✔
677
        if not package_path or package_path.strip() == "":
10✔
678
            raise ValueError("Missing argument package_path.")
10✔
679

680
        if not patterns_list:
10✔
681
            raise ValueError("Missing argument patterns_list.")
10✔
682
        patterns_list = as_list(patterns_list)
10✔
683

684
        package_name = PATH_SEP_RE.sub(".", package_path)
10✔
685
        self._manifest_include_directory(package_path, patterns_list)
10✔
686

687
        package_full_path = self.expand_path(package_root, package_path)
10✔
688

689
        for root, dirnames, filenames in os.walk(package_full_path):
10✔
690
            filenames = list(fnmatch.filter(filenames, pattern) for pattern in patterns_list)
10✔
691

692
            for filename in itertools.chain.from_iterable(filenames):
10✔
693
                full_path = np(jp(root, filename))
10✔
694
                relative_path = relpath(full_path, package_full_path)
10✔
695
                self._add_package_data(package_name, relative_path)
10✔
696

697
    def _add_package_data(self, package_name, filename):
10✔
698
        filename = filename.replace("\\", "/")
10✔
699
        self._package_data.setdefault(package_name, []).append(filename)
10✔
700

701
    @property
10✔
702
    def files_to_install(self):
10✔
703
        return self._files_to_install
10✔
704

705
    def install_file(self, destination, filename):
10✔
706
        if not destination:
10✔
707
            raise ValueError("Missing argument destination")
10✔
708

709
        if not filename or filename.strip() == "":
10✔
710
            raise ValueError("Missing argument filename")
10✔
711

712
        current_tuple = None
10✔
713
        for installation_tuple in self.files_to_install:
10✔
714
            destination_name = installation_tuple[0]
10✔
715

716
            if destination_name == destination:
10✔
717
                current_tuple = installation_tuple
10✔
718

719
        if current_tuple:
10✔
720
            list_of_files_within_tuple = current_tuple[1]
10✔
721
            list_of_files_within_tuple.append(filename)
10✔
722
        else:
723
            initial_tuple = (destination, [filename])
10✔
724
            self.files_to_install.append(initial_tuple)
10✔
725

726
        self._manifest_include(filename)
10✔
727

728
    def expand(self, format_string):
10✔
729
        previous = None
10✔
730
        result = format_string
10✔
731
        while previous != result:
10✔
732
            try:
10✔
733
                previous = result
10✔
734
                result = string.Template(result).substitute(self.properties)
10✔
735
            except KeyError as e:
10✔
736
                raise MissingPropertyException(e)
10✔
737
        return result
10✔
738

739
    def expand_path(self, format_string, *additional_path_elements):
10✔
740
        elements = [self.basedir]
10✔
741
        elements += list(PATH_SEP_RE.split(self.expand(format_string)))
10✔
742
        elements += list(additional_path_elements)
10✔
743
        return np(jp(*elements))
10✔
744

745
    def get_property(self, key, default_value=None):
10✔
746
        return self.properties.get(key, default_value)
10✔
747

748
    def get_mandatory_property(self, key):
10✔
749
        if not self.has_property(key):
10✔
750
            raise MissingPropertyException(key)
10✔
751
        return self.get_property(key)
10✔
752

753
    def has_property(self, key):
10✔
754
        return key in self.properties
10✔
755

756
    def set_property(self, key, value):
10✔
757
        self.properties[key] = value
10✔
758

759
    def set_property_if_unset(self, key, value):
10✔
760
        if not self.has_property(key):
10✔
761
            self.set_property(key, value)
10✔
762

763

764
class Logger(logging.Handler):
10✔
765
    CRITICAL = 50
10✔
766
    FATAL = CRITICAL
10✔
767
    ERROR = 40
10✔
768
    WARNING = 30
10✔
769
    WARN = WARNING
10✔
770
    INFO = 20
10✔
771
    DEBUG = 10
10✔
772

773
    def __init__(self, level=INFO, log_time_format=None):
10✔
774
        super(Logger, self).__init__(level)
10✔
775
        self.log_time_format = log_time_format
10✔
776

777
    def emit(self, record):
10✔
778
        self._do_log(record.levelno, record.getMessage())
×
779

780
    def _do_log(self, level, message, *arguments):
10✔
781
        pass
×
782

783
    @staticmethod
10✔
784
    def _format_message(message, *arguments):
10✔
785
        if arguments:
10✔
786
            return message % arguments
10✔
787
        return message
10✔
788

789
    def log(self, level, message, *arguments):
10✔
790
        if level >= self.level:
10✔
791
            self._do_log(level, message, *arguments)
10✔
792

793
    def debug(self, message, *arguments):
10✔
794
        self.log(Logger.DEBUG, message, *arguments)
10✔
795

796
    def info(self, message, *arguments):
10✔
797
        self.log(Logger.INFO, message, *arguments)
10✔
798

799
    def warn(self, message, *arguments):
10✔
800
        self.log(Logger.WARN, message, *arguments)
10✔
801

802
    def error(self, message, *arguments):
10✔
803
        self.log(Logger.ERROR, message, *arguments)
10✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc