Skip to content

Overview

Cards (aka ArtifactCards) are one of the primary data structures for working with OpsML that contain both data and model interface objects as well as associated metadata. ArtifactCards are stored in registries and can be used to track and version data and models.

flowchart TD
DS(["fa:fa-user-group" DS])
DS --> Model([Model])
Model --> Metrics([Metrics])
Metrics --> RunCard(RunCard)
RunCard --> RunRegistry[(RunRegistry)]
Model --> ModelCard(ModelCard)
ModelCard --> ModelRegistry[(ModelRegistry)]
ModelCard -->DeployableArtifact(Deployable Artifact)
DS --> Data([Data])
Data --> DataCard(DataCard)
DataCard --> DataRegistry[(DataRegistry)]
DataCard -.->ModelCard

style DS fill:#028e6b,stroke:black,stroke-width:2px,color:white,font-weight:bolder
style Model fill:#028e6b,stroke:black,stroke-width:2px,color:white,font-weight:bolder
style Metrics fill:#028e6b,stroke:black,stroke-width:2px,color:white,font-weight:bolder
style Data fill:#028e6b,stroke:black,stroke-width:2px,color:white,font-weight:bolder
style RunCard fill:#028e6b,stroke:black,stroke-width:2px,color:white,font-weight:bolder
style DataCard fill:#028e6b,stroke:black,stroke-width:2px,color:white,font-weight:bolder
style ModelCard fill:#028e6b,stroke:black,stroke-width:2px,color:white,font-weight:bolder
style DeployableArtifact fill:#028e6b,stroke:black,stroke-width:2px,color:white,font-weight:bolder
style RunRegistry fill:#5e0fb7,stroke:black,stroke-width:2px,color:white,font-weight:bolder
style ModelRegistry fill:#5e0fb7,stroke:black,stroke-width:2px,color:white,font-weight:bolder
style DataRegistry fill:#5e0fb7,stroke:black,stroke-width:2px,color:white,font-weight:bolder

Card Types

  • DataCard: Card used to store data-related information (``, dependent variables, feature descriptions, split logic, etc.)
  • ModelCard: Card used to store trained model and model information
  • RunCard: Stores artifact and metric info related to Data, Model, or Pipeline cards.
  • ProjectCard: Stores information related to unique projects. You will most likely never interact with this card directly.

Registries

Each card type is associated with a specific registry (DataCard with data registry, ModelCard with model registry, etc.), and registries can be used to list, load and register cards.

Card Information

You'll notice when working with ArtifactCards and CardRegistries that there are a few common arguments that are always required. These arguments are:

  • name: Name of card
  • repository: Repository associated with card
  • contact: Contact information for card

These arguments are required for card registration and can be supplied through named arguments or through a CardInfo dataclass.

CardInfo is a helper class that can be used to store these arguments so you don't need to make repetitive calls. In addition, the CardInfo class allows you to set runtime environment variables through a set_env() method. This will allow to create cards without having to specify name, repository and/or contact. Examples are below.

Example of named arguments

from opsml import DataCard

# skip data interface logic
...

DataCard(
  name="linnerud", 
  repository="opsml", 
  contact="mlops.com", 
  interface=data_interface
  )

Example of CardInfo

from opsml import DataCard, CardInfo

info = CardInfo(name="linnerud", repository="opsml", contact="mlops.com")

# skip data interface logic
...

DataCard(
  info=info,
  interface=data_interface
  )

Example of Runtime Env Vars

from opsml import DataCard, CardInfo

info = CardInfo(name="linnerud", repository="opsml", contact="mlops.com").set_env()

# skip data interface logic
...

DataCard(interface=data_interface)

Name Uniqueness

When registering cards, OpsML will check to see if a card with the same name, repository and version already exists. Therefore, name uniqueness is guaranteed at the repository/name level. Thus, different repositories can share cards with the same name.

Listing Cards

Returns a list of dictionaries.

Required Args:

  • Name: Name of card (Optional)
  • repository: repository associated with card (Optional)
  • Version: Version of Card (Optional)
  • uid: Uid of card (Optional)
  • info: CardInfo dataclass that can be used in place of Name, repository, Version and Uid
  • limit: Limit result

Example:

from opsml import CardRegistry

registry = CardRegistry(registry_name="model") # can be "data", "model", "run", "pipeline

# examples
registry.list_cards() 
# will list all cards in registry

registry.list_cards(limit=10) 
# will list cards and limit the result to 10

registry.list_cards(name="linear-reg")
  # list all cards with name "linear-reg"

