Calling some OpenGL functions from Python with pyglet can be a bit tricky due to fact that the functions are only thin wrappers around the C-API.
My sample program demonstrates some calls for using modern OpenGL with pyglet. The program is intentionally quite flat, without any classes. In a larger program I would manage the involved OpenGL objects with Python classes.

This is not a full tutorial, but some aspects of the sample program are explained below.
Pyglet
I used Pyglet because it includes an OpenGL binding and supports Python 3.
pyglet provides an object-oriented programming interface for developing games and other visually-rich applications for Windows, Mac OS X and Linux.
Call by Reference
Many OpenGL calls use a call by reference / pointer for returning values. These can be called using ctypes.byref
.
The example first creates a GLuint initialized to zero and then passes a pointer to glGenBuffers.
from pyglet import gl
import ctypes
vertexbuffer = gl.GLuint(0)
gl.glGenBuffers(1, ctypes.byref(vertexbuffer))
Structs for Vertex Attributes
Using a struct for passing vertex attributes can take advantage of the meta-information provided by ctypes. It is also easier to manage than a flat array of float values. In the example the vertex shader takes a vec2 and a vec4 as attributes.
Note that as in the example above I use the type aliases provided by pyglet.gl in order to ensure compatible types with OpenGL.
In ctypes you create an array by multiplying the type object.
class VERTEX(ctypes.Structure):
_fields_ = [
('position', gl.GLfloat * 2),
('color', gl.GLfloat * 4),
]
The structure fields are given an offset attribute which can be used for passing to glVertexAttribPointer
gl.glVertexAttribPointer(loc_position, 2, gl.GL_FLOAT, False,
ctypes.sizeof(VERTEX),
ctypes.c_void_p(VERTEX.position.offset))
gl.glVertexAttribPointer(loc_color, 4, gl.GL_FLOAT, False,
ctypes.sizeof(VERTEX),
ctypes.c_void_p(VERTEX.color.offset))
And the structure is initialized very easily; here I create an array of three vertices, each containing a 2D position and a 4D color.
data = (VERTEX * 3)(((-0.6, -0.5), (1.0, 0.0, 0.0, 1.0)),
(( 0.6, -0.5), (0.0, 1.0, 0.0, 1.0)),
(( 0.0, 0.5), (0.0, 0.0, 1.0, 1.0)))
gl.glBufferData(gl.GL_ARRAY_BUFFER, ctypes.sizeof(data),
data, gl.GL_DYNAMIC_DRAW)
String Arguments
OpenGL expects C-style strings, so it is easiest to use byte strings.
ctypes has string buffers which can tranlate between bytes
in Python and char*
in C.
loc_position = gl.glGetAttribLocation(program,
ctypes.create_string_buffer(b'position'))
They can also be used for retrieving strings from OpenGL such as the log of a shader compilation.
length = gl.GLint(0)
gl.glGetShaderiv(shader_name, gl.GL_INFO_LOG_LENGTH, ctypes.byref(length))
log_buffer = ctypes.create_string_buffer(length.value)
gl.glGetShaderInfoLog(shader_name, length, None, log_buffer)
Finding out about getting the compilation log really helped me when writing my own shaders.
The code for passing the shader source to OpenGL is still somewhat messy with a ctypes cast. I would be glad if you can suggest a better alternative in the comments.
src_buffer = ctypes.create_string_buffer(shader_source)
buf_pointer = ctypes.cast(ctypes.pointer(ctypes.pointer(src_buffer)),
ctypes.POINTER(ctypes.POINTER(ctypes.c_char)))
length = ctypes.c_int(len(shader_source) + 1)
gl.glShaderSource(shader_name, 1, buf_pointer, ctypes.byref(length))