Part 7: Slots and inheritance#

Slots and attributes#

Previously we have seen examples of schemas that declare fields/slots using an attributes slot under the relevant class.

In LinkML, “slots” (aka fields) are first-class entities that can be declared outside of classes. The attribute syntax is just a convenient layer on this, but explicit declaration of slots is often preferred for reuse.

(and yes, because LinkML is self-describing, we also talk about metamodel slots)

Let’s see this in action:

personinfo.yaml:

id: https://w3id.org/linkml/examples/personinfo
name: personinfo
prefixes:                                 
  linkml: https://w3id.org/linkml/
  schema: http://schema.org/
  personinfo: https://w3id.org/linkml/examples/personinfo/
  ORCID: https://orcid.org/
imports:
  - linkml:types
default_range: string
  
classes:
  Person:
    class_uri: schema:Person             
    slots:   ## specified as a list
     - id
     - full_name
     - aliases
     - phone
     - age
    id_prefixes:
      - ORCID
  Container:
    attributes:
      persons:
        multivalued: true
        inlined_as_list: true
        range: Person

# slots are first-class entities in the metamodel
# declaring them here allows them to be reused elsewhere
slots:
  id:
    identifier: true
  full_name:
    required: true
    description:
      name of the person
    slot_uri: schema:name
  aliases:
    multivalued: true
    description:
      other names for the person
  phone:
    pattern: "^[\\d\\(\\)\\-]+$"
    slot_uri: schema:telephone 
  age:
    range: integer
    minimum_value: 0
    maximum_value: 200

The JSON-Schema that is generated should be the same as in the previous example:

gen-json-schema personinfo.yaml

And just as a reminder, we can look at the UML for the schema:

gen-yuml personinfo.yaml

img

See FAQ: attributes vs slots

Inheritance#

Now let’s say we want to extend the schema, and we want to include another class Organization. Organizations may have different properties from people, but they may also share slots in common - for example, having a unique identifier and a name

LinkML provides mechanisms for inheritance/polymorphism, we can define a “superclass” called NamedThing, put shared slots in there, and inherit from it.

We also introduce a concept called “mixins” here, which allows for multiple inheritance:

personinfo-with-inheritance.yaml:

id: https://w3id.org/linkml/examples/personinfo
name: personinfo
prefixes:                                 
  linkml: https://w3id.org/linkml/
  schema: http://schema.org/
  personinfo: https://w3id.org/linkml/examples/personinfo/
  ORCID: https://orcid.org/
imports:
  - linkml:types
default_range: string
  
classes:

  ## --
  ## New parent class
  ## --
  NamedThing:
    abstract: true     ## should not be instantiated directly
    slots: 
     - id
     - full_name
    close_mappings:
     - schema:Thing
  
  Person:
    is_a: NamedThing  ## inheritance
    mixins:
      - HasAliases
    class_uri: schema:Person             
    slots:            ## note we only have slots specific to people
     - phone
     - age
    id_prefixes:
      - ORCID

  Organization:
    description: >-
      An organization such as a company or university
    is_a: NamedThing
    class_uri: schema:Organization
    mixins:
      - HasAliases
    slots:
      - mission_statement
      
  Container:
    tree_root: true
    attributes:
      persons:
        multivalued: true
        inlined_as_list: true
        range: Person
      organizations:
        multivalued: true
        inlined_as_list: true
        range: Organization

  ## --
  ## New mixin class
  ## --
  HasAliases:
    description: >-
      A mixin applied to any class that can have aliases/alternateNames
    mixin: true
    attributes:
      aliases:
        multivalued: true
        exact_mappings:
          - schema:alternateName

slots:
  id:
    identifier: true
  full_name:
    required: true
    description:
      name of the person
    slot_uri: schema:name
  aliases:
    multivalued: true
    description:
      other names for the person
  phone:
    pattern: "^[\\d\\(\\)\\-]+$"
    slot_uri: schema:telephone 
  age:
    range: integer
    minimum_value: 0
    maximum_value: 200
  mission_statement:

Note that our container object now contains two kinds of lists: people and organization

Here is the schema:

gen-yuml personinfo-with-inheritance.yaml

Renders as:

img

Let’s take a look at the JSON schema:

gen-json-schema personinfo-with-inheritance.yaml > personinfo-with-inheritance.schema.json

You can see that even though JSON-Schema doesn’t support inheritance, slots from is-a parents and mixins are “rolled down” to their children:

{
  "Person": {
    "additionalProperties": false,
    "description": "",
    "properties": {
      "age": {
        "type": "integer"
      },
      "aliases": {
        "items": {
          "type": "string"
        },
        "type": "array"
      },
      "full_name": {
        "description": "name of the person",
        "type": "string"
      },
      "id": {
        "type": "string"
      },
      "phone": {
        "pattern": "^[\\d\\(\\)\\-]+$",
        "type": "string"
      }
    },
    "required": [
      "id",
      "full_name"
    ],
    "title": "Person",
    "type": "object"
  }
}

Customizing slots in the context of classes: Slot Usage#

LinkML gives you the ability to reuse or inherit slots while customizing them for use in a particular class, using slot_usage

First let’s create a schema where we introduce a new general class Relationship

slot-usage-example.yaml:

id: https://w3id.org/linkml/examples/personinfo
name: personinfo
prefixes:                                 
  linkml: https://w3id.org/linkml/
imports:
  - linkml:types
default_range: string
  
classes:
  NamedThing:
    slots: 
     - id
     - full_name
  Person:
    is_a: NamedThing
    attributes:
      has_familial_relationships:
        multivalued: true
        range: FamilialRelationship
        inlined_as_list: true
      has_organizational_relationships:
        multivalued: true
        range: OrganizationalRelationship
        inlined_as_list: true
    
  Organization:
    is_a: NamedThing

  Relationship:
    abstract: true
    attributes:
      duration:
        range: integer
      related_to:
        range: NamedThing
      relationship_type:

  FamilialRelationship:
    is_a: Relationship
    slot_usage:
      related_to:
        range: Person
        required: true
      relationship_type:
        range: FamilialRelationshipType
      

  OrganizationalRelationship:
    is_a: Relationship
    slot_usage:
      related_to:
        range: Organization
        required: true
      relationship_type:
        range: OrganizationalRelationshipType

  Container:
    attributes:
      persons:
        multivalued: true
        inlined_as_list: true
        range: Person
      organizations:
        multivalued: true
        inlined_as_list: true
        range: Organization

slots:
  id:
    identifier: true
  full_name:
  related_to:
  relationship_type:

enums:
  FamilialRelationshipType:
    permissible_values:
      SIBLING_OF:
      PARENT_OF:
      CHILD_OF:
  OrganizationalRelationshipType:
    permissible_values:
      EMPLOYED_BY:
      FOUNDED_BY:
      LEADER_OF:
      MEMBER_OF:
gen-yuml slot-usage-example.yaml

img

Here we have a fairly generic class Relationship that holds a relationship a person can hold to another entity such as another person or an organization.

there are two subclasses, or for personal relationships (e.g. siblings) and other for person-to-organization relationships. these use the same generic slots (duration, relationship)type, and related_to). However, the latter two are constrained in a class-specific way.

data.yaml:

persons:
  - id: ORCID:1234
    full_name: Superman
    has_organizational_relationships:
      - related_to: ROR:1
        relationship_type: MEMBER_OF
  - id: ORCID:3000
    full_name: Jor El
    has_familial_relationships:
      - related_to: ORCID:1234
        relationship_type: PARENT_OF
organizations:
  - id: ROR:1
    full_name: Justice League
linkml-validate data.yaml -s slot-usage-example.yaml