Skip to content

Config

sereto.config

Config dataclass

Source code in sereto/config.py
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
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
515
516
@dataclass
class Config:
    sereto_version: SeretoVersion
    version_configs: dict[ProjectVersion, VersionConfig]
    path: FilePath

    @classmethod
    @validate_call
    def load_from(cls, path: FilePath) -> Self:
        config = ConfigModel.load_from(path)

        return cls(
            sereto_version=config.sereto_version,
            version_configs={
                version: VersionConfig.from_model(model=version_config, version=version, project_path=path.parent)
                for version, version_config in config.version_configs.items()
            },
            path=path,
        )

    def to_model(self) -> ConfigModel:
        return ConfigModel(
            sereto_version=self.sereto_version,
            version_configs={version: config.to_model() for version, config in self.version_configs.items()},
        )

    def save(self) -> None:
        self.path.write_text(self.to_model().model_dump_json(indent=2) + "\n")

    @property
    def versions(self) -> list[ProjectVersion]:
        """Get a sorted list of project versions in ascending order."""
        return sorted(list(self.version_configs.keys()))

    @validate_call
    def at_version(self, version: str | ProjectVersion) -> VersionConfig:
        """Return the configuration at a specific version.

        Args:
            version: Selects which version of the configuration should be returned.

        Returns:
            Configuration for the project at the specified version.

        Raises:
            SeretoValueError: If the specified version is unknown.
        """
        if isinstance(version, str):
            version = ProjectVersion.from_str(version)

        if version not in self.versions:
            raise SeretoValueError(f"version '{version}' not found")

        return self.version_configs[version]

    @property
    def first_version(self) -> ProjectVersion:
        """Get the first version present in the configuration."""
        return self.versions[0]

    @property
    def last_version(self) -> ProjectVersion:
        """Get the last version present in the configuration."""
        return self.versions[-1]

    @property
    def first_config(self) -> VersionConfig:
        """Get the configuration for the first project version."""
        return self.at_version(self.first_version)

    @property
    def last_config(self) -> VersionConfig:
        """Get the configuration for the last project version."""
        return self.at_version(self.last_version)

    @validate_call
    def add_version_config(self, version: ProjectVersion, config: VersionConfigModel) -> Self:
        """Add a new version configuration to the project.

        Args:
            version: The version of the new configuration.
            config: The configuration to add.

        Returns:
            The updated configuration.

        Raises:
            SeretoValueError: If the specified version already exists.
        """
        if version in self.versions:
            raise SeretoValueError(f"version '{version}' already exists")

        self.version_configs[version] = VersionConfig.from_model(
            model=config, version=version, project_path=self.path.parent
        )

        return self

first_config property

Get the configuration for the first project version.

first_version property

Get the first version present in the configuration.

last_config property

Get the configuration for the last project version.

last_version property

Get the last version present in the configuration.

versions property

Get a sorted list of project versions in ascending order.

add_version_config(version, config)

Add a new version configuration to the project.

Parameters:

Name Type Description Default
version ProjectVersion

The version of the new configuration.

required
config VersionConfigModel

The configuration to add.

required

Returns:

Type Description
Self

The updated configuration.

Raises:

Type Description
SeretoValueError

If the specified version already exists.

Source code in sereto/config.py
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
@validate_call
def add_version_config(self, version: ProjectVersion, config: VersionConfigModel) -> Self:
    """Add a new version configuration to the project.

    Args:
        version: The version of the new configuration.
        config: The configuration to add.

    Returns:
        The updated configuration.

    Raises:
        SeretoValueError: If the specified version already exists.
    """
    if version in self.versions:
        raise SeretoValueError(f"version '{version}' already exists")

    self.version_configs[version] = VersionConfig.from_model(
        model=config, version=version, project_path=self.path.parent
    )

    return self

at_version(version)

Return the configuration at a specific version.

Parameters:

