How can I connect a texture node to a material with Python? The Setup:

I was trying to create a material and a texture, and then attach the texture to the material's Displacement slot, but I couldn't find the texture node's outputs through the Python api.

Here's my test scene:

import os, bpy

bpy.context.scene.render.engine = 'CYCLES'

# new texture
texture=bpy.data.textures.new('tex', 'IMAGE')
texture.use_nodes = True
texnodes = texture.node_tree.nodes

# new material
mat = bpy.data.materials.new('mat')
mat.use_nodes = True
matnodes = mat.node_tree.nodes

# assign texture to material's displacement
disp = matnodes['Material Output'].inputs['Displacement']
mat.node_tree.links.new(disp, texnodes["Output"].color)


That last line threw the error:


  TypeError: NodeLinks.new(): error with argument 2, "output" -  Function.output expected a NodeSocket type, not Color


I've seen this method used elsewhere:

mat.node_tree.links.new(disp, texture.outputs[0])


But if I try it in this situation, it throws this error:


  AttributeError: 'ImageTexture' object has no attribute 'outputs'


???

The Plot Twist:

Then I discovered that if I create the texture directly on the material, like so:

texture=matnodes.new("ShaderNodeTexImage")


...the texture.outputs[0] method works, although then the texture node claims it doesn't have a node_tree property.

Questions:

What's the difference between these two textures? Is there a way to make my first try work like the second? What's the best way to do this?
The cool thing about Blender is you can try stuff in the Python Console and see exactly what's happening!  

The first way is NOT creating a node that can be used in a shader.  It's simply creating a texture that Blender can use for various things.  



You don't want to say use_nodes=True because it doesn't make this texture into a node that can be used in a shader anyway, it just makes the Image inaccessible via the UI (if you care about that):



Anyway, this is NOT a node for a shader graph, it's JUST a texture.  Therefore, even with the use_nodes=True, the "color" output gives you simply the RGB values of the color, and you can't connect that to a node socket (and that's why you get the error message).

&gt;&gt;&gt; print (texnodes["Output"].color)
&lt;Color (r=0.6080, g=0.6080, b=0.6080)&gt;


The second way is actually creating a node for a shader network.  It does have outputs that are socket connections, so that's why you can connect it.  

When you run this:

&gt;&gt;&gt; mat = bpy.data.materials.new('mat')
&gt;&gt;&gt; mat.use_nodes = True


Then in your Node Editor you can actually see it:



And you run this:

&gt;&gt;&gt; texture=matnodes.new("ShaderNodeTexImage")


The node pops up:



...and in the console:

&gt;&gt;&gt; print (texture.outputs.keys())
['Color', 'Alpha']


you can see that the outputs look like what you see in the node, and the type is a nice socket connection type instead of an RGB value:

&gt;&gt;&gt; print (texture.outputs["Color"])
&lt;bpy_struct, NodeSocketColor("Color")&gt;


BUT, if you notice in our node, there actually is no image.  THAT'S how we can tie this to the first method.  So supposing we had created a texture via the first method added an image to it, then:

&gt;&gt;&gt; print (firstway.image)
&lt;bpy_struct, Image("01.PNG")&gt;


...the .image property holds the image file from the texture.  To use that in our texture node, you simply set the node .image equal to this one:

&gt;&gt;&gt; texture.image = firstway.image


...and you can see in the UI that it's now set in the node:



The nice thing is that you can re-use the texture in other shaders or nodes if you want to. 

I hope this helps to understand the difference.  TL;DR is that a Texture NODE contains a Texture, but a Texture cannot be used as a node itself.