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

pybuilder / pybuilder / 16613314016

30 Jul 2025 04:14AM UTC coverage: 84.008% (-0.1%) from 84.146%
16613314016

push

github

arcivanov
Release 0.13.16

2167 of 2671 branches covered (81.13%)

Branch coverage included in aggregate %.

5534 of 6496 relevant lines covered (85.19%)

36.25 hits per line

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

96.88
/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
"""
44✔
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
44✔
25
import os
44✔
26
import string
44✔
27
from os.path import isdir, isfile, basename, relpath, sep
44✔
28

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

35
try:
44✔
36
    from datetime import UTC
44✔
37
except ImportError:
20✔
38
    from datetime import timezone
20✔
39

40
    UTC = timezone.utc
20✔
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
44✔
44
from pybuilder.utils import as_list, np, ap, jp
44✔
45
from pybuilder.python_utils import OrderedDict
44✔
46

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

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

52
ENVIRONMENTS_ATTRIBUTE = "_pybuilder_environments"
44✔
53

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

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

65
DESCRIPTION_ATTRIBUTE = "_pybuilder_description"
44✔
66

67

68
def init(*possible_callable, **additional_arguments):
44✔
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_):
44✔
95
        setattr(callable_, INITIALIZER_ATTRIBUTE, True)
44✔
96

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

100
        return callable_
44✔
101

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

105
    return do_decoration
44✔
106

107

108
def finalize(*possible_callable, **additional_arguments):
44✔
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_):
22✔
134
        setattr(callable_, FINALIZER_ATTRIBUTE, True)
22✔
135

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

139
        return callable_
22✔
140

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

144
    return do_decoration
22✔
145

146

147
def task(callable_or_string=None, description=None):
44✔
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):
44✔
155
        def set_name_and_task_attribute(callable_):
44✔
156
            setattr(callable_, TASK_ATTRIBUTE, True)
44✔
157
            setattr(callable_, NAME_ATTRIBUTE, callable_or_string)
44✔
158
            if description:
44✔
159
                setattr(callable_, DESCRIPTION_ATTRIBUTE, description)
44✔
160
            return callable_
44✔
161

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

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

183
            return set_task_and_description_attribute
44✔
184

185

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

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

194

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

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

203

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

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

212

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

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

220

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

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

237

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

242

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

247

248
def use_bldsup(build_support_dir="bldsup"):
44✔
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):
44✔
263
    from pybuilder.reactor import Reactor
44✔
264
    reactor = Reactor.current_instance()
44✔
265
    if reactor is not None:
44✔
266
        reactor.require_plugin(name, version, plugin_module_name)
44✔
267

268

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

275

276
class Dependency(object):
44✔
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):
44✔
284
        from pybuilder import pip_common
44✔
285
        if version:
44✔
286
            try:
44✔
287
                version = ">=" + str(pip_common.Version(version))
44✔
288
                self.version_not_a_spec = True
44✔
289
            except pip_common.InvalidVersion:
44✔
290
                try:
44✔
291
                    version = str(pip_common.SpecifierSet(version))
44✔
292
                except pip_common.InvalidSpecifier:
44✔
293
                    raise ValueError("'%s' must be either PEP 0440 version or a version specifier set" % version)
44✔
294

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

305
        self.name = name
44✔
306
        self.extras = extras
44✔
307
        self.version = version
44✔
308
        self.url = url
44✔
309
        self.declaration_only = declaration_only
44✔
310
        self.eager_update = eager_update
44✔
311

312
    def __eq__(self, other):
44✔
313
        if not isinstance(other, Dependency):
44✔
314
            return False
44✔
315
        return self.name == other.name and self.version == other.version and self.url == other.url
44✔
316

317
    def __ne__(self, other):
44✔
318
        return not (self == other)
44✔
319

320
    def __hash__(self):
44✔
321
        return 13 * hash(self.name) + 17 * hash(self.version)
44✔
322

323
    def __lt__(self, other):
44✔
324
        if not isinstance(other, Dependency):
44✔
325
            return True
44✔
326
        return self.name < other.name
44✔
327

328
    def __str__(self):
44✔
329
        return self.name
44✔
330

331
    def __unicode__(self):
44✔
332
        return str(self)
×
333

334
    def __repr__(self):
44✔
335
        return (self.name +
22✔
336
                (("," + self.version) if self.version else "") +
337
                (("," + self.url) if self.url else "") +
338
                (" (declaration only)" if self.declaration_only else ""))
339

340

341
class RequirementsFile(object):
44✔
342
    """