Name Type Description Default
version str | ProjectVersion

Selects which version of the configuration should be returned.

required

Returns:

Type Description
VersionConfig

Configuration for the project at the specified version.

Raises:

Type Description
SeretoValueError

If the specified version is unknown.

Source code in sereto/config.py
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
@validate_call
def at_version(self, version: str | ProjectVersion) -> VersionConfig:
    """Return the configuration at a specific version.

    Args:
        version: Selects which version of the configuration should be returned.

    Returns:
        Configuration for the project at the specified version.

    Raises:
        SeretoValueError: If the specified version is unknown.
    """
    if isinstance(version, str):
        version = ProjectVersion.from_str(version)

    if version not in self.versions:
        raise SeretoValueError(f"version '{version}' not found")

    return self.version_configs[version]

VersionConfig dataclass

Source code in sereto/config.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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
236
237
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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
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
382
383
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
@dataclass
class VersionConfig:
    version: ProjectVersion
    id: str
    name: str
    version_description: str
    targets: list[Target] = field(default_factory=list)
    dates: list[Date] = field(default_factory=list)
    people: list[Person] = field(default_factory=list)

    @validate_call
    def to_model(self) -> VersionConfigModel:
        return VersionConfigModel(
            id=self.id,
            name=self.name,
            version_description=self.version_description,
            targets=[target.to_model() for target in self.targets],
            dates=self.dates,
            people=self.people,
        )

    @classmethod
    @validate_call
    def from_model(
        cls,
        model: VersionConfigModel,
        version: ProjectVersion,
        project_path: DirectoryPath,
    ) -> Self:
        return cls(
            version=version,
            id=model.id,
            name=model.name,
            version_description=model.version_description,
            targets=[
                Target.load(data=target, path=project_path / (target.uname + version.path_suffix), version=version)
                for target in model.targets
            ],
            dates=model.dates,
            people=model.people,
        )

    @validate_call
    def filter_targets(
        self,
        category: str | Iterable[str] | None = None,
        name: str | None = None,
        inverse: bool = False,
    ) -> list[TargetModel]:
        """Filter targets based on specified criteria.

        The regular expressions support the syntax of Python's `re` module.

        Args:
            category: The category of the target. Can be a single category, a list of categories, or None.
            name: Regular expression to match the name of the target.
            inverse: If True, return the inverse of the usual results.

        Returns:
            A list of targets matching the criteria.
        """
        if isinstance(category, str):
            category = [category]

        filtered_targets = [
            t
            for t in self.targets
            if (category is None or t.data.category in category) and (name is None or re.search(name, t.data.name))
        ]

        if inverse:
            return [t.data for t in self.targets if t not in filtered_targets]
        return [t.data for t in filtered_targets]

    @validate_call
    def select_target(
        self,
        categories: Iterable[str],
        selector: int | str | None = None,
    ) -> Target:
        # only single target present
        if selector is None:
            if len(self.targets) != 1:
                raise SeretoValueError(
                    f"cannot select target; no selector provided and there are {len(self.targets)} targets present"
                )
            return self.targets[0]

        # by index
        if isinstance(selector, int) or selector.isnumeric():
            ix = selector - 1 if isinstance(selector, int) else int(selector) - 1
            if not (0 <= ix <= len(self.targets) - 1):
                raise SeretoValueError("target index out of range")

            return self.targets[ix]

        # by unique category
        if selector in categories:
            filtered_targets = [t for t in self.targets if t.data.category == selector]
            match len(filtered_targets):
                case 0:
                    raise SeretoValueError(f"category {selector!r} does not contain any target")
                case 1:
                    return filtered_targets[0]
                case _:
                    raise SeretoValueError(
                        f"category {selector!r} contains multiple targets, use unique name when querying"
                    )

        # by uname
        filtered_targets = [t for t in self.targets if t.uname == selector]
        if len(filtered_targets) != 1:
            raise SeretoValueError(f"target with uname {selector!r} not found")
        return filtered_targets[0]

    @validate_call
    @overload
    def filter_dates(
        self,
        type: str | DateType | Iterable[str] | Iterable[DateType] | None = ...,
        start: str | SeretoDate | None = ...,
        end: str | SeretoDate | None = ...,
        *,
        first_date: Literal[True],
        last_date: Literal[False] = False,
        inverse: bool = False,
    ) -> SeretoDate | None: ...

    @overload
    def filter_dates(
        self,
        type: str | DateType | Iterable[str] | Iterable[DateType] | None = ...,
        start: str | SeretoDate | None = ...,
        end: str | SeretoDate | None = ...,
        *,
        first_date: Literal[False] = False,
        last_date: Literal[True],
        inverse: bool = False,
    ) -> SeretoDate | None: ...

    @overload
    def filter_dates(
        self,
        type: str | DateType | Iterable[str] | Iterable[DateType] | None = ...,
        start: str | SeretoDate | None = ...,
        end: str | SeretoDate | None = ...,
        *,
        first_date: Literal[False],
        last_date: Literal[False],
        inverse: bool = False,
    ) -> list[Date]: ...

    def filter_dates(
        self,
        type: str | DateType | Iterable[str] | Iterable[DateType] | None = None,
        start: str | SeretoDate | None = None,
        end: str | SeretoDate | None = None,
        *,
        first_date: bool = False,
        last_date: bool = False,
        inverse: bool = False,
    ) -> list[Date] | SeretoDate | None:
        """Filter dates based on specified criteria.

        The start and end dates are inclusive. For date ranges, a date is considered matching if it completely overlaps
        with the specified range.

        Args:
            type: The type of the date. Can be `DateType`, a list of `DateType`s, or None.
            start: Only dates on or after this date will be included.
            end: Only dates on or before this date will be included.
            first_date: If True, return the earliest date matching the criteria. Even for date ranges, only the start
                date  is considered. The type returned is `SeretoDate` or None.
            last_date: If True, return the latest date matching the criteria. Even for date ranges, only the end date
                is considered. The type returned is `SeretoDate` or None.
            inverse: If True, return the inverse of the usual results.

        Returns:
            For first_date or last_date = True, returns SeretoDate or None. Otherwise, returns a list[Date].
        """
        # Check for invalid parameter combinations
        if first_date and last_date:
            raise SeretoValueError("cannot specify both first_date and last_date")
        if (first_date or last_date) and inverse:
            raise SeretoValueError("cannot specify inverse with first_date or last_date")

        match type:
            case str():
                type = [DateType(type)]
            case Iterable():
                type = [DateType(t) for t in type]
            case None:
                pass

        if isinstance(start, str):
            start = SeretoDate.from_str(start)
        if isinstance(end, str):
            end = SeretoDate.from_str(end)

        filtered_dates = [
            d
            for d in self.dates
            if (type is None or d.type in type)
            and (
                start is None
                or (isinstance(d.date, SeretoDate) and d.date >= start)
                or (isinstance(d.date, DateRange) and d.date.start >= start)
            )
            and (
                end is None
                or (isinstance(d.date, SeretoDate) and d.date <= end)
                or (isinstance(d.date, DateRange) and d.date.end <= end)
            )
        ]

        if first_date:
            single_dates = [d.date.start if isinstance(d.date, DateRange) else d.date for d in filtered_dates]
            return min(single_dates, default=None)

        if last_date:
            single_dates = [d.date.end if isinstance(d.date, DateRange) else d.date for d in filtered_dates]
            return max(single_dates, default=None)

        if inverse:
            return [d for d in self.dates if d not in filtered_dates]
        return filtered_dates

    @validate_call
    def filter_people(
        self,
        type: str | PersonType | Iterable[str] | Iterable[PersonType] | None = None,
        name: str | None = None,
        business_unit: str | None = None,
        email: str | None = None,
        role: str | None = None,
        inverse: bool = False,
    ) -> list[Person]:
        """Filter people based on specified criteria.

        The regular expressions support the syntax of Python's `re` module.

        Args:
            type: The type of the person. Can be a single type, a list of types, or None.
            name: Regular expression to match the name of the person.
            business_unit: Regular expression to match the business unit of the person.
            email: Regular expression to match the email of the person.
            role: Regular expression to match the role of the person.
            inverse: If True, return the inverse of the usual results.

        Returns:
            A list of people matching the criteria.
        """
        match type:
            case str():
                type = [PersonType(type)]
            case Iterable():
                type = [PersonType(t) for t in type]
            case None:
                pass

        filtered_people = [
            p
            for p in self.people
            if (type is None or p.type in type)
            and (name is None or (p.name is not None and re.search(name, p.name)))
            and (business_unit is None or (p.business_unit is not None and re.search(business_unit, p.business_unit)))
            and (email is None or (p.email is not None and re.search(email, p.email)))
            and (role is None or (p.role is not None and re.search(role, p.role)))
        ]

        if inverse:
            return [p for p in self.people if p not in filtered_people]
        return filtered_people

    @validate_call
    def add_target(self, target: Target) -> Self:
        """Add a target to the configuration.

        Args:
            target: The target to add.

        Returns:
            The configuration with the added target.
        """
        self.targets.append(target)
        return self

    @validate_call
    def delete_target(self, index: int) -> Self:
        """Delete a target from the configuration.

        Args:
            index: The index of the target to delete. First item is 1.

        Returns:
            The configuration with the target deleted.
        """
        # Convert to 0-based index
        index -= 1

        # Check if the index is in the allowed range
        if not 0 <= index <= len(self.targets) - 1:
            raise SeretoValueError("index out of range")

        # Delete the target
        del self.targets[index]

        return self

    @validate_call
    def add_date(self, date: Date) -> Self:
        """Add a date to the configuration.

        Args:
            date: The date to add.

        Returns:
            The configuration with the added date.
        """
        self.dates.append(date)
        return self

    @validate_call
    def delete_date(self, index: int) -> Self:
        """Delete a date from the configuration.

        Args:
            index: The index of the date to delete. First item is 1.

        Returns:
            The configuration with the date deleted.
        """
        # Convert to 0-based index
        index -= 1

        # Check if the index is in the allowed range
        if not 0 <= index <= len(self.dates) - 1:
            raise SeretoValueError("index out of range")

        # Delete the date
        del self.dates[index]

        return self

    @validate_call
    def add_person(self, person: Person) -> Self:
        """Add a person to the configuration.

        Args:
            person: The person to add.

        Returns:
            The configuration with the added person.
        """
        self.people.append(person)
        return self

    @validate_call
    def delete_person(self, index: int) -> Self:
        """Delete a person from the configuration.

        Args:
            index: The index of the person to delete. First item is 1.

        Returns:
            The configuration with the person deleted.
        """
        # Convert to 0-based index
        index -= 1

        # Check if the index is in the allowed range
        if not 0 <= index <= len(self.people) - 1:
            raise SeretoValueError("index out of range")

        # Delete the person
        del self.people[index]

        return self

    @property
    def report_sent_date(self) -> SeretoDate | None:
        """Get the report sent date

        It has fallback to the review date and last date of the pentest ongoing.
        """
        return self.filter_dates(
            type=[DateType.report_sent, DateType.review, DateType.pentest_ongoing], last_date=True
        )

    @property
    def total_open_risks(self) -> NonNegativeInt:
        """Get the total number of open risks across all risk levels."""
        return sum(t.findings.risks.sum_open for t in self.targets)

    @property
    def sum_risks(self) -> Risks:
        """Get the sum of risks across all targets."""
        return reduce(operator.add, (t.findings.risks for t in self.targets))

