utils.fact_sheet

class FactSheet:

Parent class for all fact sheet types. Contains all methods that are identical between all fact sheet types.

The following fact sheet attributes loaded by default, plus all fields specific to the fact sheet type:

id
name
displayName
description
rev
level
type
qualitySeal
lxState
naFields
status
createdAt
updatedAt
tags (simplified structure)
subscriptions (simplified structure)
documents (simplified structure)
permittedWriteACL{id name} (only if ACL feature is enabled)
permittedReadACL{id name} (only if ACL feature is enabled)
FactSheet(obj, options=None)

Only used internally. To instantiate a fact sheet, use the respective child class, e.g. Application().

def relation(self, key):

Follows the relation specified by key from the current fact sheet and returns a list of all related fact sheets. key can either be the key of a relation, e.g. relApplicationToProcess, or a fact sheet type, e.g. Process. In the latter case, the relation key is derived from the relation model. In case of ambiguous relation keys, a warning is logged.

This function is usually never called directly. It's rather recommended to directly call fs.key, e.g. app.processes() or app.relApplicationToProcess().

@classmethod
def get_fs_class(cls, name):

Avoids the usage of eval(fs_type)(id) if you want to instantiate fact sheets based on a dynamic type.

@classmethod
def load(cls, fs_type, fs_id=None, filter_list=None):

depending on the usage, this method either loads a single fact sheet or all fact sheets of a certain type.

fs_type: The fact sheet type to be loaded.
fs_id: The id of the fact sheet to be loaded (if any, otherwise None).
filter_list: List of facet filters. See FactSheet.filter() for documentation. Returns either the fact sheet (as json response, unprocessed) or the list of fact sheets (equally unprocessed). All responses are harmonized to turn them into a more useful and "pythonic" data structure.

@classmethod
def all(cls, filter_list=None):

Careful when using this: It returns a list of all fact sheets in the workspace, so this can be huge.
Can be used to remove all demo fact sheets from a new workspace:

for fs in FactSheet.all():
    fs.archive()
@classmethod
def filter(cls, filter_list):

Returns a list of all fact sheets that match the facet filters.

Examples:

# Filter by lifecycle range
filtered_fs = Application.filter(
    # Facet filter for "FactSheetTypes = Application" is added automatically because of  "Application".filter,
    # doesn't need to be added here.
    [
        {
            "facetKey": "lifecycle",
            "operator": "OR",
            "keys": [
                "active"
            ],
            "dateFilter": {
                "type": "RANGE",
                "from": "2023-01-01",
                "to": "2023-12-31"
            }
        }
    ]
)

# Fact sheets that have a subscription (NOR + missing = "any subscription")
filtered_fs = FactSheet.filter(
    [
        {
            "facetKey": "Subscriptions",
            "operator": "NOR",
            "keys": [
                "__missing__"
            ]
        }
    ]
)

# Filter by relation including filter on relation field
filtered_fs = Application.filter(
    # Facet filter for "FactSheetTypes = Application" is added automatically, doesn't need to be added here
    [
        {
            "facetKey": "relApplicationToUserGroup",
            "operator": "OR",
            "keys": [
                TEST_USERGROUP
            ],
            "relationFieldsFilter": [
                {
                    "fieldName": "usageType",
                    "values": [
                        "user"
                    ]
                }
            ],
            "relationFieldsFilterOperator": "INCLUSIVE",
            # INCLUSIVE:
            # Only the matches of the applied relation field filters are kept in the response.
            # 
            # EXCLUSIVE:
            # The matches of the applied relation field filters are removed from the response.
        }
    ]
)

# Filter by relation including transitively (parent/child) related fact sheets
filtered_fs = Application.filter(
    # Facet filter for "FactSheetTypes = Application" is added automatically, doesn't need to be added here
    [
        {
            "facetKey": "relApplicationToUserGroup",
            "operator": "OR",
            "keys": [
                TEST_USERGROUP
            ]
        }
    ]
)

# Filter by relation excluding transitively (parent/child) related fact sheets
filtered_fs_ex = Application.filter(
    # Facet filter for "FactSheetTypes = Application" is added automatically, doesn't need to be added here
    [
        {
            "facetKey": "relApplicationToUserGroup",
            "operator": "OR",
            "keys": [
                TEST_USERGROUP
            ],
            "excludeTransitiveRelations": True
        }
    ]
)