registry.list_cards(name="linear-reg", repository="opsml") 
# list all cards with name "linear-reg" with repository "opsml"

registry.list_cards(name="linear-reg", repository="opsml", version="1.0.0") 
# list card with name "linear-reg" with repository "opsml" and version 1.0.0

registry.list_cards(name="linear-reg", repository="opsml", version="1.*.*") 
# list cards with name "linear-reg" with repository "opsml" and major version of "1"

registry.list_cards(name="linear-reg", repository="opsml", version="^2.3.4") 
# list card with name "linear-reg" with repository "opsml" and latest version < 3.0.0

registry.list_cards(name="linear-reg", repository="opsml", version="~2.3.4") 
# list card with name "linear-reg" with repository "opsml" and latest version < 2.4.0

registry.list_cards(uid=uid)
# list card by uid

Registering a Card

Register a card to a registry

Required Args:

  • card: Card to register
  • version_type: Type of version increment. Can be "major", "minor" and "patch" (Optional)
  • save_path: Specific path to save to in root opsml folder if default are not preferred (Optional)

Example:

from opsml import CardRegistry

model_registry = CardRegistry(registry_name="model")

# skipping ModelInterface logic
...

model_card = ModelCard(
      interface=model_interface,
      name="linear-reg",
      repository="opsml",
      contact="mlops.com",
      datacard_uid=data_card.uid,
  )

example_record = model_registry.register_card(card=model_card)
print(model_card.version)
#> 1.0.0

Loading Cards

Load an Artifact card from a registry.

Required Args:

  • Name: Name of card (Optional)
  • repository: repository associated with card (Optional)
  • Version: Version of Card (Optional)
  • uid: Uid of card (Optional)
  • info: CardInfo dataclass that can be used in place of Name, repository, Version and Uid

Example:

from opsml import CardRegistry
model_registry = CardRegistry(registry_name="model")

example_record = model_registry.list_cards(name="linnerrud", )[0]

model_card = model_registry.load_card(uid=example_record.get("uid"))
print(model_card.version)
#> 1.0.0

Update Cards

You can also update cards

from opsml import CardRegistry
model_registry = CardRegistry(registry_name="model")

# skipping card logic
...

card.contact = "new_contact"
model_registry.update_card(card)

Deleting Cards

In the event you need to delete a card, there's a built in delete_card method.

from opsml import CardRegistry
model_registry = CardRegistry(registry_name="model")

# skipping card logic
...

model_registry.delete_card(card)

opsml.CardRegistry

Source code in opsml/registry/registry.py
 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