report_sent_date property

Get the report sent date

It has fallback to the review date and last date of the pentest ongoing.

sum_risks property

Get the sum of risks across all targets.

total_open_risks property

Get the total number of open risks across all risk levels.

add_date(date)

Add a date to the configuration.

Parameters:

Name Type Description Default
date Date

The date to add.

required

Returns:

Type Description
Self

The configuration with the added date.

Source code in sereto/config.py
329
330
331
332
333
334
335
336
337
338
339
340
@validate_call
def add_date(self, date: Date) -> Self:
    """Add a date to the configuration.

    Args:
        date: The date to add.

    Returns:
        The configuration with the added date.
    """
    self.dates.append(date)
    return self

add_person(person)

Add a person to the configuration.

Parameters:

Name Type Description Default
person Person

The person to add.

required

Returns:

Type Description
Self

The configuration with the added person.

Source code in sereto/config.py
364
365
366
367
368
369
370
371
372
373
374
375
@validate_call
def add_person(self, person: Person) -> Self:
    """Add a person to the configuration.

    Args:
        person: The person to add.

    Returns:
        The configuration with the added person.
    """
    self.people.append(person)
    return self

add_target(target)

Add a target to the configuration.

Parameters:

Name Type Description Default
target Target

The target to add.

required

Returns:

Type Description
Self

The configuration with the added target.

Source code in sereto/config.py
294
295
296
297
298
299
300
301
302
303
304
305
@validate_call
def add_target(self, target: Target) -> Self:
    """Add a target to the configuration.

    Args:
        target: The target to add.

    Returns:
        The configuration with the added target.
    """
    self.targets.append(target)
    return self

delete_date(index)

Delete a date from the configuration.

Parameters:

Name Type Description Default
index int

The index of the date to delete. First item is 1.

required

Returns:

Type Description
Self

The configuration with the date deleted.

Source code in sereto/config.py
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
@validate_call
def delete_date(self, index: int) -> Self:
    """Delete a date from the configuration.

    Args:
        index: The index of the date to delete. First item is 1.

    Returns:
        The configuration with the date deleted.
    """
    # Convert to 0-based index
    index -= 1

    # Check if the index is in the allowed range
    if not 0 <= index <= len(self.dates) - 1:
        raise SeretoValueError("index out of range")

    # Delete the date
    del self.dates[index]

    return self

delete_person(index)

Delete a person from the configuration.

Parameters:

Name Type Description Default
index int

The index of the person to delete. First item is 1.

required

Returns:

Type Description
Self

The configuration with the person deleted.

Source code in sereto/config.py
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
@validate_call
def delete_person(self, index: int) -> Self:
    """Delete a person from the configuration.

    Args:
        index: The index of the person to delete. First item is 1.

    Returns:
        The configuration with the person deleted.
    """
    # Convert to 0-based index
    index -= 1

    # Check if the index is in the allowed range
    if not 0 <= index <= len(self.people) - 1:
        raise SeretoValueError("index out of range")

    # Delete the person
    del self.people[index]

    return self

