Dffield bindings (#185)

* Add bindings for Projection

* Address review comments

* cargo fmt

* datafusion_common::DFField bindings
diff --git a/Cargo.lock b/Cargo.lock
index 7c61090..5059afa 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -605,9 +605,9 @@
 
 [[package]]
 name = "cxx"
-version = "1.0.90"
+version = "1.0.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90d59d9acd2a682b4e40605a242f6670eaa58c5957471cbf85e8aa6a0b97a5e8"
+checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62"
 dependencies = [
  "cc",
  "cxxbridge-flags",
@@ -617,9 +617,9 @@
 
 [[package]]
 name = "cxx-build"
-version = "1.0.90"
+version = "1.0.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebfa40bda659dd5c864e65f4c9a2b0aff19bea56b017b9b77c73d3766a453a38"
+checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690"
 dependencies = [
  "cc",
  "codespan-reporting",
@@ -632,15 +632,15 @@
 
 [[package]]
 name = "cxxbridge-flags"
-version = "1.0.90"
+version = "1.0.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "457ce6757c5c70dc6ecdbda6925b958aae7f959bda7d8fb9bde889e34a09dc03"
+checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf"
 
 [[package]]
 name = "cxxbridge-macro"
-version = "1.0.90"
+version = "1.0.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebf883b7aacd7b2aeb2a7b338648ee19f57c140d4ee8e52c68979c6b2f7f2263"
+checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/datafusion/__init__.py b/datafusion/__init__.py
index 4667835..b6cd517 100644
--- a/datafusion/__init__.py
+++ b/datafusion/__init__.py
@@ -36,6 +36,7 @@
 )
 
 from .common import (
+    DFField,
     DFSchema,
 )
 
@@ -61,6 +62,7 @@
     "TableScan",
     "Projection",
     "DFSchema",
+    "DFField",
 ]
 
 
diff --git a/datafusion/tests/test_imports.py b/datafusion/tests/test_imports.py
index 7bdbd83..e5d9585 100644
--- a/datafusion/tests/test_imports.py
+++ b/datafusion/tests/test_imports.py
@@ -27,6 +27,7 @@
 )
 
 from datafusion.common import (
+    DFField,
     DFSchema,
 )
 
@@ -57,7 +58,7 @@
     for klass in [Expr, Projection, TableScan]:
         assert klass.__module__ == "datafusion.expr"
 
-    for klass in [DFSchema]:
+    for klass in [DFField, DFSchema]:
         assert klass.__module__ == "datafusion.common"
 
 
diff --git a/src/common.rs b/src/common.rs
index ba4438e..8a8e2ad 100644
--- a/src/common.rs
+++ b/src/common.rs
@@ -24,5 +24,10 @@
 /// Initializes the `common` module to match the pattern of `datafusion-common` https://docs.rs/datafusion-common/18.0.0/datafusion_common/index.html
 pub(crate) fn init_module(m: &PyModule) -> PyResult<()> {
     m.add_class::<df_schema::PyDFSchema>()?;
+    m.add_class::<df_field::PyDFField>()?;
+    m.add_class::<data_type::PyDataType>()?;
+    m.add_class::<data_type::DataTypeMap>()?;
+    m.add_class::<data_type::PythonType>()?;
+    m.add_class::<data_type::SqlType>()?;
     Ok(())
 }
diff --git a/src/common/data_type.rs b/src/common/data_type.rs
index 8ada1c7..e07805c 100644
--- a/src/common/data_type.rs
+++ b/src/common/data_type.rs
@@ -31,7 +31,7 @@
 /// to map those types and provide a simple place for developers
 /// to map types from one system to another.
 #[derive(Debug, Clone)]