class CardRegistry:
    def __init__(self, registry_type: Union[RegistryType, str]):
        """
        Interface for connecting to any of the Card registries

        Args:
            registry_type:
                Type of card registry to create
            settings:
                Storage settings

        Returns:
            Instantiated connection to specific Card registry

        Example:
            data_registry = CardRegistry(RegistryType.DATA)
            data_registry.list_cards()

            or
            data_registry = CardRegistry("data")
            data_registry.list_cards()
        """

        _registry_type = (
            registry_type if isinstance(registry_type, RegistryType) else RegistryType.from_str(registry_type)
        )

        self._registry = _set_registry(_registry_type)
        self.table_name = self._registry.table_name

    @property
    def registry_type(self) -> RegistryType:
        "Registry type for card registry"
        return self._registry.registry_type

    def list_cards(
        self,
        uid: Optional[str] = None,
        name: Optional[str] = None,
        repository: Optional[str] = None,
        version: Optional[str] = None,
        tags: Optional[Dict[str, str]] = None,
        info: Optional[CardInfo] = None,
        max_date: Optional[str] = None,
        limit: Optional[int] = None,
        ignore_release_candidates: bool = False,
        sort_by_timestamp: bool = False,
    ) -> List[Dict[str, Any]]:
        """Retrieves records from registry

        Args:
            name:
                Card name
            repository:
                Repository associated with card
            version:
                Optional version number of existing data. If not specified, the
                most recent version will be used
            tags:
                Dictionary of key, value tags to search for
            uid:
                Unique identifier for Card. If present, the uid takes precedence
            max_date:
                Max date to search. (e.g. "2023-05-01" would search for cards up to and including "2023-05-01")
            limit:
                Places a limit on result list. Results are sorted by SemVer
            info:
                CardInfo object. If present, the info object takes precedence
            ignore_release_candidates:
                If True, ignores release candidates
            sort_by_timestamp:
                If True, sorts by timestamp descending

        Returns:
            pandas dataframe of records or list of dictionaries
        """

        if info is not None:
            name = name or info.name
            repository = repository or info.repository
            uid = uid or info.uid
            version = version or info.version
            tags = tags or info.tags

        if name is not None:
            name = name.lower()

        if repository is not None:
            repository = repository.lower()

        if all(not bool(var) for var in [name, repository, version, uid, tags]):
            limit = limit or 25

        card_list = self._registry.list_cards(
            uid=uid,
            name=name,
            repository=repository,
            version=version,
            max_date=max_date,
            limit=limit,
            tags=tags,
            ignore_release_candidates=ignore_release_candidates,
            sort_by_timestamp=sort_by_timestamp,
        )

        return card_list

    def load_card(
        self,
        name: Optional[str] = None,
        repository: Optional[str] = None,
        uid: Optional[str] = None,
        tags: Optional[Dict[str, str]] = None,
        version: Optional[str] = None,
        info: Optional[CardInfo] = None,
        ignore_release_candidates: bool = False,
        interface: Optional[Union[Type[ModelInterface], Type[DataInterface]]] = None,
    ) -> Card:
        """Loads a specific card

        Args:
            name:
                Optional Card name
            uid:
                Unique identifier for card. If present, the uid takes
                precedence.
            tags:
                Optional tags associated with model.
            repository:
                Optional repository associated with card
            version:
                Optional version number of existing data. If not specified, the
                most recent version will be used
            info:
                Optional CardInfo object. If present, the info takes precedence
            ignore_release_candidates:
                If True, ignores release candidates
            interface:
                Optional interface to use for loading card. This is required for when using
                subclassed interfaces.

        Returns
            Card
        """

        # find better way to do this later
        if info is not None:
            name = name or info.name
            uid = uid or info.uid
            version = version or info.version
            tags = tags or info.tags

        name = clean_string(name)

        records = self.list_cards(
            uid=uid,
            name=name,
            repository=repository,
            version=version,
            tags=tags,
            ignore_release_candidates=ignore_release_candidates,
            limit=1,
        )

        return CardLoader(
            card_args=records[0],
            registry_type=self.registry_type,
        ).load_card(interface=interface)

    def register_card(
        self,
        card: Card,
        version_type: Union[VersionType, str] = VersionType.MINOR,
        pre_tag: str = "rc",
        build_tag: str = "build",
    ) -> None:
        """
        Adds a new `Card` record to registry. Registration will be skipped if the card already exists.

        Args:
            card:
                card to register
            version_type:
                Version type for increment. Options are "major", "minor" and
                "patch". Defaults to "minor".
            pre_tag:
                pre-release tag to add to card version
            build_tag:
                build tag to add to card version
        """

        _version_type = version_type if isinstance(version_type, VersionType) else VersionType.from_str(version_type)

        if card.uid is not None and card.version != CommonKwargs.BASE_VERSION.value:
            logger.info(
                textwrap.dedent(
                    f"""
                Card {card.uid} already exists. Skipping registration. If you'd like to register
                a new card, please instantiate a new Card object. If you'd like to update the
                existing card, please use the update_card method.
                """
                )
            )

        else:
            self._registry.register_card(
                card=card,
                version_type=_version_type,
                pre_tag=pre_tag,
                build_tag=build_tag,
            )

    def update_card(self, card: Card) -> None:
        """
        Update an artifact card based on current registry

        Args:
            card:
                Card to register
        """
        return self._registry.update_card(card=card)

    def query_value_from_card(self, uid: str, columns: List[str]) -> Dict[str, Any]:
        """
        Query column values from a specific Card

        Args:
            uid:
                Uid of Card
            columns:
                List of columns to query

        Returns:
            Dictionary of column, values pairs
        """
        results = self._registry.list_cards(uid=uid)[0]
        return {col: results[col] for col in columns}

    def delete_card(self, card: Card) -> None:
        """
        Delete a specific Card

        Args:
            card:
                Card to delete
        """
        return self._registry.delete_card(card)

registry_type: RegistryType property

Registry type for card registry

__init__(registry_type)

Interface for connecting to any of the Card registries

Parameters:

Name Type Description Default
registry_type Union[RegistryType, str]

Type of card registry to create

required
settings

Storage settings

required

Returns:

Type Description

Instantiated connection to specific Card registry

Example

data_registry = CardRegistry(RegistryType.DATA) data_registry.list_cards()

or data_registry = CardRegistry("data") data_registry.list_cards()

