Inserts record into the provided table from the database. Returns inserted record as
        list, str or series depending on the value of `astype`.

        Parameters
        ----------
        tname : str
            Table to insert records into.
        where : dict or None (default `None`)
            Dictionary of <column, value> where value can be of str type for exact match or a
            compiled regex expression for more advanced matching.
        where_not : dict or None (default `None`)
            Identical to `where` but for negative-matching.
        columns: list of str, str or None (default `None`)
            Column(s) to return for the inserted records.
        astype: str, type or None (default `None`)
            Type to cast the output to. Possible values are: `nonetype`, `series`, `str`, `dict`
            `json`. If this is `None`, falls back to the type provided to the constructor.
            If a type was provided to the constructor but the user wants to avoid any casting,
            "nonetype" should be passed as the value.

        Returns
        -------
        record : str, dict or series
            Inserted record. Output type depends on `astype` parameter.

        Examples
        --------
        >>> db = PandasDatabase("test")
        >>> db.insert("test", record={"Name": "John"})
            Name                                      John
            __id__    dc876999-1f5b-4262-b6bf-c23b875f3a54
            dtype: object

def insert(self, tname, record=None, columns=None, astype=None):
        '''
        Inserts record into the provided table from the database. Returns inserted record as
        list, str or series depending on the value of `astype`.

        Parameters
        ----------
        tname : str
            Table to insert records into.
        where : dict or None (default `None`)
            Dictionary of <column, value> where value can be of str type for exact match or a
            compiled regex expression for more advanced matching.
        where_not : dict or None (default `None`)
            Identical to `where` but for negative-matching.
        columns: list of str, str or None (default `None`)
            Column(s) to return for the inserted records.
        astype: str, type or None (default `None`)
            Type to cast the output to. Possible values are: `nonetype`, `series`, `str`, `dict`
            `json`. If this is `None`, falls back to the type provided to the constructor.
            If a type was provided to the constructor but the user wants to avoid any casting,
            "nonetype" should be passed as the value.

        Returns
        -------
        record : str, dict or series
            Inserted record. Output type depends on `astype` parameter.

        Examples
        --------
        >>> db = PandasDatabase("test")
        >>> db.insert("test", record={"Name": "John"})
            Name                                      John
            __id__    dc876999-1f5b-4262-b6bf-c23b875f3a54
            dtype: object
        '''
        tname = self._check_tname(tname)
        record = PandasDatabase._check_dict_type(str, str, record, cast=self.auto_cast)
        columns = PandasDatabase._check_type_iter(str, columns)
        record[self._id_colname] = str(uuid.uuid4())

        # If a row generation function exists for this table, use that
        record_new = {}
        if tname in self._rowgens:
            self._print('Using row generator to create new record in "%s"' % tname)
            record_new = self._rowgens[tname]()

        # Set as many fields as provided in new record, leave the rest as-is
        if record is not None:
            for field_key, field_val in record.items():
                record_new[field_key] = field_val

        with self._lock:
            self._print('Inserting new record into "%s": %r' % (tname, record_new))
            self._update_schema(tname, record_new.keys())

            row = Series(record_new)
            self._db[tname].loc[len(self._db[tname])] = row

        # Save the changes to disk if required
        if self.auto_save:
            self.save()

        if len(columns) > 0:
            row = row[columns]

        return self._output(row, single=True, astype=astype)