# Filter by relation=n/a
filtered_fs = Application.filter(
    # Facet filter for "FactSheetTypes = Application" is added automatically, doesn't need to be added here
    [
        {
            "facetKey": "relApplicationToUserGroup",
            "operator": "OR",
            "keys": [
                "__missing__"
            ],
            "relationFieldsFilter": [],
            "relationFieldsFilterOperator": "INCLUSIVE",
        }
    ]
)

# Advanced filter: Apps with relations to IT Components that are EoL today
filtered_fs = Application.filter(
    [
        {
            "facetKey": "relApplicationToITComponent",
            "operator": "OR",
            "keys": [],
            "relationFieldsFilterOperator": "INCLUSIVE",
            "relationFieldsFilter": [],
            "subFilter": {
                "facetFilters": [
                    {
                        "facetKey": "FactSheetTypes",
                        "operator": "OR",
                        "keys": [
                            "ITComponent"
                        ]
                    },
                    {
                        "facetKey": "lifecycle",
                        "operator": "OR",
                        "keys": [
                            "endOfLife"
                        ],
                        "dateFilter": {
                            "from": "2023-02-22",
                            "to": "2023-02-22",
                            "type": "TODAY",
                        }
                    }
                ],
            }
        }
    ]
)

# Advanced filter: ITComponents with two-step indirect relation to one specific Application (via Servers)
filtered_fs = ITComponent.filter(
    [
        {
            "facetKey": "relITComponentToServer",
            "operator": "OR",
            "keys": [],
            "excludeTransitiveRelations": True,
            "subFilter": {
                "facetFilters": [
                    {
                        "facetKey": "FactSheetTypes",
                        "operator": "OR",
                        "keys": [
                            "Server"
                        ]
                    },
                    {
                        "facetKey": "relServerToApplication",
                        "operator": "OR",
                        "keys": [
                            TEST_APP
                        ],
                        "excludeTransitiveRelations": True
                    },
                ],
            }
        }
    ]
)

# Date filter for lifecycle "active" in a specific year
filtered_fs = Application.filter(
    [
        {
            "facetKey": "lifecycle",
            "operator": "OR",
            "keys": [
                "active"
                # "__any__" # filters for "any" lifecycle
            ],
            "dateFilter": {
                "from": str(datetime.datetime.today().year) + "-01-01",
                "to": str(datetime.datetime.today().year) + "-12-31",
                "type": "RANGE",
                # Alternative values:
                # "POINT",
                # "TODAY",
                # "END_OF_MONTH",
                # "END_OF_YEAR",
                # "RANGE",
                # "RANGE_STARTS",
                # "RANGE_ENDS",
            }
        }
    ]
)

# Filter by hierarchy level
filtered_fs = FactSheet.filter(
    [
        {
            "facetKey": "hierarchyLevel",
            "operator": "OR",
            "keys": [
                "1"
            ]
        }
    ]
)

# Filter by quality state
filtered_fs = Application.filter(
    [
        {
            "facetKey": "lxState",
            "operator": "OR",
            "keys": [
                "BROKEN_QUALITY_SEAL",
                # "DRAFT",
                # "REJECTED",
                # "APPROVED",
            ]
        }
    ]
)

# Filter by archived state
filtered_fs = Application.filter(
    [
        {
            "facetKey": "TrashBin",
            "operator": "OR",
            "keys": [
                "archived",
            ]
        }
    ]
)

# Filter by tags
group = "Application Type"
tag = "Web-based"
filtered_fs = Application.filter(
    [
        {
            "facetKey": LeanIXClient().get_tag_group_id(group),
            "operator": "OR",
            "keys": [
                LeanIXClient().get_tag_id(tag_group=group, tag=tag),
                # "__missing__" (filters for n/a)
            ]
        }
    ]
)

# Filter by tags in "Other Tags" group
filtered_fs = Application.filter(
    [
        {
            "facetKey": LeanIXClient().get_tag_group_id("Other tags"),
            "operator": "OR",
            "keys": [
                LeanIXClient().get_tag_id(tag_group="Other tags", tag="Holding"),
                # "__missing__" (filters for n/a)
            ]
        }
    ]
)