343
    Represents all dependencies in a requirements file (requirements.txt).
344
    """
345

346
    def __init__(self, filename, declaration_only=False):
44✔
347
        self.name = filename
44✔
348
        self.version = None
44✔
349
        self.declaration_only = declaration_only
44✔
350

351
    def __eq__(self, other):
44✔
352
        if not isinstance(other, RequirementsFile):
44!
353
            return False
×
354
        return self.name == other.name
44✔
355

356
    def __ne__(self, other):
44✔
357
        return not (self == other)
44✔
358

359
    def __lt__(self, other):
44✔
360
        if not isinstance(other, RequirementsFile):
44✔
361
            return False
44✔
362
        return self.name < other.name
44✔
363

364
    def __hash__(self):
44✔
365
        return 42 * hash(self.name)
44✔
366

367
    def __str__(self):
44✔
368
        return self.name
44✔
369

370
    def __repr__(self):
44✔
371
        return self.name
×
372

373

374
class PluginDef:
44✔
375
    PYPI_PLUGIN_PROTOCOL = "pypi:"
44✔
376
    VCS_PLUGIN_PROTOCOL = "vcs:"
44✔
377

378
    def __init__(self, name, version=None, plugin_module_name=None):
44✔
379
        pip_package = pip_package_version = pip_package_url = None
44✔
380

381
        if name.startswith(PluginDef.PYPI_PLUGIN_PROTOCOL):
44✔
382
            pip_package = name.replace(PluginDef.PYPI_PLUGIN_PROTOCOL, "")
44✔
383
            if version:
44✔
384
                pip_package_version = str(version)
44✔
385
            plugin_module_name = plugin_module_name or pip_package
44✔
386
        elif name.startswith(PluginDef.VCS_PLUGIN_PROTOCOL):
44✔
387
            pip_package_url = name.replace(PluginDef.VCS_PLUGIN_PROTOCOL, "")
44✔
388
            if not plugin_module_name:
44!
389
                raise UnspecifiedPluginNameException(name)
×
390
            pip_package = pip_package_url
44✔
391

392
        self._dep = None
44✔
393
        if pip_package or pip_package_version or pip_package_url:
44✔
394
            self._dep = Dependency(pip_package, pip_package_version, pip_package_url)
44✔
395
        self._val = (name, version, plugin_module_name)
44✔
396

397
    @property
44✔
398
    def name(self):
44✔
399
        return self._val[0]
44✔
400

401
    @property
44✔
402
    def version(self):
44✔
403
        return self._val[1]
44✔
404

405
    @property
44✔
406
    def plugin_module_name(self):
44✔
407
        return self._val[2]
44✔
408

409
    @property
44✔
410
    def dependency(self):
44✔
411
        return self._dep
44✔
412

413
    def __repr__(self):
44✔
414
        return "PluginDef [name=%r, version=%r, plugin_module_name=%r]" % (self.name,
×
415
                                                                           self.version,
416
                                                                           self.plugin_module_name)
417

418
    def __str__(self):
44✔
419
        return "%s%s%s" % (self.name, " version %s" % self.version if self.version else "",
44✔
420
                           ", module name '%s'" % self.plugin_module_name if self.plugin_module_name else "")
421

422
    def __eq__(self, other):
44✔
423
        return isinstance(other, PluginDef) and other._val == self._val
44✔
424

425
    def __hash__(self):
44✔
426
        return self._val.__hash__()
44✔
427

428

429
class Project(object):
44✔
430
    """
431
    Descriptor for a project to be built. A project has a number of attributes
432
    as well as some convenience methods to access these properties.