delete_target(index)

Delete a target from the configuration.

Parameters:

Name Type Description Default
index int

The index of the target to delete. First item is 1.

required

Returns:

Type Description
Self

The configuration with the target deleted.

Source code in sereto/config.py
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
@validate_call
def delete_target(self, index: int) -> Self:
    """Delete a target from the configuration.

    Args:
        index: The index of the target to delete. First item is 1.

    Returns:
        The configuration with the target deleted.
    """
    # Convert to 0-based index
    index -= 1

    # Check if the index is in the allowed range
    if not 0 <= index <= len(self.targets) - 1:
        raise SeretoValueError("index out of range")

    # Delete the target
    del self.targets[index]

    return self

filter_dates(type=None, start=None, end=None, *, first_date=False, last_date=False, inverse=False)

filter_dates(type: str | DateType | Iterable[str] | Iterable[DateType] | None = ..., start: str | SeretoDate | None = ..., end: str | SeretoDate | None = ..., *, first_date: Literal[True], last_date: Literal[False] = False, inverse: bool = False) -> SeretoDate | None
filter_dates(type: str | DateType | Iterable[str] | Iterable[DateType] | None = ..., start: str | SeretoDate | None = ..., end: str | SeretoDate | None = ..., *, first_date: Literal[False] = False, last_date: Literal[True], inverse: bool = False) -> SeretoDate | None
filter_dates(type: str | DateType | Iterable[str] | Iterable[DateType] | None = ..., start: str | SeretoDate | None = ..., end: str | SeretoDate | None = ..., *, first_date: Literal[False], last_date: Literal[False], inverse: bool = False) -> list[Date]

