def lookup(
        self,
        lookup_values: Dict[str, Any],
        extra_values: Optional[Dict[str, Any]] = None,
        pk: Optional[str] = "id",
        foreign_keys: Optional[ForeignKeysType] = None,
        column_order: Optional[List[str]] = None,
        not_null: Optional[Iterable[str]] = None,
        defaults: Optional[Dict[str, Any]] = None,
        extracts: Optional[Union[Dict[str, str], List[str]]] = None,
        conversions: Optional[Dict[str, str]] = None,
        columns: Optional[Dict[str, Any]] = None,
        strict: Optional[bool] = False,
    ):
        """
        Create or populate a lookup table with the specified values.

        ``db["Species"].lookup({"name": "Palm"})`` will create a table called ``Species``
        (if one does not already exist) with two columns: ``id`` and ``name``. It will
        set up a unique constraint on the ``name`` column to guarantee it will not
        contain duplicate rows.

        It will then insert a new row with the ``name`` set to ``Palm`` and return the
        new integer primary key value.

        An optional second argument can be provided with more ``name: value`` pairs to
        be included only if the record is being created for the first time. These will
        be ignored on subsequent lookup calls for records that already exist.

        All other keyword arguments are passed through to ``.insert()``.

        See :ref:`python_api_lookup_tables` for more details.

        :param lookup_values: Dictionary specifying column names and values to use for the lookup
        :param extra_values: Additional column values to be used only if creating a new record
        :param strict: Boolean, apply STRICT mode if creating the table.
        """
        assert isinstance(lookup_values, dict)
        if extra_values is not None:
            assert isinstance(extra_values, dict)
        combined_values = dict(lookup_values)
        if extra_values is not None:
            combined_values.update(extra_values)
        if self.exists():
            self.add_missing_columns([combined_values])
            unique_column_sets = [set(i.columns) for i in self.indexes]
            if set(lookup_values.keys()) not in unique_column_sets:
                self.create_index(lookup_values.keys(), unique=True)
            wheres = ["[{}] = ?".format(column) for column in lookup_values]
            rows = list(
                self.rows_where(
                    " and ".join(wheres), [value for _, value in lookup_values.items()]
                )
            )
            try:
                return rows[0][pk]
            except IndexError:
                return self.insert(
                    combined_values,
                    pk=pk,
                    foreign_keys=foreign_keys,
                    column_order=column_order,
                    not_null=not_null,
                    defaults=defaults,
                    extracts=extracts,
                    conversions=conversions,
                    columns=columns,
                    strict=strict,
                ).last_pk
        else:
            pk = self.insert(
                combined_values,
                pk=pk,
                foreign_keys=foreign_keys,
                column_order=column_order,
                not_null=not_null,
                defaults=defaults,
                extracts=extracts,
                conversions=conversions,
                columns=columns,
                strict=strict,
            ).last_pk
            self.create_index(lookup_values.keys(), unique=True)
            return pk

        Create or populate a lookup table with the specified values.

        ``db["Species"].lookup({"name": "Palm"})`` will create a table called ``Species``
        (if one does not already exist) with two columns: ``id`` and ``name``. It will
        set up a unique constraint on the ``name`` column to guarantee it will not
        contain duplicate rows.

        It will then insert a new row with the ``name`` set to ``Palm`` and return the
        new integer primary key value.

        An optional second argument can be provided with more ``name: value`` pairs to
        be included only if the record is being created for the first time. These will
        be ignored on subsequent lookup calls for records that already exist.

        All other keyword arguments are passed through to ``.insert()``.

        See :ref:`python_api_lookup_tables` for more details.

        :param lookup_values: Dictionary specifying column names and values to use for the lookup
        :param extra_values: Additional column values to be used only if creating a new record
        :param strict: Boolean, apply STRICT mode if creating the table.
        
sqlite_utils/db.py