based on https://learnopengl.com/Getting-started/Shaders

from OpenGL.GL import *
import glm, ctypes
 
class ShaderFromSource:
    def __init__(self, vertex_source, fragment_source):
        # Step 1 - creating vertex shader
        self.vertex = self.create_shader(GL_VERTEX_SHADER, vertex_source)
        # Step 2 - creating fragment shader
        self.fragment = self.create_shader(GL_FRAGMENT_SHADER, fragment_source)
        self.program = self.create_shader_program(self.vertex, self.fragment)
 
    def create_shader_program(self, vertex_shader, fragment_shader):
        # Step 3 - creating shader program
        shader_program = glCreateProgram()
        # Step 4 - attaching vertex & fragment shaders
        glAttachShader(shader_program, vertex_shader)
        glAttachShader(shader_program, fragment_shader)
        glBindFragDataLocation(shader_program, 0, "outColor")
        # Step 5 - linking shader program
        glLinkProgram(shader_program)
 
        return shader_program
 
    def create_shader(self, shader_type, source):
        """compile a shader"""
        shader = glCreateShader(shader_type)
        glShaderSource(shader, source)
        glCompileShader(shader)
        if glGetShaderiv(shader, GL_COMPILE_STATUS) != GL_TRUE:
            raise RuntimeError(glGetShaderInfoLog(shader))
 
        return shader
 
    def use(self):
        glUseProgram(self.program)
    def get_location(self, name):
        return glGetUniformLocation(self.program, name)
    def set_bool(self, name, value):
        glUniform1i(self.get_location(name), value)
    def set_int(self, name, value):
        glUniform1i(self.get_location(name), value)
    def set_float(self, name, value):
        glUniform1f(self.get_location(name), value)
    def _set_vector(self, name, vector_callback, points_callback, args):
        if(len(args) == 1):
            vector_callback(self.get_location(name), 1, glm.value_ptr(args[0]))
        else:
            points_callback(self.get_location(name), *(GLfloat * len(args))(*args))
    def set_vec2(self, *args):
        self._set_vector(args[0], glUniform2fv, glUniform2f, args[1:])
    def set_vec3(self, *args):
        self._set_vector(args[0], glUniform3fv, glUniform3f, args[1:])
    def set_vec4(self, *args):
        self._set_vector(args[0], glUniform4fv, glUniform4f, args[1:])
    def set_mat2(self, name, mat):
        glUniformMatrix2fv(self.get_location(name), 1, GL_FALSE, glm.value_ptr(mat))
    def set_mat3(self, name, mat):
        glUniformMatrix3fv(self.get_location(name), 1, GL_FALSE, glm.value_ptr(mat))
    def set_mat4(self, name, mat):
        glUniformMatrix4fv(self.get_location(name), 1, GL_FALSE, glm.value_ptr(mat))
 
class Shader(ShaderFromSource):
    def __init__(self, vertex_path, fragment_path):
        super().__init__(self.read_from_file(vertex_path), self.read_from_file(fragment_path))
 
    def read_from_file(self, path):
        # Loading shader source
        with open(path, 'r') as file:
            return file.read()