# Filter by tags=n/a
group = "Application Type"
filtered_fs = Application.filter(
    [
        {
            "facetKey": LeanIXClient().get_tag_group_id(group),
            "operator": "OR",
            "keys": [
                "__missing__"   # (filters for n/a)
            ]
        }
    ]
)

# Filter by hierarchy level
filtered_fs = Application.filter(
    [
        {
            "facetKey": "hierarchyLevel",
            "operator": "OR",
            "keys": [
                "1"
            ]
        }
    ]
)

# Filter by subscriptions ("me" filter + role)
filtered_fs = Application.filter(
    [
        {
            "facetKey": "Subscriptions",
            "operator": "OR",
            "keys": [
                "__me__"
                # "__missing__" (filters for n/a)
            ],
            "subscriptionFilter": {
                "type": "RESPONSIBLE",
                "roleId": LeanIXClient().get_role_id("Application Owner")
            }
        }
    ]
)

# Filter by subscription e-mail
filtered_fs = Application.filter(
    [
        {
            "facetKey": "Subscriptions",
            "operator": "OR",
            "keys": [
                LeanIXUser.find_by_email("john.doe@example.com")["id"]
                # "__missing__" (filters for n/a)
            ]
        }
    ]
)

# Filter by subscription=n/a
filtered_fs = Application.filter(
    [
        {
            "facetKey": "Subscriptions",
            "operator": "OR",
            "keys": [
                "__missing__" # (filters for n/a)
            ]
        }
    ]
)

# Filter by "data quality" filter set
filtered_fs = Application.filter(
    [
        {
            "facetKey": "DataQuality",
            "operator": "OR",
            "keys": [
                "_noResponsible_"
                # "_qualitySealBroken_"
                # "_noDescription_"
                # "_noLifecycle_"
            ]
        }
    ]
)

# Filter by fact sheet property (single select)
filtered_fs = Application.filter(
    [
        {
            "facetKey": "functionalSuitability",
            "operator": "OR",
            "keys": [
                # "unreasonable"
                # "insufficient"
                "appropriate"
                # "perfect"
                # "__missing__" # filters for "n/a"
            ]
        }
    ]
)
@classmethod
def ensure_load_all(cls, force=False):

Checks if a fact sheet type has already been loaded to the cache. If not, it loads all fact sheets of this type via self.load() and instantiates them, which stores them in the cache.

fs_type: The fact sheet type, such as application.Application. If None: Load all fact sheets of all types. Returns a list of all instances of the given fact sheet type.

force: If set to True, reload all fact sheets from LeanIX, even if this fact sheet type has already been loaded.

def parents(self):

Returns a list of all parent fact sheets of this fact sheet. This list usually contains only one element.

def children(self):

Returns a list of all child fact sheets of this fact sheet.

def requires(self):

Returns a list of all fact sheets that this fact sheet requires.

def required_by(self):

Returns a list of all fact sheets that this fact sheet is required by.

def successors(self):

Returns a list of all successor fact sheets of this fact sheet.

def predecessors(self):

Returns a list of all predecessor fact sheets of this fact sheet.

def get(self, key, default=None):

Almost identical to the standard Python collection 'get' method, except that it also returns the default if 'key' is in self and self[key] is None. Usually, get() would return None, which is somewhat impractical if you would like to chain get() requests with a default value of {} for simplicity.

@classmethod
def create(cls, name, description='', tags=None, lifecycle=None):

Creates a new fact sheet. name is mandatory, description, tags and lifecycle are optional.

tags example: {"TagGroup": ["Tag 1", "Tag 2"...]}
lifecycle example: {"plan": "2020-01-31", "phaseIn": "2021-01-31", "active": "2022-01-31", "phaseOut": "2023-01-31", "endOfLife": "2024-01-31"}

@classmethod
def archive_(cls, fs_id, comment='deleted by Python API'):

Please ignore the trailing underscore "_", the method is called archive(...). This documentation entry is just there to inform you that you can call archive on both the FactSheet class but also on an instance.
Examples:

FactSheet.archive("fac7f0a6-88be-49fd-8935-1fde3edd335d", "Fact sheet was redundant")
Application("My test application").archive("Fact sheet was redundant")
@classmethod
def restore_(cls, fs_id, comment='restored by Python API'):