Source code in opsml/registry/registry.py
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
def __init__(self, registry_type: Union[RegistryType, str]):
    """
    Interface for connecting to any of the Card registries

    Args:
        registry_type:
            Type of card registry to create
        settings:
            Storage settings

    Returns:
        Instantiated connection to specific Card registry

    Example:
        data_registry = CardRegistry(RegistryType.DATA)
        data_registry.list_cards()

        or
        data_registry = CardRegistry("data")
        data_registry.list_cards()
    """

    _registry_type = (
        registry_type if isinstance(registry_type, RegistryType) else RegistryType.from_str(registry_type)
    )

    self._registry = _set_registry(_registry_type)
    self.table_name = self._registry.table_name

delete_card(card)

Delete a specific Card

Parameters:

Name Type Description Default
card Card

Card to delete

required
Source code in opsml/registry/registry.py
260
261
262
263
264
265
266
267
268
def delete_card(self, card: Card) -> None:
    """
    Delete a specific Card

    Args:
        card:
            Card to delete
    """
    return self._registry.delete_card(card)

list_cards(uid=None, name=None, repository=None, version=None, tags=None, info=None, max_date=None, limit=None, ignore_release_candidates=False, sort_by_timestamp=False)

Retrieves records from registry

Parameters:

Name Type Description Default
name Optional[str]

Card name

None
repository Optional[str]

Repository associated with card

None
version Optional[str]

Optional version number of existing data. If not specified, the most recent version will be used

None
tags Optional[Dict[str, str]]

Dictionary of key, value tags to search for

None
uid Optional[str]

Unique identifier for Card. If present, the uid takes precedence

None
max_date Optional[str]

Max date to search. (e.g. "2023-05-01" would search for cards up to and including "2023-05-01")

None
limit Optional[int]

Places a limit on result list. Results are sorted by SemVer

None
info Optional[CardInfo]

CardInfo object. If present, the info object takes precedence

None
ignore_release_candidates bool

If True, ignores release candidates

False
sort_by_timestamp bool

If True, sorts by timestamp descending

False

Returns:

Type Description
List[Dict[str, Any]]

pandas dataframe of records or list of dictionaries

Source code in opsml/registry/registry.py
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def list_cards(
    self,
    uid: Optional[str] = None,
    name: Optional[str] = None,
    repository: Optional[str] = None,
    version: Optional[str] = None,
    tags: Optional[Dict[str, str]] = None,
    info: Optional[CardInfo] = None,
    max_date: Optional[str] = None,
    limit: Optional[int] = None,
    ignore_release_candidates: bool = False,
    sort_by_timestamp: bool = False,
) -> List[Dict[str, Any]]:
    """Retrieves records from registry

    Args:
        name:
            Card name
        repository:
            Repository associated with card
        version:
            Optional version number of existing data. If not specified, the
            most recent version will be used
        tags:
            Dictionary of key, value tags to search for
        uid:
            Unique identifier for Card. If present, the uid takes precedence
        max_date:
            Max date to search. (e.g. "2023-05-01" would search for cards up to and including "2023-05-01")
        limit:
            Places a limit on result list. Results are sorted by SemVer
        info:
            CardInfo object. If present, the info object takes precedence
        ignore_release_candidates:
            If True, ignores release candidates
        sort_by_timestamp:
            If True, sorts by timestamp descending

    Returns:
        pandas dataframe of records or list of dictionaries
    """

    if info is not None:
        name = name or info.name
        repository = repository or info.repository
        uid = uid or info.uid
        version = version or info.version
        tags = tags or info.tags

    if name is not None:
        name = name.lower()

    if repository is not None:
        repository = repository.lower()

    if all(not bool(var) for var in [name, repository, version, uid, tags]):
        limit = limit or 25

    card_list = self._registry.list_cards(
        uid=uid,
        name=name,
        repository=repository,
        version=version,
        max_date=max_date,
        limit=limit,
        tags=tags,
        ignore_release_candidates=ignore_release_candidates,
        sort_by_timestamp=sort_by_timestamp,
    )

    return card_list

load_card(name=None, repository=None, uid=None, tags=None, version=None, info=None, ignore_release_candidates=False, interface=None)

Loads a specific card

Parameters:

Name Type Description Default
name Optional[str]

Optional Card name

None
uid Optional[str]

Unique identifier for card. If present, the uid takes precedence.

None
tags Optional[Dict[str, str]]

Optional tags associated with model.

None
repository Optional[str]

Optional repository associated with card

None
version Optional[str]