Filter dates based on specified criteria.

The start and end dates are inclusive. For date ranges, a date is considered matching if it completely overlaps with the specified range.

Parameters:

Name Type Description Default
type str | DateType | Iterable[str] | Iterable[DateType] | None

The type of the date. Can be DateType, a list of DateTypes, or None.

None
start str | SeretoDate | None

Only dates on or after this date will be included.

None
end str | SeretoDate | None

Only dates on or before this date will be included.

None
first_date bool

If True, return the earliest date matching the criteria. Even for date ranges, only the start date is considered. The type returned is SeretoDate or None.

False
last_date bool

If True, return the latest date matching the criteria. Even for date ranges, only the end date is considered. The type returned is SeretoDate or None.

False
inverse bool

If True, return the inverse of the usual results.

False

Returns:

Type Description
list[Date] | SeretoDate | None

For first_date or last_date = True, returns SeretoDate or None. Otherwise, returns a list[Date].

Source code in sereto/config.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
236
237
238
239
240
241
242
243
244
245
def filter_dates(
    self,
    type: str | DateType | Iterable[str] | Iterable[DateType] | None = None,
    start: str | SeretoDate | None = None,
    end: str | SeretoDate | None = None,
    *,
    first_date: bool = False,
    last_date: bool = False,
    inverse: bool = False,
) -> list[Date] | SeretoDate | None:
    """Filter dates based on specified criteria.

    The start and end dates are inclusive. For date ranges, a date is considered matching if it completely overlaps
    with the specified range.

    Args:
        type: The type of the date. Can be `DateType`, a list of `DateType`s, or None.
        start: Only dates on or after this date will be included.
        end: Only dates on or before this date will be included.
        first_date: If True, return the earliest date matching the criteria. Even for date ranges, only the start
            date  is considered. The type returned is `SeretoDate` or None.
        last_date: If True, return the latest date matching the criteria. Even for date ranges, only the end date
            is considered. The type returned is `SeretoDate` or None.
        inverse: If True, return the inverse of the usual results.

    Returns:
        For first_date or last_date = True, returns SeretoDate or None. Otherwise, returns a list[Date].
    """
    # Check for invalid parameter combinations
    if first_date and last_date:
        raise SeretoValueError("cannot specify both first_date and last_date")
    if (first_date or last_date) and inverse:
        raise SeretoValueError("cannot specify inverse with first_date or last_date")

    match type:
        case str():
            type = [DateType(type)]
        case Iterable():
            type = [DateType(t) for t in type]
        case None:
            pass

    if isinstance(start, str):
        start = SeretoDate.from_str(start)
    if isinstance(end, str):
        end = SeretoDate.from_str(end)

    filtered_dates = [
        d
        for d in self.dates
        if (type is None or d.type in type)
        and (
            start is None
            or (isinstance(d.date, SeretoDate) and d.date >= start)
            or (isinstance(d.date, DateRange) and d.date.start >= start)
        )
        and (
            end is None
            or (isinstance(d.date, SeretoDate) and d.date <= end)
            or (isinstance(d.date, DateRange) and d.date.end <= end)
        )
    ]

    if first_date:
        single_dates = [d.date.start if isinstance(d.date, DateRange) else d.date for d in filtered_dates]
        return min(single_dates, default=None)

    if last_date:
        single_dates = [d.date.end if isinstance(d.date, DateRange) else d.date for d in filtered_dates]
        return max(single_dates, default=None)

    if inverse:
        return [d for d in self.dates if d not in filtered_dates]
    return filtered_dates