Please ignore the trailing underscore "_", the method is called restore(...). This documentation entry is just there to inform you that you can call restore on both the FactSheet class but also on an instance.
Examples:

FactSheet.restore("fac7f0a6-88be-49fd-8935-1fde3edd335d", "Fact sheet was archived mistakenly")
Application("My test application").restore("Fact sheet was archived mistakenly")
def archive(self, comment='deleted by Python API'):

This documentation entry is just there to inform you that you can call archive on both the FactSheet class but also on an instance.
Examples:

FactSheet.archive("fac7f0a6-88be-49fd-8935-1fde3edd335d", "Fact sheet was redundant")
Application("My test application").archive("Fact sheet was redundant")
def restore(self, comment='restored by Python API'):

This documentation entry is just there to inform you that you can call restore on both the FactSheet class but also on an instance.
Examples:

FactSheet.restore("fac7f0a6-88be-49fd-8935-1fde3edd335d", "Fact sheet was archived mistakenly")
Application("My test application").restore("Fact sheet was archived mistakenly")
def add_tags(self, tags):

Adds tags to a fact sheet without touching the already assigned tags.

Example:

# Adds the tag "Mobile" from tag group "Application Type" as well as 2 more tags from another tag group.
app = Application("...")
tags = {"Application Type": {"Mobile"}, "Another Tag Group": {"Another Tag", "One more Tag"}}
app.add_tags(tags)
def delete_tags(self, tags):

Deletes tags from an application without touching any other already assigned tags.

Example:

# Deletes the "Application Type" tag "Mobile" from application app, as well as 2 more tags.
app = Application("...")
tags = {"Application Type": {"Mobile"}, "Another Tag Group": {"Another Tag", "One more Tag"}}
app.delete_tags(tags)
def set_tags(self, tags):

Setting a fixed tag set, unsetting all other tags in the same tag groups.

Examples:

# unset all tags in the tag groups “Segment” and “Application Type”:
tags = {"Segment": {}, "Application Type": {}}
app.set_tags(tags)

# set two tag groups to a fixed tag set, unsetting all other tags in the respective tag groups
tags = {"Segment": {"Development", "Cross Functions"}, "Application Type": {"Web-based", "Client"}}
app.set_tags(tags)
def has_tag(self, tag, tag_group=None):

Returns true if the fact sheet has a tag named tag in any tag group, or explicitly in the tag group tag_group if the parameter is given.

Example:

app = Application("My Application")
if app.has_tag("Global"):
    # Application has "Global" tag in any tag group
if app.has_tag("Global", tag_group="Application Type"):
    # Application has "Global" tag in tag group "Application Type"
def add_subscription( self, user_id_or_email, subscription_type='RESPONSIBLE', role_name=None, comment=None):

Subscribes a user to a fact sheet.
user_id_or_email is the user ID or e-mail address of the user to be subscribed.
subscription_type is the role type to subscribe. Default: "RESPONSIBLE"
role_name is The role name to be subscribed. Default: None.
comment is the comment to be added to the subscription. Default: Empty.
If a subscription of this user in this role_name already exists, the comment is updated. Returns the ["data"] part of the API response object.

def update_subscription( self, user_id_or_email, subscription_type='RESPONSIBLE', role_name=None, comment=None):

Updates the comment of an existing subscription given by the user, subscription_type and role_name.

def change_subscription_type(self, user_id_or_email, subscription_type, force=False):

Change the subscription type of an existing subscription, e.g. from OBSERVER to RESPONSIBLE.

Warning: LeanIX will allow you to even change the subscription type if some of the related subscription roles are not configured for the target subscription type.
If you want to avoid an inconsistent state, make sure that all relevant roles support the target subscription types, e.g. via setting their subscription type to "All" first.
This method automatically checks if the target subscription type is valid for all relevant roles. To override this check, use force=True.

Example:

app = Application("My Application")
app.change_subscription_type("user@example.com", "RESPONSIBLE")
def delete_subscription( self, subscription_id=None, role=None, subscription_user_id=None, subscription_type='RESPONSIBLE', user_email=None, options=None):

Deletes a subscription.
Needs at least one of the following parameters to identify the subscription: subscription_id, subscription_user_id, user_email.