Optional version number of existing data. If not specified, the most recent version will be used

None
info Optional[CardInfo]

Optional CardInfo object. If present, the info takes precedence

None
ignore_release_candidates bool

If True, ignores release candidates

False
interface Optional[Union[Type[ModelInterface], Type[DataInterface]]]

Optional interface to use for loading card. This is required for when using subclassed interfaces.

None

Returns Card

Source code in opsml/registry/registry.py
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
def load_card(
    self,
    name: Optional[str] = None,
    repository: Optional[str] = None,
    uid: Optional[str] = None,
    tags: Optional[Dict[str, str]] = None,
    version: Optional[str] = None,
    info: Optional[CardInfo] = None,
    ignore_release_candidates: bool = False,
    interface: Optional[Union[Type[ModelInterface], Type[DataInterface]]] = None,
) -> Card:
    """Loads a specific card

    Args:
        name:
            Optional Card name
        uid:
            Unique identifier for card. If present, the uid takes
            precedence.
        tags:
            Optional tags associated with model.
        repository:
            Optional repository associated with card
        version:
            Optional version number of existing data. If not specified, the
            most recent version will be used
        info:
            Optional CardInfo object. If present, the info takes precedence
        ignore_release_candidates:
            If True, ignores release candidates
        interface:
            Optional interface to use for loading card. This is required for when using
            subclassed interfaces.

    Returns
        Card
    """

    # find better way to do this later
    if info is not None:
        name = name or info.name
        uid = uid or info.uid
        version = version or info.version
        tags = tags or info.tags

    name = clean_string(name)

    records = self.list_cards(
        uid=uid,
        name=name,
        repository=repository,
        version=version,
        tags=tags,
        ignore_release_candidates=ignore_release_candidates,
        limit=1,
    )

    return CardLoader(
        card_args=records[0],
        registry_type=self.registry_type,
    ).load_card(interface=interface)

query_value_from_card(uid, columns)

Query column values from a specific Card

Parameters:

Name Type Description Default
uid str

Uid of Card

required
columns List[str]

List of columns to query

required

Returns:

Type Description
Dict[str, Any]

Dictionary of column, values pairs

Source code in opsml/registry/registry.py
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
def query_value_from_card(self, uid: str, columns: List[str]) -> Dict[str, Any]:
    """
    Query column values from a specific Card

    Args:
        uid:
            Uid of Card
        columns:
            List of columns to query

    Returns:
        Dictionary of column, values pairs
    """
    results = self._registry.list_cards(uid=uid)[0]
    return {col: results[col] for col in columns}

register_card(card, version_type=VersionType.MINOR, pre_tag='rc', build_tag='build')

Adds a new Card record to registry. Registration will be skipped if the card already exists.

Parameters:

Name Type Description Default
card Card

card to register

required
version_type Union[VersionType, str]

Version type for increment. Options are "major", "minor" and "patch". Defaults to "minor".

MINOR
pre_tag str

pre-release tag to add to card version

'rc'
build_tag str

build tag to add to card version

'build'
Source code in opsml/registry/registry.py
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
def register_card(
    self,
    card: Card,
    version_type: Union[VersionType, str] = VersionType.MINOR,
    pre_tag: str = "rc",
    build_tag: str = "build",
) -> None:
    """
    Adds a new `Card` record to registry. Registration will be skipped if the card already exists.

    Args:
        card:
            card to register
        version_type:
            Version type for increment. Options are "major", "minor" and
            "patch". Defaults to "minor".
        pre_tag:
            pre-release tag to add to card version
        build_tag:
            build tag to add to card version
    """

    _version_type = version_type if isinstance(version_type, VersionType) else VersionType.from_str(version_type)

    if card.uid is not None and card.version != CommonKwargs.BASE_VERSION.value:
        logger.info(
            textwrap.dedent(
                f"""
            Card {card.uid} already exists. Skipping registration. If you'd like to register
            a new card, please instantiate a new Card object. If you'd like to update the
            existing card, please use the update_card method.
            """
            )
        )

    else:
        self._registry.register_card(
            card=card,
            version_type=_version_type,
            pre_tag=pre_tag,
            build_tag=build_tag,
        )

update_card(card)

Update an artifact card based on current registry

Parameters:

Name Type Description Default
card Card

Card to register

required
Source code in opsml/registry/registry.py
234
235
236
237
238
239
240
241
242
def update_card(self, card: Card) -> None:
    """
    Update an artifact card based on current registry

    Args:
        card:
            Card to register
    """
    return self._registry.update_card(card=card)