433
    """
434

435
    def __init__(self, basedir, version="1.0.dev0", name=None, offline=False, no_venvs=False):
44✔
436
        self.name = name
44✔
437
        self._version = None
44✔
438
        self._dist_version = None
44✔
439
        self.offline = offline
44✔
440
        self.no_venvs = no_venvs
44✔
441
        self.version = version
44✔
442
        self.basedir = ap(basedir)
44✔
443
        if not self.name:
44✔
444
            self.name = basename(basedir)
44✔
445

446
        self.default_task = None
44✔
447

448
        self.summary = ""
44✔
449
        self.description = ""
44✔
450

451
        self.author = ""
44✔
452
        self.authors = []
44✔
453
        self.maintainer = ""
44✔
454
        self.maintainers = []
44✔
455

456
        self.license = ""
44✔
457
        self.url = ""
44✔
458
        self.urls = {}
44✔
459

460
        self._requires_python = ""
44✔
461
        self._obsoletes = []
44✔
462
        self._explicit_namespaces = []
44✔
463
        self._properties = {"verbose": False}
44✔
464
        self._install_dependencies = set()
44✔
465
        self._build_dependencies = set()
44✔
466
        self._plugin_dependencies = set()
44✔
467
        self._manifest_included_files = []
44✔
468
        self._manifest_included_directories = []
44✔
469
        self._package_data = OrderedDict()
44✔
470
        self._files_to_install = []
44✔
471
        self._preinstall_script = None
44✔
472
        self._postinstall_script = None
44✔
473
        self._environments = ()
44✔
474

475
    def __str__(self):
44✔
476
        return "[Project name=%s basedir=%s]" % (self.name, self.basedir)
×
477

478
    @property
44✔
479
    def version(self):
44✔
480
        return self._version
44✔
481

482
    @version.setter
44✔
483
    def version(self, value):
44✔
484
        self._version = value
44✔
485
        if value.endswith('.dev'):
44!
486
            value += datetime.now(UTC).strftime("%Y%m%d%H%M%S")
×
487
        self._dist_version = value
44✔
488

489
    @property
44✔
490
    def requires_python(self):
44✔
491
        return self._requires_python
44✔
492

493
    @requires_python.setter
44✔
494
    def requires_python(self, value):
44✔
495
        from pybuilder import pip_common
22✔
496
        spec_set = pip_common.SpecifierSet(value)
22✔
497
        self._requires_python = str(spec_set)
22✔
498

499
    @property
44✔
500
    def obsoletes(self):
44✔
501
        return self._obsoletes
44✔
502

503
    @obsoletes.setter
44✔
504
    def obsoletes(self, value):
44✔
505
        self._obsoletes = as_list(value)
×
506

507
    @property
44✔
508
    def explicit_namespaces(self):
44✔
509
        return self._explicit_namespaces
44✔
510

511
    @explicit_namespaces.setter
44✔
512
    def explicit_namespaces(self, value):
44✔
513
        self._explicit_namespaces = as_list(value)
44✔
514

515
    @property
44✔
516
    def dist_version(self):
44✔
517
        return self._dist_version
44✔
518

519
    def validate(self):
44✔
520
        """
521
        Validates the project returning a list of validation error messages if the project is not valid.
522
        Returns an empty list if the project is valid.