In case you want to delete only an individual role within the subscription and not the entire subscription for this user, you can use the optional parameters role and subscription_type to specify the role you want to delete. If you do this, please note: If you are deleting an individual role from - say - a RESPONSIBLE subscription, and it's the last role in that subscription, you will end up with an empty RESPONSIBLE subscription, which is maybe not what you expect.
To delete the entire subscription when the last role was deleted, set options={"delete_entire_subscription_if_last_role_is_removed": True}

def create_relation(self, target, attributes=None, rel_type=None):

Creates a relation to another fact sheet. target is a fact sheet, attributes are the attributes on the relation. If there are multiple relation types between the two fact sheet types, you need to specify rel_type, e.g. relApplicationToUserGroup. All valid relation types can be found in your auto-generated utils/ws_[workspace_name]/fact_sheet_types.py.
If you want to use line breaks in multiline fields, e.g. for 'description', you need to use the literal string r' ' or '\n' instead of newline strings ' '.
Returns False if the relation already exists, so that you don't need to .reload() after calling.

    Example:  

        app = Application("My Application")
        ug = UserGroup("My User Group")

        attributes = {
            "usageType": "owner",
            "numberOfUsers": 101,
            "functionalSuitability": "insufficient",
            "description": r"This is a description.\nAnd this is line 2 of the description.",
            "activeFrom": "2022-07-20",
            "activeUntil": "2022-07-21"
        }

        app.create_relation(ug, attributes)

    Example including relation type:

        app = Application("My Application")
        provider = Provider("My Provider")
        attributes = {}
        app.create_relation(ug, attributes, "relToRequires")
def delete_relation(self, target, rel_type=None):

Deletes the relation between this fact sheet and the target.
If there are multiple relation types between the two fact sheet types, you need to specify rel_type, e.g. relApplicationToUserGroup. All valid relation types can be found in your auto-generated utils/ws_[workspace_name]/fact_sheet_types.py.

Example:

app = Application("My Application")
ug = UserGroup("My User Group")
app.delete_relation(ug)
def update_relation(self, target=None, attributes=None, rel_type=None, relid=None):

Updates the relation between this fact sheet and the target.
If there are multiple relation types between the two fact sheet types, you need to specify rel_type, e.g. relApplicationToUserGroup. All valid relation types can be found in your auto-generated utils/ws_[workspace_name]/fact_sheet_types.py.
Note that if you omit any already existing attributes in the attributes parameter, their values will not be changed.

Example:

app = Application("My Application")
ug = UserGroup("My User Group")

attributes = {
    "numberOfUsers": 201,
    "description": "This is a new description."
}

# only updates the attributes "numberOfUsers" and "description".
app.update_relation(ug)
def switch_relation(self, target, new_target, rel_type=None):

Replaces the target of an existing relation to the current fact sheet target to the new fact sheet new_target while keeping all of its relation attributes.

def get_relation(self, target, rel_type=None):

Returns the relation between this fact sheet and the target. This is a convenience function to access the attributes on the relation in an easy way.
If there are multiple relation types between the two fact sheet types, you need to specify rel_type, e.g. relApplicationToUserGroup. All valid relation types can be found in your auto-generated utils/ws_[workspace_name]/fact_sheet_types.py. If you do not specify a relation type, it will return the first relation that it finds. It will, however, not scan relToRequires and relToRequiredBy.

Example:

app = Application("My Application")
ug = UserGroup("My User Group")
rel = app.get_relation(ug)
print(rel["description"])
print(rel["activeFrom"])
def get_matching_relation(self, match, fs_type=None, rel_type=None, fallback=None):

Returns the first relation of this fact sheet to any target fact sheet with fact sheet type = fs_type or with relation type = rel_type where the relation attribute given by the key in the match dict matches the value given by match[key] (or any of the values in match[key], if match[key] is a list).
If no relations are found and the fallback parameter is provided, it searches again, and uses the "fallback" parameter as match.

Example - finding the owning user group of an application:

  app.get_matching_relation("UserGroup", {"usageType": "owner"})
def get_matching_relations(self, match, fs_type=None, rel_type=None, fallback=None):

See get_matching_relation(), but returns all relations instead of just the first one.

def add_resource(self, name, url, resource_type, description):

