Actually the pigpio Python module can do this quite easily for a single stepper.

You would use the wave functions in combination with wave chains.

Say you want a ramp of 20ms, 10ms, 5ms, 2ms, 1ms.

You could create separate waves with 10ms on 10 ms off, 5 ms on 5 ms off, ..., 500 µs on, 500 µs off.  You could then use a wave chain to send the first wave 100 times, the second 90 times, the third x times, etc.

Here is some code which may help show the sort of thing I mean.  It is for a stepper driver which requires pulsing the separate stepper coils.  If you are using a pulse/direction stepper driver it would be quite a bit simpler.

<!-- language: lang-py -->

    #!/usr/bin/env python
    
    import pigpio # http://abyz.me.uk/rpi/pigpio/python.html
    
    class stepper:
       """
       A stepper class to return step pulses.
       """
    
       def __init__(self, pi, g1, g2, g3, g4, delay=1000):
          """
          """
          self._pi = pi
    
          self._g1 = g1
          self._g2 = g2
          self._g3 = g3
          self._g4 = g4
    
          self._delay = delay
    
          self._all = (1<<g1 | 1<<g2 | 1<<g3 | 1<<g4)
    
          self._g1_mode = pi.get_mode(g1)
          self._g2_mode = pi.get_mode(g2)
          self._g3_mode = pi.get_mode(g3)
          self._g4_mode = pi.get_mode(g4)
    
          pi.set_mode(g1, pigpio.OUTPUT)
          pi.set_mode(g2, pigpio.OUTPUT)
          pi.set_mode(g3, pigpio.OUTPUT)
          pi.set_mode(g4, pigpio.OUTPUT)
    
          self._forward_wid = None
          self._backward_wid = None
    
          self._forward_wave_init(delay)
          self._backward_wave_init(delay)
    
       def _step_on(self, pos):
          if   pos == 0: return (1<<self._g4)
          elif pos == 1: return (1<<self._g3 | 1<<self._g4)
          elif pos == 2: return (1<<self._g3)
          elif pos == 3: return (1<<self._g2 | 1<<self._g3)
          elif pos == 4: return (1<<self._g2)
          elif pos == 5: return (1<<self._g1 | 1<<self._g2)
          elif pos == 6: return (1<<self._g1)
          elif pos == 7: return (1<<self._g4 | 1<<self._g1)
          else:          return 0
    
       def _step_off(self, pos):
          return self._step_on(pos) ^ self._all
    
       def _forward_wave_init(self, delay):
          if self._forward_wid is not None:
             self._pi.wave_delete(self._forward_wid)
             self._forward_wid = None
    
          p=[] # pulses to drive stepper 1 cycle forward
    
          for i in range(8):
             p.append(pigpio.pulse(self._step_on(i), self._step_off(i), delay))
    
          self._pi.wave_add_generic(p)
    
          self._forward_wid = self._pi.wave_create()
    
       def _backward_wave_init(self, delay):
          if self._backward_wid is not None:
             self._pi.wave_delete(self._backward_wid)
             self._backward_wid = None
    
          p=[] # pulses to drive stepper 1 cycle backward
    
          for i in range(8):
             p.append(pigpio.pulse(self._step_on(7-i), self._step_off(7-i), delay))
    
          self._pi.wave_add_generic(p)
    
          self._backward_wid = self._pi.wave_create()
    
       def forward_wid(self):
          return self._forward_wid
    
       def backward_wid(self):
          return self._backward_wid
    
       def set_delay(self, delay):
          self._forward_wave_init(delay)
          self._backward_wave_init(delay)
    
       def cancel(self):
          self._pi.wave_delete(self._forward_wid)
          self._pi.wave_delete(self._backward_wid)
    
          self._pi.set_mode(self._g1, self._g1_mode)
          self._pi.set_mode(self._g2, self._g2_mode)
          self._pi.set_mode(self._g3, self._g3_mode)
          self._pi.set_mode(self._g4, self._g4_mode)
    
    if __name__ == "__main__":
    
       import time
    
       pi = pigpio.pi()
    
       pi.wave_clear()
    
       delay = 2000
    
       s1 = stepper(pi,  2,  3,  4, 17, delay)
       s2 = stepper(pi, 14, 15, 18, 23, delay)
    
       for i in range(5):
    
          pi.wave_chain([
             255, 0, s1._forward_wid, s2._backward_wid, 255, 1, 0, 2,
             255, 0, s2._forward_wid, s1._backward_wid, 255, 1, 0, 2,
             ])
    
          while pi.wave_tx_busy():
             time.sleep(0.1)
    
          delay -= 200
    
          s1.set_delay(delay)
          s2.set_delay(delay)
    
       s1.cancel()
       s2.cancel()
    
       pi.stop()