523
        """
524
        result = self.validate_dependencies()
44✔
525

526
        return result
44✔
527

528
    def validate_dependencies(self):
44✔
529
        result = []
44✔
530

531
        build_dependencies_found = {}
44✔
532

533
        for dependency in self.build_dependencies:
44✔
534
            if dependency.name in build_dependencies_found:
44✔
535
                if build_dependencies_found[dependency.name] == 1:
44✔
536
                    result.append("Build dependency '%s' has been defined multiple times." % dependency.name)
44✔
537
                build_dependencies_found[dependency.name] += 1
44✔
538
            else:
539
                build_dependencies_found[dependency.name] = 1
44✔
540

541
        runtime_dependencies_found = {}
44✔
542

543
        for dependency in self.dependencies:
44✔
544
            if dependency.name in runtime_dependencies_found:
44✔
545
                if runtime_dependencies_found[dependency.name] == 1:
44✔
546
                    result.append("Runtime dependency '%s' has been defined multiple times." % dependency.name)
44✔
547
                runtime_dependencies_found[dependency.name] += 1
44✔
548
            else:
549
                runtime_dependencies_found[dependency.name] = 1
44✔
550
            if dependency.name in build_dependencies_found:
44✔
551
                result.append("Runtime dependency '%s' has also been given as build dependency." % dependency.name)
44✔
552

553
        return result
44✔
554

555
    @property
44✔
556
    def properties(self):
44✔
557
        result = self._properties
44✔
558
        result["basedir"] = self.basedir
44✔
559
        return result
44✔
560

561
    @property
44✔
562
    def dependencies(self):
44✔
563
        return list(sorted(self._install_dependencies))
44✔
564

565
    @property
44✔
566
    def build_dependencies(self):
44✔
567
        return list(sorted(self._build_dependencies))
44✔
568

569
    @property
44✔
570
    def plugin_dependencies(self):
44✔
571
        return list(sorted(self._plugin_dependencies))
44✔
572

573
    def depends_on(self, name, version=None, url=None, declaration_only=False, eager_update=None):
44✔
574
        self._install_dependencies.add(Dependency(name, version, url, declaration_only, eager_update=eager_update))
44✔
575

576
    def build_depends_on(self, name, version=None, url=None, declaration_only=False, eager_update=None):
44✔
577
        self._build_dependencies.add(Dependency(name, version, url, declaration_only, eager_update=eager_update))
44✔
578

579
    def depends_on_requirements(self, file, declaration_only=False):
44✔
580
        self._install_dependencies.add(RequirementsFile(file, declaration_only=declaration_only))
44✔
581

582
    def build_depends_on_requirements(self, file):
44✔
583
        self._build_dependencies.add(RequirementsFile(file))
44✔
584

585
    def plugin_depends_on(self, name, version=None, url=None, declaration_only=False, eager_update=None):
44✔
586
        self._plugin_dependencies.add(Dependency(name, version, url, declaration_only, eager_update=eager_update))
44✔
587

588
    @property
44✔
589
    def environments(self):
44✔
590
        return self._environments
44✔
591

592
    @property
44✔
593
    def setup_preinstall_script(self):
44✔
594
        return self._preinstall_script
44✔
595

596
    def pre_install_script(self, script):
44✔
597
        self._preinstall_script = script
44✔
598

599
    @property
44✔
600
    def setup_postinstall_script(self):
44✔
601
        return self._postinstall_script
44✔
602

603
    def post_install_script(self, script):
44✔
604
        self._postinstall_script = script
44✔
605

606
    @property
44✔
607
    def manifest_included_files(self):
44✔
608
        return self._manifest_included_files
44✔
609

610
    @property
44✔
611
    def manifest_included_directories(self):
44✔
612
        return self._manifest_included_directories
44✔
613

614
    def _manifest_include(self, glob_pattern):
44✔
615
        if not glob_pattern or glob_pattern.strip() == "":
44✔
616
            raise ValueError("Missing glob_pattern argument.")
44✔
617

618
        self._manifest_included_files.append(glob_pattern)
44✔
619

620
    def _manifest_include_directory(self, directory, patterns_list):
44✔
621
        if not directory or directory.strip() == "":
44✔
622
            raise ValueError("Missing directory argument.")
44✔
623

624
        patterns_list = map(lambda s: s.strip(), patterns_list)
44✔
625
        patterns_list = tuple(filter(bool, patterns_list))
44✔
626
        if len(patterns_list) == 0:
44✔
627
            raise ValueError("Missing patterns_list argument.")
44✔
628

629
        directory_to_include = (directory, patterns_list)
44✔
630
        self._manifest_included_directories.append(directory_to_include)
44✔
631

632
    @property
44✔
633
    def package_data(self):
44✔
634
        return self._package_data
44✔
635

636
    def include_file(self, package_name, filename):
44✔
637
        package_name = package_name or ""
44✔
638

639
        if not filename or filename.strip() == "":
44✔
640
            raise ValueError("Missing argument filename.")
44✔
641

642
        full_filename = np(jp(package_name.replace(".", sep), filename))
44✔
643
        self._manifest_include(full_filename)
44✔
644

645
        self._add_package_data(package_name, filename)
44✔
646

647
    def include_directory(self, package_path, patterns_list, package_root=""):
44✔
648
        if not package_path or package_path.strip() == "":
44✔
649
            raise ValueError("Missing argument package_path.")
44✔
650

651
        if not patterns_list:
44✔
652
            raise ValueError("Missing argument patterns_list.")
44✔
653
        patterns_list = as_list(patterns_list)
44✔
654

655
        package_name = PATH_SEP_RE.sub(".", package_path)
44✔
656
        self._manifest_include_directory(package_path, patterns_list)
44✔
657

658
        package_full_path = self.expand_path(package_root, package_path)
44✔
659

660
        for root, dirnames, filenames in os.walk(package_full_path):
44✔
661
            filenames = list(fnmatch.filter(filenames, pattern) for pattern in patterns_list)
44✔
662

663
            for filename in itertools.chain.from_iterable(filenames):
44✔
664
                full_path = np(jp(root, filename))
44✔
665
                relative_path = relpath(full_path, package_full_path)
44✔
666
                self._add_package_data(package_name, relative_path)
44✔
667

668
    def _add_package_data(self, package_name, filename):
44✔
669
        filename = filename.replace("\\", "/")
44✔
670
        self._package_data.setdefault(package_name, []).append(filename)
44✔
671

672
    @property
44✔
673
    def files_to_install(self):
44✔
674
        return self._files_to_install
44✔
675

676
    def install_file(self, destination, filename):
44✔
677
        if not destination:
44✔
678
            raise ValueError("Missing argument destination")
44✔
679

680
        if not filename or filename.strip() == "":
44✔
681
            raise ValueError("Missing argument filename")
44✔
682

683
        current_tuple = None
44✔
684
        for installation_tuple in self.files_to_install:
44✔
685
            destination_name = installation_tuple[0]
44✔
686

687
            if destination_name == destination:
44✔
688
                current_tuple = installation_tuple
44✔
689

690
        if current_tuple:
44✔
691
            list_of_files_within_tuple = current_tuple[1]
44✔
692
            list_of_files_within_tuple.append(filename)
44✔
693
        else:
694
            initial_tuple = (destination, [filename])
44✔
695
            self.files_to_install.append(initial_tuple)
44✔
696

697
        self._manifest_include(filename)
44✔
698

699
    def expand(self, format_string):
44✔
700
        previous = None
44✔
701
        result = format_string
44✔
702
        while previous != result:
44✔
703
            try:
44✔
704
                previous = result
44✔
705
                result = string.Template(result).substitute(self.properties)
44✔
706
            except KeyError as e:
44✔
707
                raise MissingPropertyException(e)
44✔
708
        return result
44✔
709

710
    def expand_path(self, format_string, *additional_path_elements):
44✔
711
        elements = [self.basedir]
44✔
712
        elements += list(PATH_SEP_RE.split(self.expand(format_string)))
44✔
713
        elements += list(additional_path_elements)
44✔
714
        return np(jp(*elements))
44✔
715

716
    def get_property(self, key, default_value=None):
44✔
717
        return self.properties.get(key, default_value)
44✔
718

719
    def get_mandatory_property(self, key):
44✔
720
        if not self.has_property(key):
44✔
721
            raise MissingPropertyException(key)
44✔
722
        return self.get_property(key)
44✔
723

724
    def has_property(self, key):
44✔
725
        return key in self.properties
44✔
726

727
    def set_property(self, key, value):
44✔
728
        self.properties[key] = value
44✔
729

730
    def set_property_if_unset(self, key, value):
44✔
731
        if not self.has_property(key):
44✔
732
            self.set_property(key, value)
44✔
733

734

735
class Logger(logging.Handler):
44✔
736
    CRITICAL = 50
44✔
737
    FATAL = CRITICAL
44✔
738
    ERROR = 40
44✔
739
    WARNING = 30
44✔
740
    WARN = WARNING
44✔
741
    INFO = 20
44✔
742
    DEBUG = 10
44✔
743

744
    def __init__(self, level=INFO, log_time_format=None):
44✔
745
        super(Logger, self).__init__(level)
44✔
746
        self.log_time_format = log_time_format
44✔
747

748
    def emit(self, record):
44✔
749
        self._do_log(record.levelno, record.getMessage())
×
750

751
    def _do_log(self, level, message, *arguments):
44✔
752
        pass
×
753

754
    @staticmethod
44✔
755
    def _format_message(message, *arguments):
44✔
756
        if arguments:
44✔
757
            return message % arguments
44✔
758
        return message
44✔
759

760
    def log(self, level, message, *arguments):
44✔
761
        if level >= self.level:
44✔
762
            self._do_log(level, message, *arguments)
44✔
763

764
    def debug(self, message, *arguments):
44✔
765
        self.log(Logger.DEBUG, message, *arguments)
44✔
766

767
    def info(self, message, *arguments):
44✔
768
        self.log(Logger.INFO, message, *arguments)
44✔
769

770
    def warn(self, message, *arguments):
44✔
771
        self.log(Logger.WARN, message, *arguments)
44✔
772

773
    def error(self, message, *arguments):
44✔
774
        self.log(Logger.ERROR, message, *arguments)
44✔
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

© 2025 Coveralls, Inc