-#[pyclass(name = "DataTypeMap", module = "datafusion", subclass)]
+#[pyclass(name = "DataTypeMap", module = "datafusion.common", subclass)]
 pub struct DataTypeMap {
     #[allow(dead_code)]
     arrow_type: PyDataType,
@@ -419,7 +419,7 @@
 /// Since `DataType` exists in another package we cannot make that happen here so we wrap
 /// `DataType` as `PyDataType` This exists solely to satisfy those constraints.
 #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
-#[pyclass(name = "DataType", module = "datafusion")]
+#[pyclass(name = "DataType", module = "datafusion.common")]
 pub struct PyDataType {
     data_type: DataType,
 }
@@ -438,7 +438,7 @@
 
 /// Represents the possible Python types that can be mapped to the SQL types
 #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
-#[pyclass(name = "PythonType", module = "datafusion")]
+#[pyclass(name = "PythonType", module = "datafusion.common")]
 pub enum PythonType {
     Array,
     Bool,
@@ -458,7 +458,7 @@
 #[allow(non_camel_case_types)]
 #[allow(clippy::upper_case_acronyms)]
 #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
-#[pyclass(name = "SqlType", module = "datafusion")]
+#[pyclass(name = "SqlType", module = "datafusion.common")]
 pub enum SqlType {
     ANY,
     ARRAY,
diff --git a/src/common/df_field.rs b/src/common/df_field.rs
index 098df9b..fa65e04 100644
--- a/src/common/df_field.rs
+++ b/src/common/df_field.rs
@@ -15,27 +15,92 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use datafusion::arrow::datatypes::Field;
+use datafusion::arrow::datatypes::DataType;
+use datafusion_common::DFField;
 use pyo3::prelude::*;
 
-use crate::common::data_type::DataTypeMap;
+use super::data_type::PyDataType;
 
 /// PyDFField wraps an arrow-datafusion `DFField` struct type
 /// and also supplies convenience methods for interacting
 /// with the `DFField` instance in the context of Python
-#[pyclass(name = "DFField", module = "datafusion", subclass)]
+#[pyclass(name = "DFField", module = "datafusion.common", subclass)]
 #[derive(Debug, Clone)]
 pub struct PyDFField {
-    /// Optional qualifier (usually a table or relation name)
-    #[allow(dead_code)]
-    qualifier: Option<String>,
-    #[allow(dead_code)]
-    name: String,
-    #[allow(dead_code)]
-    data_type: DataTypeMap,
-    /// Arrow field definition
-    #[allow(dead_code)]
-    field: Field,
-    #[allow(dead_code)]
-    index: usize,
+    field: DFField,
+}
+
+impl From<PyDFField> for DFField {
+    fn from(py_field: PyDFField) -> DFField {
+        py_field.field
+    }
+}
+
+impl From<DFField> for PyDFField {
+    fn from(field: DFField) -> PyDFField {
+        PyDFField { field }
+    }
+}
+
+#[pymethods]
+impl PyDFField {
+    #[new]
+    #[pyo3(signature = (qualifier=None, name="", data_type=DataType::Int64.into(), nullable=false))]
+    fn new(qualifier: Option<&str>, name: &str, data_type: PyDataType, nullable: bool) -> Self {
+        PyDFField {
+            field: DFField::new(qualifier, name, data_type.into(), nullable),
+        }
+    }
+
+    // TODO: Need bindings for Array `Field` first
+    // #[staticmethod]
+    // #[pyo3(name = "from")]
+    // fn py_from(field: Field) -> Self {}
+
+    // TODO: Need bindings for Array `Field` first
+    // #[staticmethod]
+    // #[pyo3(name = "from_qualified")]
+    // fn py_from_qualified(field: Field) -> Self {}
+
+    #[pyo3(name = "name")]
+    fn py_name(&self) -> PyResult<String> {
+        Ok(self.field.name().clone())
+    }
+
+    #[pyo3(name = "data_type")]
+    fn py_data_type(&self) -> PyResult<PyDataType> {
+        Ok(self.field.data_type().clone().into())
+    }
+
+    #[pyo3(name = "is_nullable")]
+    fn py_is_nullable(&self) -> PyResult<bool> {
+        Ok(self.field.is_nullable())
+    }
+
+    #[pyo3(name = "qualified_name")]
+    fn py_qualified_name(&self) -> PyResult<String> {
+        Ok(self.field.qualified_name())
+    }
+
+    // TODO: Need bindings for `Column` first
+    // #[pyo3(name = "qualified_column")]
+    // fn py_qualified_column(&self) -> PyResult<PyColumn> {}
+
+    // TODO: Need bindings for `Column` first
+    // #[pyo3(name = "unqualified_column")]
+    // fn py_unqualified_column(&self) -> PyResult<PyColumn> {}
+
+    #[pyo3(name = "qualifier")]
+    fn py_qualifier(&self) -> PyResult<Option<&String>> {
+        Ok(self.field.qualifier())
+    }
+
+    // TODO: Need bindings for Arrow `Field` first
+    // #[pyo3(name = "field")]
+    // fn py_field(&self) -> PyResult<Field> {}
+
+    #[pyo3(name = "strip_qualifier")]
+    fn py_strip_qualifier(&self) -> PyResult<Self> {
+        Ok(self.field.clone().strip_qualifier().into())
+    }
 }