Adds a resource to a fact sheet. resourceType needs to be a valid resource type as defined in LeanIX.

Example:

app.add_resource("My resource", "https://example.com/resource.html", "documentation", "My description")
@staticmethod
def delete_resource(resource_id):

Deletes the resource with the given resource_id.

Example:

# Delete all resources from a fact sheet
application = Application("My Test Application")
for doc in application["documents"]:
    application.delete_resource(doc["id"])
def get_log_events(self, options=None):

Gets all log events for this fact sheet.

Returns the ["data"] part of the API response object.

def reload(self):

Reloads this fact sheet from LeanIX. Reloading is not done automatically due to performance reasons, so it can be done explicitly if required.

Example:

app.reload()
def set_quality_seal(self, status='APPROVED'):

Sets (or breaks) the quality seal.
Possible values for status: "APPROVED" (default), "DRAFT", "REJECTED", "BROKEN_QUALITY_SEAL"

def break_quality_seal(self):

Breaks the quality seal.

def url(self):

Returns the URL that points to this fact sheet, including FQDN. Used in auto-generated e-mails.

@classmethod
def field(cls, field, language='en'):

Returns language-specific translated help texts and label translations for a fact sheet field.

def update_field(self, update=None, **kwargs):

Updates a fact sheet field. update parameter is a dict with keys name (field to update) and value (new value of the field).
For convenience, name + value can also be provided via keyword arguments.

Examples:

application = Application("...")

application.update_field(name="New name")
application.update_field(description="New description")
application.update_field(lifecycle={"phaseIn": "2020-01-01", "active": "2022-03-03"})
application.update_field(functionalSuitability="insufficient")
application.update_field(technicalSuitability="adequate")

Alternative, using old syntax:

application.update_field({"name": "/name", "value": "New name"})
application.update_field({"name": "/description", "value": "New description"})

If you want to set a relation to "leave empty" to leave it blank, you can use the LeanIX attribute "naFields" ("not applicable fields"). You can set it like this:

itc = ITComponent("...")
itc.update_field(naFields=["relITComponentToUserGroup"])

Or more generically, if you want to keep the existing naFields for a fact sheet:

itc = ITComponent("...")
itc["naFields"].append("relITComponentToUserGroup")
itc.update_field(naFields=itc["naFields"])
def clone( self, release=None, name=None, externalId=None, use_as='Predecessor', cloneRelations=True, cloneExternalIds=True, cloneCreationDate=False):

Clones a fact sheet field and returns the new fact sheet's id.
use_as can be either None, "Predecessor" or "Successor".

Examples:

app = Application("My application")
new_fs_id = app.clone(release=release, use_as="Successor", externalId="NewExtId")
new_app = Application(new_fs_id)
def create_todo( self, title, description, user_ids, category='ACTION_ITEM', due_date=None):

Creates a To-Do item for this fact sheet.
user_ids is a list of user ids.
category is either "ACTION_ITEM" for ToDos, or "ANSWER" for questions.
due_date is a string in format YYYY-mm-dd.

Example:

app = Application("MyTest")
title = "My To-Do Item"
description = "My Description"
category = "ACTION_ITEM"
user_ids = [LeanIXUser.find_by_email("john.doe@example.com")["id"]]
due_date = "2029-12-31"

app.create_todo(title=title, description=description, category=category, user_ids=user_ids, due_date=due_date)
def create_todo_if_not_exists( self, title, description, user_ids, category='ACTION_ITEM', due_date=None):
def todos(self, states=None):

Loads all ToDos for this fact sheet. Currently only supports the "states" parameter. If you also need the other parameters, let me know.

Full parameter list that would be theoretically possible:

states: ["OPEN", "IN_PROGRESS", "CLOSED"]  
categories: ["ACTION_ITEM", "ANSWER", "IMPORT", "LINK"]  
claimedBy: List of user ids  
assignees: List of user ids  
creatorIds: List of user ids  
dueDate: {"type": "RANGE", "value": {"from": "2023-10-29", "to": "2023-11-12"}}  
creationDate: {"type": "RANGE", "value": {"from": "2023-11-12", "to": "2023-11-13"}}  
resolutions: ["ACCEPTED", "REJECTED", "REVERTED", "NONE"]