filter_people(type=None, name=None, business_unit=None, email=None, role=None, inverse=False)

Filter people based on specified criteria.

The regular expressions support the syntax of Python's re module.

Parameters:

Name Type Description Default
type str | PersonType | Iterable[str] | Iterable[PersonType] | None

The type of the person. Can be a single type, a list of types, or None.

None
name str | None

Regular expression to match the name of the person.

None
business_unit str | None

Regular expression to match the business unit of the person.

None
email str | None

Regular expression to match the email of the person.

None
role str | None

Regular expression to match the role of the person.

None
inverse bool

If True, return the inverse of the usual results.

False

Returns:

Type Description
list[Person]

A list of people matching the criteria.

Source code in sereto/config.py
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
@validate_call
def filter_people(
    self,
    type: str | PersonType | Iterable[str] | Iterable[PersonType] | None = None,
    name: str | None = None,
    business_unit: str | None = None,
    email: str | None = None,
    role: str | None = None,
    inverse: bool = False,
) -> list[Person]:
    """Filter people based on specified criteria.

    The regular expressions support the syntax of Python's `re` module.

    Args:
        type: The type of the person. Can be a single type, a list of types, or None.
        name: Regular expression to match the name of the person.
        business_unit: Regular expression to match the business unit of the person.
        email: Regular expression to match the email of the person.
        role: Regular expression to match the role of the person.
        inverse: If True, return the inverse of the usual results.

    Returns:
        A list of people matching the criteria.
    """
    match type:
        case str():
            type = [PersonType(type)]
        case Iterable():
            type = [PersonType(t) for t in type]
        case None:
            pass

    filtered_people = [
        p
        for p in self.people
        if (type is None or p.type in type)
        and (name is None or (p.name is not None and re.search(name, p.name)))
        and (business_unit is None or (p.business_unit is not None and re.search(business_unit, p.business_unit)))
        and (email is None or (p.email is not None and re.search(email, p.email)))
        and (role is None or (p.role is not None and re.search(role, p.role)))
    ]

    if inverse:
        return [p for p in self.people if p not in filtered_people]
    return filtered_people

filter_targets(category=None, name=None, inverse=False)

Filter targets based on specified criteria.

The regular expressions support the syntax of Python's re module.

Parameters:

Name Type Description Default
category str | Iterable[str] | None

The category of the target. Can be a single category, a list of categories, or None.

None
name str | None

Regular expression to match the name of the target.

None
inverse bool

If True, return the inverse of the usual results.

False

Returns:

Type Description
list[TargetModel]

A list of targets matching the criteria.

Source code in sereto/config.py
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
@validate_call
def filter_targets(
    self,
    category: str | Iterable[str] | None = None,
    name: str | None = None,
    inverse: bool = False,
) -> list[TargetModel]:
    """Filter targets based on specified criteria.

    The regular expressions support the syntax of Python's `re` module.

    Args:
        category: The category of the target. Can be a single category, a list of categories, or None.
        name: Regular expression to match the name of the target.
        inverse: If True, return the inverse of the usual results.

    Returns:
        A list of targets matching the criteria.
    """
    if isinstance(category, str):
        category = [category]

    filtered_targets = [
        t
        for t in self.targets
        if (category is None or t.data.category in category) and (name is None or re.search(name, t.data.name))
    ]

    if inverse:
        return [t.data for t in self.targets if t not in filtered_targets]
    return [t.data for t in filtered_targets]