{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true,
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "# Enumerations\n",
    "LinkML supports a wide spectrum of enumerations, from simple option lists to values come directly from\n",
    "ontologies or pre-defined value sets."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-04-02T22:12:23.610899Z",
     "iopub.status.busy": "2021-04-02T22:12:23.610318Z",
     "iopub.status.idle": "2021-04-02T22:12:25.354337Z",
     "shell.execute_reply": "2021-04-02T22:12:25.354736Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "!pip install -q --disable-pip-version-check yamlmagic\n",
    "%reload_ext yamlmagic"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "Helper function to compile linkml model to python"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2021-04-02T22:12:25.361154Z",
     "iopub.status.busy": "2021-04-02T22:12:25.360496Z",
     "iopub.status.idle": "2021-04-02T22:12:25.959995Z",
     "shell.execute_reply": "2021-04-02T22:12:25.960446Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "from linkml.generators.pythongen import PythonGenerator\n",
    "from linkml.utils.compile_python import compile_python\n",
    "from linkml_model.meta import SchemaDefinition\n",
    "\n",
    "def compile_model(model: str, print_python: bool=False) -> SchemaDefinition:\n",
    "    gen = PythonGenerator(model, merge_imports=False, gen_classvars=False, gen_slots=False)\n",
    "    code = gen.serialize()\n",
    "    if print_python:\n",
    "        print(code)\n",
    "    return compile_python(code)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The simplist form of a LinkML enumeration is a list of tag and (optional) values\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2021-04-02T22:12:25.970875Z",
     "iopub.status.busy": "2021-04-02T22:12:25.970135Z",
     "iopub.status.idle": "2021-04-02T22:12:25.978463Z",
     "shell.execute_reply": "2021-04-02T22:12:25.978813Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "\n",
       "            require(\n",
       "                [\n",
       "                    \"notebook/js/codecell\",\n",
       "                    \"codemirror/mode/yaml/yaml\"\n",
       "                ],\n",
       "                function(cc){\n",
       "                    cc.CodeCell.options_default.highlight_modes.magic_yaml = {\n",
       "                        reg: [\"^%%yaml\"]\n",
       "                    }\n",
       "                }\n",
       "            );\n",
       "            "
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%yaml model\n",
    "\n",
    "id: http://example.org/test/simple\n",
    "name: simple\n",
    "description: Very simple enumeration\n",
    "\n",
    "prefixes:\n",
    "  linkml: https://w3id.org/linkml/\n",
    "  play: http://example.org/test/play/\n",
    "  CS: http://ontologies-r.us/codesystem/\n",
    "\n",
    "default_range: string\n",
    "default_prefix: play\n",
    "\n",
    "\n",
    "imports:\n",
    "  - linkml:types\n",
    "\n",
    "\n",
    "enums:\n",
    "  Position:\n",
    "    description: Baseline enumeration -- simple code/value pairs, where the value (description) is optional\n",
    "    permissible_values:\n",
    "      a: top\n",
    "      b: middle\n",
    "      c: bottom\n",
    "      d:\n",
    "\n",
    "classes:\n",
    "    PositionalRecord:\n",
    "        description: The combination of a unique identifier and with an associated position\n",
    "        attributes:\n",
    "            id:\n",
    "                range: string\n",
    "                identifier: true\n",
    "            position:\n",
    "                range: Position\n",
    "                required: true"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-04-02T22:12:25.993748Z",
     "iopub.status.busy": "2021-04-02T22:12:25.984685Z",
     "iopub.status.idle": "2021-04-02T22:12:26.449712Z",
     "shell.execute_reply": "2021-04-02T22:12:26.450215Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "# Auto generated from None by pythongen.py version: 0.9.0\n",
      "# Generation date: 2021-04-02 17:12\n",
      "# Schema: simple\n",
      "#\n",
      "# id: http://example.org/test/simple\n",
      "# description: Very simple enumeration\n",
      "# license: https://creativecommons.org/publicdomain/zero/1.0/\n",
      "\n",
      "import dataclasses\n",
      "import sys\n",
      "import re\n",
      "from typing import Optional, List, Union, Dict, ClassVar, Any\n",
      "from dataclasses import dataclass\n",
      "from linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions\n",
      "\n",
      "from linkml_runtime.utils.slot import Slot\n",
      "from linkml_runtime.utils.metamodelcore import empty_list, empty_dict, bnode\n",
      "from linkml_runtime.utils.yamlutils import YAMLRoot, extended_str, extended_float, extended_int\n",
      "from linkml_runtime.utils.dataclass_extensions_376 import dataclasses_init_fn_with_kwargs\n",
      "from linkml_runtime.utils.formatutils import camelcase, underscore, sfx\n",
      "from linkml_runtime.utils.enumerations import EnumDefinitionImpl\n",
      "from rdflib import Namespace, URIRef\n",
      "from linkml_runtime.utils.curienamespace import CurieNamespace\n",
      "from linkml_model.types import String\n",
      "\n",
      "metamodel_version = \"1.7.0\"\n",
      "\n",
      "# Overwrite dataclasses _init_fn to add **kwargs in __init__\n",
      "dataclasses._init_fn = dataclasses_init_fn_with_kwargs\n",
      "\n",
      "# Namespaces\n",
      "CS = CurieNamespace('CS', 'http://ontologies-r.us/codesystem/')\n",
      "LINKML = CurieNamespace('linkml', 'https://w3id.org/linkml/')\n",
      "PLAY = CurieNamespace('play', 'http://example.org/test/play/')\n",
      "DEFAULT_ = PLAY\n",
      "\n",
      "\n",
      "# Types\n",
      "\n",
      "# Class references\n",
      "class PositionalRecordId(extended_str):\n",
      "    pass\n",
      "\n",
      "\n",
      "@dataclass\n",
      "class PositionalRecord(YAMLRoot):\n",
      "    \"\"\"\n",
      "    The combination of a unique identifier and with an associated position\n",
      "    \"\"\"\n",
      "    id: Union[str, PositionalRecordId] = None\n",
      "    position: Union[str, \"Position\"] = None\n",
      "\n",
      "    def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):\n",
      "        if self.id is None:\n",
      "            raise ValueError(\"id must be supplied\")\n",
      "        if not isinstance(self.id, PositionalRecordId):\n",
      "            self.id = PositionalRecordId(self.id)\n",
      "\n",
      "        if self.position is None:\n",
      "            raise ValueError(\"position must be supplied\")\n",
      "        if not isinstance(self.position, Position):\n",
      "            self.position = Position(self.position)\n",
      "\n",
      "        super().__post_init__(**kwargs)\n",
      "\n",
      "\n",
      "# Enumerations\n",
      "class Position(EnumDefinitionImpl):\n",
      "    \"\"\"\n",
      "    Baseline enumeration -- simple code/value pairs, where the value (description) is optional\n",
      "    \"\"\"\n",
      "    a = PermissibleValue(text=\"a\",\n",
      "                         description=\"top\")\n",
      "    b = PermissibleValue(text=\"b\",\n",
      "                         description=\"middle\")\n",
      "    c = PermissibleValue(text=\"c\",\n",
      "                         description=\"bottom\")\n",
      "    d = PermissibleValue(text=\"d\")\n",
      "\n",
      "    _defn = EnumDefinition(\n",
      "        name=\"Position\",\n",
      "        description=\"Baseline enumeration -- simple code/value pairs, where the value (description) is optional\",\n",
      "    )\n",
      "\n",
      "# Slots\n",
      "\n",
      "----------------------------------------\n",
      "PositionalRecord(id='my location', position=(text='a', description='top'))\n",
      "a: top\n",
      "(text='a', description='top')\n",
      "Unknown Position enumeration code: z\n"
     ]
    }
   ],
   "source": [
    "module = compile_model(model, print_python=True)\n",
    "# Create a record selecting position \"a\" (top)\n",
    "print('-' * 40)\n",
    "c1 = module.PositionalRecord('my location', 'a')\n",
    "print(str(c1))\n",
    "print(str(c1.position))\n",
    "print(repr(c1.position))\n",
    "try:\n",
    "    c2 = module.PositionalRecord('your location', 'z')\n",
    "except ValueError as e:\n",
    "    print(e)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## Note that non-python values still work, although they won't appear in the auto-complete section"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2021-04-02T22:12:26.453951Z",
     "iopub.status.busy": "2021-04-02T22:12:26.453343Z",
     "iopub.status.idle": "2021-04-02T22:12:26.461051Z",
     "shell.execute_reply": "2021-04-02T22:12:26.460642Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "\n",
       "            require(\n",
       "                [\n",
       "                    \"notebook/js/codecell\",\n",
       "                    \"codemirror/mode/yaml/yaml\"\n",
       "                ],\n",
       "                function(cc){\n",
       "                    cc.CodeCell.options_default.highlight_modes.magic_yaml = {\n",
       "                        reg: [\"^%%yaml\"]\n",
       "                    }\n",
       "                }\n",
       "            );\n",
       "            "
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%yaml model2\n",
    "\n",
    "id: http://example.org/test/simple\n",
    "name: simple\n",
    "description: Enumeration with some non-std values\n",
    "\n",
    "prefixes:\n",
    "  linkml: https://w3id.org/linkml/\n",
    "  play: http://example.org/test/play/\n",
    "\n",
    "default_range: string\n",
    "default_prefix: play\n",
    "\n",
    "imports:\n",
    "  - linkml:types\n",
    "\n",
    "classes:\n",
    "    Sample:\n",
    "        attributes:\n",
    "            key:\n",
    "                range: string\n",
    "                identifier: true\n",
    "            enumentry:\n",
    "                range: Unusual enum patterns\n",
    "                multivalued: true\n",
    "\n",
    "enums:\n",
    "  Unusual enum patterns:\n",
    "    description: Very odd enumeration\n",
    "    permissible_values:\n",
    "      M: Normal selection\n",
    "      1: Numeric selection\n",
    "      def: Python reserved word\n",
    "      \"embedded space\": Embedded space\n",
    "      \"% ! -- whoo\": Really weird stuff"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2021-04-02T22:12:26.472248Z",
     "iopub.status.busy": "2021-04-02T22:12:26.471385Z",
     "iopub.status.idle": "2021-04-02T22:12:26.628402Z",
     "shell.execute_reply": "2021-04-02T22:12:26.628829Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "# Auto generated from None by pythongen.py version: 0.9.0\n",
      "# Generation date: 2021-04-02 17:12\n",
      "# Schema: simple\n",
      "#\n",
      "# id: http://example.org/test/simple\n",
      "# description: Enumeration with some non-std values\n",
      "# license: https://creativecommons.org/publicdomain/zero/1.0/\n",
      "\n",
      "import dataclasses\n",
      "import sys\n",
      "import re\n",
      "from typing import Optional, List, Union, Dict, ClassVar, Any\n",
      "from dataclasses import dataclass\n",
      "from linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions\n",
      "\n",
      "from linkml_runtime.utils.slot import Slot\n",
      "from linkml_runtime.utils.metamodelcore import empty_list, empty_dict, bnode\n",
      "from linkml_runtime.utils.yamlutils import YAMLRoot, extended_str, extended_float, extended_int\n",
      "from linkml_runtime.utils.dataclass_extensions_376 import dataclasses_init_fn_with_kwargs\n",
      "from linkml_runtime.utils.formatutils import camelcase, underscore, sfx\n",
      "from linkml_runtime.utils.enumerations import EnumDefinitionImpl\n",
      "from rdflib import Namespace, URIRef\n",
      "from linkml_runtime.utils.curienamespace import CurieNamespace\n",
      "from linkml_model.types import String\n",
      "\n",
      "metamodel_version = \"1.7.0\"\n",
      "\n",
      "# Overwrite dataclasses _init_fn to add **kwargs in __init__\n",
      "dataclasses._init_fn = dataclasses_init_fn_with_kwargs\n",
      "\n",
      "# Namespaces\n",
      "LINKML = CurieNamespace('linkml', 'https://w3id.org/linkml/')\n",
      "PLAY = CurieNamespace('play', 'http://example.org/test/play/')\n",
      "DEFAULT_ = PLAY\n",
      "\n",
      "\n",
      "# Types\n",
      "\n",
      "# Class references\n",
      "class SampleKey(extended_str):\n",
      "    pass\n",
      "\n",
      "\n",
      "@dataclass\n",
      "class Sample(YAMLRoot):\n",
      "    key: Union[str, SampleKey] = None\n",
      "    enumentry: Optional[Union[Union[str, \"UnusualEnumPatterns\"], List[Union[str, \"UnusualEnumPatterns\"]]]] = empty_list()\n",
      "\n",
      "    def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):\n",
      "        if self.key is None:\n",
      "            raise ValueError(\"key must be supplied\")\n",
      "        if not isinstance(self.key, SampleKey):\n",
      "            self.key = SampleKey(self.key)\n",
      "\n",
      "        if self.enumentry is None:\n",
      "            self.enumentry = []\n",
      "        if not isinstance(self.enumentry, list):\n",
      "            self.enumentry = [self.enumentry]\n",
      "        self.enumentry = [v if isinstance(v, UnusualEnumPatterns) else UnusualEnumPatterns(v) for v in self.enumentry]\n",
      "\n",
      "        super().__post_init__(**kwargs)\n",
      "\n",
      "\n",
      "# Enumerations\n",
      "class UnusualEnumPatterns(EnumDefinitionImpl):\n",
      "    \"\"\"\n",
      "    Very odd enumeration\n",
      "    \"\"\"\n",
      "    M = PermissibleValue(text=\"M\",\n",
      "                         description=\"Normal selection\")\n",
      "\n",
      "    _defn = EnumDefinition(\n",
      "        name=\"UnusualEnumPatterns\",\n",
      "        description=\"Very odd enumeration\",\n",
      "    )\n",
      "\n",
      "    @classmethod\n",
      "    def _addvals(cls):\n",
      "        setattr(cls, \"1\",\n",
      "                PermissibleValue(text=\"1\",\n",
      "                                 description=\"Numeric selection\") )\n",
      "        setattr(cls, \"def\",\n",
      "                PermissibleValue(text=\"def\",\n",
      "                                 description=\"Python reserved word\") )\n",
      "        setattr(cls, \"embedded space\",\n",
      "                PermissibleValue(text=\"embedded space\",\n",
      "                                 description=\"Embedded space\") )\n",
      "        setattr(cls, \"% ! -- whoo\",\n",
      "                PermissibleValue(text=\"% ! -- whoo\",\n",
      "                                 description=\"Really weird stuff\") )\n",
      "\n",
      "# Slots\n",
      "\n",
      "Sample(key='Something', enumentry=[(text='M', description='Normal selection'), (text='% ! -- whoo', description='Really weird stuff')])\n"
     ]
    }
   ],
   "source": [
    "module = compile_model(model2, print_python=True)\n",
    "t = module.Sample(\"Something\", [module.UnusualEnumPatterns.M, module.UnusualEnumPatterns['% ! -- whoo']])\n",
    "print(str(t))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## Enumerations can be \"bound\" to external URI's\n",
    "The URI's can be emitted as Curies OR URI's (your choice)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2021-04-02T22:12:26.632775Z",
     "iopub.status.busy": "2021-04-02T22:12:26.632033Z",
     "iopub.status.idle": "2021-04-02T22:12:26.640492Z",
     "shell.execute_reply": "2021-04-02T22:12:26.641236Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "\n",
       "            require(\n",
       "                [\n",
       "                    \"notebook/js/codecell\",\n",
       "                    \"codemirror/mode/yaml/yaml\"\n",
       "                ],\n",
       "                function(cc){\n",
       "                    cc.CodeCell.options_default.highlight_modes.magic_yaml = {\n",
       "                        reg: [\"^%%yaml\"]\n",
       "                    }\n",
       "                }\n",
       "            );\n",
       "            "
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%yaml model3\n",
    "id: http://example.org/test/simple\n",
    "name: simple\n",
    "description: Very simple enumeration\n",
    "\n",
    "prefixes:\n",
    "  linkml: https://w3id.org/linkml/\n",
    "  play: http://example.org/test/play/\n",
    "  SCT: http://snomed.info/id/\n",
    "\n",
    "default_range: string\n",
    "default_prefix: play\n",
    "\n",
    "imports:\n",
    "  - linkml:types\n",
    "\n",
    "enums:\n",
    "  Colors:\n",
    "    description: Color values, mapped to SNOMED CT\n",
    "    permissible_values:\n",
    "      1:\n",
    "        description: Red\n",
    "        meaning: SCT:371240000\n",
    "      2:\n",
    "        description: Yellow\n",
    "        meaning: SCT:371244009\n",
    "      3:\n",
    "        meaning: SCT:405738005\n",
    "      4:\n",
    "        description: Muted\n",
    "        meaning: SCT:abcde\n",
    "        notes:\n",
    "            - Note that CURIEs are not validated if a code_set is not specified\n",
    "      9:\n",
    "        description: Muddy\n",
    "        notes:\n",
    "            - \"There isn't a SCT map for this term\"\n",
    "\n",
    "classes:\n",
    "    FavoriteColor:\n",
    "        attributes:\n",
    "            id:\n",
    "                range: string\n",
    "                identifier: true\n",
    "            position:\n",
    "                range: Colors\n",
    "                required: true"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-04-02T22:12:26.653244Z",
     "iopub.status.busy": "2021-04-02T22:12:26.652590Z",
     "iopub.status.idle": "2021-04-02T22:12:26.799644Z",
     "shell.execute_reply": "2021-04-02T22:12:26.800034Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "FavoriteColor(id='Harold', position=(text='2', description='Yellow', meaning='http://snomed.info/id/371244009'))\n",
      "2: Yellow\n",
      "http://snomed.info/id/371244009\n",
      "http://snomed.info/id/abcde\n"
     ]
    }
   ],
   "source": [
    "module = compile_model(model3)\n",
    "colorrec = module.FavoriteColor(\"Harold\", module.Colors['2'])\n",
    "print(colorrec)\n",
    "print(str(colorrec.position))\n",
    "print(colorrec.position.meaning)\n",
    "cr2 = module.FavoriteColor(\"Donald\", module.Colors['4'])\n",
    "print(cr2.position.meaning)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "# Enumerations and code sets\n",
    "Enumerations have additional variables that reference code sets (i.e. a list of URI's)\n",
    "* `code_set` - A curie that references a list of URI's. Can be:\n",
    "    * The URI of an entire ontology, which uses the 'CS' prefix (e.g. `CS:SCT`, `CS:HP`, ...)\n",
    "    * The URI of a code set definition (see: TCCM model)\n",
    "* `code_set_tag` - the (case insensitive) tag assigned to a particular release of a code set by a terminology service.\n",
    "   If default tag, if neither a tag or a version is supplied, is \"Current\", the meaning of which\n",
    "   is assigned by the service.\n",
    "* `code_set_version` - a (case sensitive) version identifier that names a specific code set.\n",
    "\n",
    "In the example above, the URI, `http://snomed.info/id/abcde` was accepted as a valid meaning\n",
    "association.  If, however, we supply the SNOMED CT Code set (CS:SCT):\n",
    "\n",
    "1. The enumeration will only accept _valid_ SCT URI's and\n",
    "2. Any valid SCT URI will automatically provide the description and, if appropriate, its description\n",
    "\n",
    "*Note: Need to make it clear that CS:HP is actually shorthand for a CodeSetDefinition that states ALL codes from\n",
    "the CURRENT version of CS:HP.*\n",
    "\n",
    "*Note 2: Also same thing for descendents of:  SCT:74400008 == NS1:SCTAppendicites(root=SCT:744000008, rel=descendants)*"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
