Показаны сообщения с ярлыком python. Показать все сообщения
Показаны сообщения с ярлыком python. Показать все сообщения

понедельник, 26 декабря 2016 г.

Хэш-функция ГОСТ 34.11-2012 на Python 2.7

Понадобилось тут, сам не знаю зачем... Так как я пару лет назад разбирался с этим вопросом, то решил не мудрить и почти дословно переписал.

Файл stribog.py:
#!/usr/bin/python
# -"- coding: UTF-8 -"-

class entry:
    """Вычисление хэш-функции по алгоритму ГОСТ 34.11-2012"""



    def __init__(self):
        pass



    def XOR (self, A, B):
        return [a^b for a, b in zip(A, B)]



    def plus (self, A, B):

        result = [0]*64

        i, remain = 63, 0
        while(i >= 0):
            s = A[i] + B[i] + remain
            remain = (s >> 8) if s >= 256 else 0
            result[i] = s & 0xFF
            i -= 1

        return result



    def P (self, A):

        tau = ( \
            0,  8, 16, 24, 32, 40, 48, 56, \
            1,  9, 17, 25, 33, 41, 49, 57, \
            2, 10, 18, 26, 34, 42, 50, 58, \
            3, 11, 19, 27, 35, 43, 51, 59, \
            4, 12, 20, 28, 36, 44, 52, 60, \
            5, 13, 21, 29, 37, 45, 53, 61, \
            6, 14, 22, 30, 38, 46, 54, 62, \
            7, 15, 23, 31, 39, 47, 55, 63)

        result = [0]*64
        for i in xrange(0, 64):
            result[i] = A[tau[i]]

        return result



    def S (self, A):
        Pi = ( \
            252, 238, 221,  17, 207, 110,  49,  22, 251, 196, 250, \
            218,  35, 197,   4,  77, 233, 119, 240, 219, 147,  46, \
            153, 186,  23,  54, 241, 187,  20, 205,  95, 193, 249, \
             24, 101,  90, 226,  92, 239,  33, 129,  28,  60,  66, \
            139,   1, 142,  79,   5, 132,   2, 174, 227, 106, 143, \
            160,   6,  11, 237, 152, 127, 212, 211,  31, 235,  52, \
             44,  81, 234, 200,  72, 171, 242,  42, 104, 162, 253, \
             58, 206, 204, 181, 112,  14,  86,   8,  12, 118,  18, \
            191, 114,  19,  71, 156, 183,  93, 135,  21, 161, 150, \
             41,  16, 123, 154, 199, 243, 145, 120, 111, 157, 158, \
            178, 177,  50, 117,  25,  61, 255,  53, 138, 126, 109, \
             84, 198, 128, 195, 189,  13,  87, 223, 245,  36, 169, \
             62, 168,  67, 201, 215, 121, 214, 246, 124,  34, 185, \
              3, 224,  15, 236, 222, 122, 148, 176, 188, 220, 232, \
             40,  80,  78,  51,  10,  74, 167, 151,  96, 115,  30, \
              0,  98,  68,  26, 184,  56, 130, 100, 159,  38,  65, \
            173,  69,  70, 146,  39,  94,  85,  47, 140, 163, 165, \
            125, 105, 213, 149,  59,   7,  88, 179,  64, 134, 172, \
             29, 247,  48,  55, 107, 228, 136, 217, 231, 137, 225, \
             27, 131,  73,  76,  63, 248, 254, 141,  83, 170, 144, \
            202, 216, 133,  97,  32, 113, 103, 164,  45,  43,   9, \
             91, 203, 155,  37, 208, 190, 229, 108,  82,  89, 166, \
            116, 210, 230, 244, 180, 192, 209, 102, 175, 194,  57, \
            75,  99, 182)

        result = [0]*64
        for i in xrange(0, 64):
            result[i] = Pi[A[i]]

        return result



    def L (self, A):

        l = ( \
            0x8e, 0x20, 0xfa, 0xa7, 0x2b, 0xa0, 0xb4, 0x70, \
            0x47, 0x10, 0x7d, 0xdd, 0x9b, 0x50, 0x5a, 0x38, \
            0xad, 0x08, 0xb0, 0xe0, 0xc3, 0x28, 0x2d, 0x1c, \
            0xd8, 0x04, 0x58, 0x70, 0xef, 0x14, 0x98, 0x0e, \
            0x6c, 0x02, 0x2c, 0x38, 0xf9, 0x0a, 0x4c, 0x07, \
            0x36, 0x01, 0x16, 0x1c, 0xf2, 0x05, 0x26, 0x8d, \
            0x1b, 0x8e, 0x0b, 0x0e, 0x79, 0x8c, 0x13, 0xc8, \
            0x83, 0x47, 0x8b, 0x07, 0xb2, 0x46, 0x87, 0x64, \
             \
            0xa0, 0x11, 0xd3, 0x80, 0x81, 0x8e, 0x8f, 0x40, \
            0x50, 0x86, 0xe7, 0x40, 0xce, 0x47, 0xc9, 0x20, \
            0x28, 0x43, 0xfd, 0x20, 0x67, 0xad, 0xea, 0x10, \
            0x14, 0xaf, 0xf0, 0x10, 0xbd, 0xd8, 0x75, 0x08, \
            0x0a, 0xd9, 0x78, 0x08, 0xd0, 0x6c, 0xb4, 0x04, \
            0x05, 0xe2, 0x3c, 0x04, 0x68, 0x36, 0x5a, 0x02, \
            0x8c, 0x71, 0x1e, 0x02, 0x34, 0x1b, 0x2d, 0x01, \
            0x46, 0xb6, 0x0f, 0x01, 0x1a, 0x83, 0x98, 0x8e, \
             \
            0x90, 0xda, 0xb5, 0x2a, 0x38, 0x7a, 0xe7, 0x6f, \
            0x48, 0x6d, 0xd4, 0x15, 0x1c, 0x3d, 0xfd, 0xb9, \
            0x24, 0xb8, 0x6a, 0x84, 0x0e, 0x90, 0xf0, 0xd2, \
            0x12, 0x5c, 0x35, 0x42, 0x07, 0x48, 0x78, 0x69, \
            0x09, 0x2e, 0x94, 0x21, 0x8d, 0x24, 0x3c, 0xba, \
            0x8a, 0x17, 0x4a, 0x9e, 0xc8, 0x12, 0x1e, 0x5d, \
            0x45, 0x85, 0x25, 0x4f, 0x64, 0x09, 0x0f, 0xa0, \
            0xac, 0xcc, 0x9c, 0xa9, 0x32, 0x8a, 0x89, 0x50, \
             \
            0x9d, 0x4d, 0xf0, 0x5d, 0x5f, 0x66, 0x14, 0x51, \
            0xc0, 0xa8, 0x78, 0xa0, 0xa1, 0x33, 0x0a, 0xa6, \
            0x60, 0x54, 0x3c, 0x50, 0xde, 0x97, 0x05, 0x53, \
            0x30, 0x2a, 0x1e, 0x28, 0x6f, 0xc5, 0x8c, 0xa7, \
            0x18, 0x15, 0x0f, 0x14, 0xb9, 0xec, 0x46, 0xdd, \
            0x0c, 0x84, 0x89, 0x0a, 0xd2, 0x76, 0x23, 0xe0, \
            0x06, 0x42, 0xca, 0x05, 0x69, 0x3b, 0x9f, 0x70, \
            0x03, 0x21, 0x65, 0x8c, 0xba, 0x93, 0xc1, 0x38, \
             \
            0x86, 0x27, 0x5d, 0xf0, 0x9c, 0xe8, 0xaa, 0xa8, \
            0x43, 0x9d, 0xa0, 0x78, 0x4e, 0x74, 0x55, 0x54, \
            0xaf, 0xc0, 0x50, 0x3c, 0x27, 0x3a, 0xa4, 0x2a, \
            0xd9, 0x60, 0x28, 0x1e, 0x9d, 0x1d, 0x52, 0x15, \
            0xe2, 0x30, 0x14, 0x0f, 0xc0, 0x80, 0x29, 0x84, \
            0x71, 0x18, 0x0a, 0x89, 0x60, 0x40, 0x9a, 0x42, \
            0xb6, 0x0c, 0x05, 0xca, 0x30, 0x20, 0x4d, 0x21, \
            0x5b, 0x06, 0x8c, 0x65, 0x18, 0x10, 0xa8, 0x9e, \
             \
            0x45, 0x6c, 0x34, 0x88, 0x7a, 0x38, 0x05, 0xb9, \
            0xac, 0x36, 0x1a, 0x44, 0x3d, 0x1c, 0x8c, 0xd2, \
            0x56, 0x1b, 0x0d, 0x22, 0x90, 0x0e, 0x46, 0x69, \
            0x2b, 0x83, 0x88, 0x11, 0x48, 0x07, 0x23, 0xba, \
            0x9b, 0xcf, 0x44, 0x86, 0x24, 0x8d, 0x9f, 0x5d, \
            0xc3, 0xe9, 0x22, 0x43, 0x12, 0xc8, 0xc1, 0xa0, \
            0xef, 0xfa, 0x11, 0xaf, 0x09, 0x64, 0xee, 0x50, \
            0xf9, 0x7d, 0x86, 0xd9, 0x8a, 0x32, 0x77, 0x28, \
             \
            0xe4, 0xfa, 0x20, 0x54, 0xa8, 0x0b, 0x32, 0x9c, \
            0x72, 0x7d, 0x10, 0x2a, 0x54, 0x8b, 0x19, 0x4e, \
            0x39, 0xb0, 0x08, 0x15, 0x2a, 0xcb, 0x82, 0x27, \
            0x92, 0x58, 0x04, 0x84, 0x15, 0xeb, 0x41, 0x9d, \
            0x49, 0x2c, 0x02, 0x42, 0x84, 0xfb, 0xae, 0xc0, \
            0xaa, 0x16, 0x01, 0x21, 0x42, 0xf3, 0x57, 0x60, \
            0x55, 0x0b, 0x8e, 0x9e, 0x21, 0xf7, 0xa5, 0x30, \
            0xa4, 0x8b, 0x47, 0x4f, 0x9e, 0xf5, 0xdc, 0x18, \
             \
            0x70, 0xa6, 0xa5, 0x6e, 0x24, 0x40, 0x59, 0x8e, \
            0x38, 0x53, 0xdc, 0x37, 0x12, 0x20, 0xa2, 0x47, \
            0x1c, 0xa7, 0x6e, 0x95, 0x09, 0x10, 0x51, 0xad, \
            0x0e, 0xdd, 0x37, 0xc4, 0x8a, 0x08, 0xa6, 0xd8, \
            0x07, 0xe0, 0x95, 0x62, 0x45, 0x04, 0x53, 0x6c, \
            0x8d, 0x70, 0xc4, 0x31, 0xac, 0x02, 0xa7, 0x36, \
            0xc8, 0x38, 0x62, 0x96, 0x56, 0x01, 0xdd, 0x1b, \
            0x64, 0x1c, 0x31, 0x4b, 0x2b, 0x8e, 0xe0, 0x83)

        result = [0]*64

        i = 7
        while(i >= 0):
            n = 0
            while(n <= 7):
                p = 63
                j = 7
                while(j >= 0):
                    k = 0
                    while(k <= 7):
                        if ((A[i*8+j]>>k) & 1):
                            result[i*8+n] ^= l[p*8+n]
                        p-=1
                        k+=1
                    j-=1
                n+=1
            i-=1

        return result



    def X (self, K, A):
        return self.XOR(K, A)



    def LPS (self, A):
        return self.L(self.P(self.S(A)))



    def E (self, K, M):

        C = ( \
            (0xb1, 0x08, 0x5b, 0xda, 0x1e, 0xca, 0xda, 0xe9, 0xeb, 0xcb, 0x2f, 0x81, 0xc0, 0x65, 0x7c, 0x1f, \
            0x2f, 0x6a, 0x76, 0x43, 0x2e, 0x45, 0xd0, 0x16, 0x71, 0x4e, 0xb8, 0x8d, 0x75, 0x85, 0xc4, 0xfc, \
            0x4b, 0x7c, 0xe0, 0x91, 0x92, 0x67, 0x69, 0x01, 0xa2, 0x42, 0x2a, 0x08, 0xa4, 0x60, 0xd3, 0x15, \
            0x05, 0x76, 0x74, 0x36, 0xcc, 0x74, 0x4d, 0x23, 0xdd, 0x80, 0x65, 0x59, 0xf2, 0xa6, 0x45, 0x07), \
            \
            (0x6f, 0xa3, 0xb5, 0x8a, 0xa9, 0x9d, 0x2f, 0x1a, 0x4f, 0xe3, 0x9d, 0x46, 0x0f, 0x70, 0xb5, 0xd7, \
            0xf3, 0xfe, 0xea, 0x72, 0x0a, 0x23, 0x2b, 0x98, 0x61, 0xd5, 0x5e, 0x0f, 0x16, 0xb5, 0x01, 0x31, \
            0x9a, 0xb5, 0x17, 0x6b, 0x12, 0xd6, 0x99, 0x58, 0x5c, 0xb5, 0x61, 0xc2, 0xdb, 0x0a, 0xa7, 0xca, \
            0x55, 0xdd, 0xa2, 0x1b, 0xd7, 0xcb, 0xcd, 0x56, 0xe6, 0x79, 0x04, 0x70, 0x21, 0xb1, 0x9b, 0xb7), \
            \
            (0xf5, 0x74, 0xdc, 0xac, 0x2b, 0xce, 0x2f, 0xc7, 0x0a, 0x39, 0xfc, 0x28, 0x6a, 0x3d, 0x84, 0x35, \
            0x06, 0xf1, 0x5e, 0x5f, 0x52, 0x9c, 0x1f, 0x8b, 0xf2, 0xea, 0x75, 0x14, 0xb1, 0x29, 0x7b, 0x7b, \
            0xd3, 0xe2, 0x0f, 0xe4, 0x90, 0x35, 0x9e, 0xb1, 0xc1, 0xc9, 0x3a, 0x37, 0x60, 0x62, 0xdb, 0x09, \
            0xc2, 0xb6, 0xf4, 0x43, 0x86, 0x7a, 0xdb, 0x31, 0x99, 0x1e, 0x96, 0xf5, 0x0a, 0xba, 0x0a, 0xb2), \
            \
            (0xef, 0x1f, 0xdf, 0xb3, 0xe8, 0x15, 0x66, 0xd2, 0xf9, 0x48, 0xe1, 0xa0, 0x5d, 0x71, 0xe4, 0xdd, \
            0x48, 0x8e, 0x85, 0x7e, 0x33, 0x5c, 0x3c, 0x7d, 0x9d, 0x72, 0x1c, 0xad, 0x68, 0x5e, 0x35, 0x3f, \
            0xa9, 0xd7, 0x2c, 0x82, 0xed, 0x03, 0xd6, 0x75, 0xd8, 0xb7, 0x13, 0x33, 0x93, 0x52, 0x03, 0xbe, \
            0x34, 0x53, 0xea, 0xa1, 0x93, 0xe8, 0x37, 0xf1, 0x22, 0x0c, 0xbe, 0xbc, 0x84, 0xe3, 0xd1, 0x2e), \
            \
            (0x4b, 0xea, 0x6b, 0xac, 0xad, 0x47, 0x47, 0x99, 0x9a, 0x3f, 0x41, 0x0c, 0x6c, 0xa9, 0x23, 0x63, \
            0x7f, 0x15, 0x1c, 0x1f, 0x16, 0x86, 0x10, 0x4a, 0x35, 0x9e, 0x35, 0xd7, 0x80, 0x0f, 0xff, 0xbd, \
            0xbf, 0xcd, 0x17, 0x47, 0x25, 0x3a, 0xf5, 0xa3, 0xdf, 0xff, 0x00, 0xb7, 0x23, 0x27, 0x1a, 0x16, \
            0x7a, 0x56, 0xa2, 0x7e, 0xa9, 0xea, 0x63, 0xf5, 0x60, 0x17, 0x58, 0xfd, 0x7c, 0x6c, 0xfe, 0x57), \
            \
            (0xae, 0x4f, 0xae, 0xae, 0x1d, 0x3a, 0xd3, 0xd9, 0x6f, 0xa4, 0xc3, 0x3b, 0x7a, 0x30, 0x39, 0xc0, \
            0x2d, 0x66, 0xc4, 0xf9, 0x51, 0x42, 0xa4, 0x6c, 0x18, 0x7f, 0x9a, 0xb4, 0x9a, 0xf0, 0x8e, 0xc6, \
            0xcf, 0xfa, 0xa6, 0xb7, 0x1c, 0x9a, 0xb7, 0xb4, 0x0a, 0xf2, 0x1f, 0x66, 0xc2, 0xbe, 0xc6, 0xb6, \
            0xbf, 0x71, 0xc5, 0x72, 0x36, 0x90, 0x4f, 0x35, 0xfa, 0x68, 0x40, 0x7a, 0x46, 0x64, 0x7d, 0x6e), \
            \
            (0xf4, 0xc7, 0x0e, 0x16, 0xee, 0xaa, 0xc5, 0xec, 0x51, 0xac, 0x86, 0xfe, 0xbf, 0x24, 0x09, 0x54, \
            0x39, 0x9e, 0xc6, 0xc7, 0xe6, 0xbf, 0x87, 0xc9, 0xd3, 0x47, 0x3e, 0x33, 0x19, 0x7a, 0x93, 0xc9, \
            0x09, 0x92, 0xab, 0xc5, 0x2d, 0x82, 0x2c, 0x37, 0x06, 0x47, 0x69, 0x83, 0x28, 0x4a, 0x05, 0x04, \
            0x35, 0x17, 0x45, 0x4c, 0xa2, 0x3c, 0x4a, 0xf3, 0x88, 0x86, 0x56, 0x4d, 0x3a, 0x14, 0xd4, 0x93), \
            \
            (0x9b, 0x1f, 0x5b, 0x42, 0x4d, 0x93, 0xc9, 0xa7, 0x03, 0xe7, 0xaa, 0x02, 0x0c, 0x6e, 0x41, 0x41, \
            0x4e, 0xb7, 0xf8, 0x71, 0x9c, 0x36, 0xde, 0x1e, 0x89, 0xb4, 0x44, 0x3b, 0x4d, 0xdb, 0xc4, 0x9a, \
            0xf4, 0x89, 0x2b, 0xcb, 0x92, 0x9b, 0x06, 0x90, 0x69, 0xd1, 0x8d, 0x2b, 0xd1, 0xa5, 0xc4, 0x2f, \
            0x36, 0xac, 0xc2, 0x35, 0x59, 0x51, 0xa8, 0xd9, 0xa4, 0x7f, 0x0d, 0xd4, 0xbf, 0x02, 0xe7, 0x1e), \
            \
            (0x37, 0x8f, 0x5a, 0x54, 0x16, 0x31, 0x22, 0x9b, 0x94, 0x4c, 0x9a, 0xd8, 0xec, 0x16, 0x5f, 0xde, \
            0x3a, 0x7d, 0x3a, 0x1b, 0x25, 0x89, 0x42, 0x24, 0x3c, 0xd9, 0x55, 0xb7, 0xe0, 0x0d, 0x09, 0x84, \
            0x80, 0x0a, 0x44, 0x0b, 0xdb, 0xb2, 0xce, 0xb1, 0x7b, 0x2b, 0x8a, 0x9a, 0xa6, 0x07, 0x9c, 0x54, \
            0x0e, 0x38, 0xdc, 0x92, 0xcb, 0x1f, 0x2a, 0x60, 0x72, 0x61, 0x44, 0x51, 0x83, 0x23, 0x5a, 0xdb), \
            \
            (0xab, 0xbe, 0xde, 0xa6, 0x80, 0x05, 0x6f, 0x52, 0x38, 0x2a, 0xe5, 0x48, 0xb2, 0xe4, 0xf3, 0xf3, \
            0x89, 0x41, 0xe7, 0x1c, 0xff, 0x8a, 0x78, 0xdb, 0x1f, 0xff, 0xe1, 0x8a, 0x1b, 0x33, 0x61, 0x03, \
            0x9f, 0xe7, 0x67, 0x02, 0xaf, 0x69, 0x33, 0x4b, 0x7a, 0x1e, 0x6c, 0x30, 0x3b, 0x76, 0x52, 0xf4, \
            0x36, 0x98, 0xfa, 0xd1, 0x15, 0x3b, 0xb6, 0xc3, 0x74, 0xb4, 0xc7, 0xfb, 0x98, 0x45, 0x9c, 0xed), \
            \
            (0x7b, 0xcd, 0x9e, 0xd0, 0xef, 0xc8, 0x89, 0xfb, 0x30, 0x02, 0xc6, 0xcd, 0x63, 0x5a, 0xfe, 0x94, \
            0xd8, 0xfa, 0x6b, 0xbb, 0xeb, 0xab, 0x07, 0x61, 0x20, 0x01, 0x80, 0x21, 0x14, 0x84, 0x66, 0x79, \
            0x8a, 0x1d, 0x71, 0xef, 0xea, 0x48, 0xb9, 0xca, 0xef, 0xba, 0xcd, 0x1d, 0x7d, 0x47, 0x6e, 0x98, \
            0xde, 0xa2, 0x59, 0x4a, 0xc0, 0x6f, 0xd8, 0x5d, 0x6b, 0xca, 0xa4, 0xcd, 0x81, 0xf3, 0x2d, 0x1b), \
            \
            (0x37, 0x8e, 0xe7, 0x67, 0xf1, 0x16, 0x31, 0xba, 0xd2, 0x13, 0x80, 0xb0, 0x04, 0x49, 0xb1, 0x7a, \
            0xcd, 0xa4, 0x3c, 0x32, 0xbc, 0xdf, 0x1d, 0x77, 0xf8, 0x20, 0x12, 0xd4, 0x30, 0x21, 0x9f, 0x9b, \
            0x5d, 0x80, 0xef, 0x9d, 0x18, 0x91, 0xcc, 0x86, 0xe7, 0x1d, 0xa4, 0xaa, 0x88, 0xe1, 0x28, 0x52, \
            0xfa, 0xf4, 0x17, 0xd5, 0xd9, 0xb2, 0x1b, 0x99, 0x48, 0xbc, 0x92, 0x4a, 0xf1, 0x1b, 0xd7, 0x20))

        result = self.X(K, M)

        i = 0
        while(i < 12):
            result = self.LPS(result)
            K = self.XOR(K, C[i])
            K = self.LPS(K)
            result = self.X(K, result)
            i+=1

        return result



    def g (self, N, h, m):

        result = self.XOR(h, N)
        result = self.LPS(result)

        result = self.E(result, m)
        result = self.XOR(result, h)
        result = self.XOR(result, m)

        return result



    def FromString (self, string, hashtype):

        h = [1 if hashtype == 256 else 0]*64

        e = [0]*64
        N = [0]*64
        Z = [0]*64
        m = [0]*64

        start = 0
        while start + 64 <= len(string):
            for i in xrange(0, 64):
                m[63-i] = ord(string[start+i])

            h = self.g(N, h, m)

            e[62] = (512 >> 8);
            e[63] = (512 & 0xFF);
            N = self.plus(N, e);

            Z = self.plus(Z, m);

            start += 64

        sz = len(string)-start
        m = [0]*64
        for i in xrange(0, sz):
            m[63-i] = ord(string[start+i])
        m[64-sz-1] = 1

        h = self.g(N, h, m)
        e[62] = (sz*8)>>8
        e[63] = (sz*8)&0xFF
        N = self.plus(N, e)
        Z = self.plus(Z, m)

        e[62] = 0
        e[63] = 0
        h = self.g(e, h, N)

        h = self.g(e, h, Z)

        return ''.join(reversed([('%0.2X' % a) for a in h][:(32 if hashtype == 256 else 64)]))

Пользоваться этим просто:
#!/usr/bin/python
# -"- coding: UTF-8 -"-

import stribog

print "test 1"
print stribog.entry().FromString('012345678901234567890123456789012345678901234567890123456789012', 512)
print stribog.entry().FromString('012345678901234567890123456789012345678901234567890123456789012', 256)

print "test 2"
print stribog.entry().FromString(open('stribog_test2.txt').read(), 512)
print stribog.entry().FromString(open('stribog_test2.txt').read(), 256)

четверг, 10 ноября 2016 г.

Python: округление

Выяснилась интересная особенность. В python 2.7 округление выполняется как учили в школе, где число x.5 округляется до (x+1):
>>> print round(0.5), round(1.5)
1.0 2.0

В python 3.3 поведение этой функции изменилось:
>>> print ( round(0.5), round(1.5) )
0 2
то есть, во-первых, по умолчанию возвращается целое число, и, главное, выполняется bankers rounding (банковское округление, в котором x.5 округляется до ближайшего четного).

Как эту печаль обойти штатными срадствами, не нашел, поэтому пока пользуюсь таким костыликом:
def round27 (number, ndigits=0):
    result = round(number, ndigits)
    if abs(result) >= abs(number): return result
    delta = abs(number) - abs(result)
    if ndigits >= 0:
        multiplier = 10.0**(ndigits+1)
        if abs(number)*multiplier-abs(result)*multiplier == 5.0:
            result = number + (delta if number >= 0 else -delta)
    else:
        multiplier = 10.0**(-ndigits-1)
        if abs(number)-abs(result) == 5.0*multiplier:
            result = number + (delta if number >= 0 else -delta)
    return result

Вообще, оказывается, есть даже стандарт IEEE 754, в котором все эти правила округления указаны. Надо глянуть, может, и ГОСТ найдётся.

UPD. Нашелся вот такой документ: СТ СЭВ 543-77 Числа. Правила записи и округления
Тут как раз округление по правилу "половины дальше от нуля".

четверг, 13 октября 2016 г.

Python: проблема с подключением к MSSQL при наличии триггера входа

История такова. Жил-был под линуксом скрипт, написанный когда-то на Python 2.7 и использующий pymssql, который успешно подключался к Microsoft SQL Server. В один прекрасный момент подключаться он перестал с ошибкой:
Logon failed for login 'huhmuh' due to trigger execution. DB-Lib error message 20018, severity 14:
General SQL Server error: Check messages from the SQL Server
DB-Lib error message 20002, severity 9:
Adaptive Server connection failed

Расследование выявило интересные вещи. Оказывается, pymssql использовал FreeTDS, который, собственно, и заглючил. Обмен FreeTDS можно подслушать примерно так:
import os
import pymssql

os.environ['TDSDUMP'] = 'stdout'

conn = pymssql.connect(host='mySQLserver', user='huhmuh', password='', charset='UTF-8', database='master', appname='my Application')
conn.close()
Анализ дампа выявил, что используется FreeTDS версии 0.91. Обновление до версии 1.00 не помогло.

При этом в соседнем каталоге лежит другая программа, написанная на C, которая через unixODBC получает данные с того же сервера без всяких проблем. Дальнейшие раскопки показали, что этот самый unixODBC взаимодействует с сервером через Microsoft SQL Server Native Client.

В конце концов оказалось, что на сервере разработчики добавили триггер входа. При наличии этого триггера FreeTDS не может нормально подключиться к базе данных.

Как перенацелить pymssql на использование нативного клиента, найти не удалось, поэтому пришлось перетаскивать скрипт на pyodbc. Тут тоже нашлась пара подводных камней. Самый крупный из которых - ошибка при выполнении пакета команд в одном запросе: Error: No results. Previous SQL was not a query. В этом случае виноваты, во-первых, всякие сообщения сервера "1 rows affected", которые легко подавить командой SET NOCOUNT ON в начале запроса, и, во-вторых, результаты работы команд print, причудливо раскиданных разработчиками внутри хранимых процедур, используемых в нашем пакете. Эти последние подавить нельзя, поэтому пришлось часть пакетов переписывать в виде группы отдельных запросов.

В общем, тот ещё опыт.

понедельник, 26 октября 2015 г.

Linux: PostgreSQL 9.x и сертификаты

Захотелось тут, чтобы определенный пользователь при авторизации в СУБД был избавлен от необходимости ввода пароля. Можно было бы указать, что соединения от имени этого пользователя - доверенные, но я почему-то предпочёл другой путь: авторизацию с использованием сертификата. Наверно, в последнее время слишком часто приходилось иметь с этими штуками дело, вот и решил пойти проторенной дорожкой.

Как я понял, PostgreSQL использует сертификаты в двух случаях: для установления защищенного соединения и для проверки подлинности пользователя. В любом случае используется информация:

1) адрес или имя сервера баз данных, по которому будет стучаться клиент, например, myserver

2) имя системного пользователя, который пытается авторизоваться, например, sysUser

3) имя пользователя базы данных, под которым пытается авторизоваться системный пользователь, например, pgUser

При этом затрагиваются следующие каталоги и файлы конфигурации:

1) На стороне сервера: файлы postgresql.conf, pg_hba.conf и pg_ident.conf. Эти файлы в зависимости от сборки могут располагаться в разных местах. Например, на дебиане они обнаружились в каталоге /etc/postgresql, а на слакваре при установке из исходных кодов обосновались в рабочем каталоге /usr/local/pgsql/data

2) На стороне клиента: каталог ~/.postgresql/, в который должна быть положена пара ключ-сертификат, использующаяся для проверки подлинности пользователя.

Итак, настраиваем все эти сертификаты. Так как я - по натуре нищеброд, то сертификаты у меня будут самоподписанные, а создавать их буду бесплатной утилитой openssl.

Сначала генерируем корневой сертификат, которым будем подписывать сертификаты сервера и клиента:
# генерируем приватный ключ
openssl genrsa -out root.key 1024

# создаем самоподписанный корневой сертификат
openssl req -new -x509 -days 1826 -key root.key -out root.crt
При этом будут заданы всякие вопросы, от ответов, я так понял, мало что зависит.

Затем при помощи этого корневого сертификата создаем сертификат сервера:
# генерируем приватный ключ
openssl genrsa -out server.key 1024

# создаем запрос сертификата
# тут надо внимательно отнестись к заполнению полей:
# в поле Common Name (CN) надо указать адрес или имя сервера баз данных
# (например, myserver)
openssl req -new -utf8 -key server.key -out server.csr

# из запроса создаём сертификат сервера, подписанный корневым сертификатом
openssl x509 -req -days 730 -in server.csr -CA root.crt -CAkey root.key -out server.crt

У нас получилось три ценных файла: root.crt, server.key, server.crt. Их нужно запихнуть в рабочий каталог postgresql (тот самый что-то-там/data, в котором при установке СУБД делалась команда initdb). При этом на файл server.key налагаются суровые ограничения по безопасности: он должен иметь владельцем пользователя, под которым работает СУБД, и уровень доступа -rw-------.

Теперь сгенерируем сертификат для клиента:
# генерируем приватный ключ
openssl genrsa -out postgresql.key 1024

# создаем запрос сертификата
# тут тоже надо внимательно отнестись к заполнению полей:
# в поле Common Name (CN) следует указать имя системного пользователя,
# для которого создаётся сертификат
# (например, sysUser)
openssl req -new -utf8 -key postgresql.key -out postgresql.csr

# из запроса создаём сертификат клиента, подписанный корневым сертификатом
openssl x509 -req -days 730 -in postgresql.csr -CA root.crt -CAkey root.key -out postgresql.crt -CAcreateserial

После этой процедуры два файла, postgresql.key и postgresql.crt, закидываем клиенту в папку ~/.postgresql/ и у файла postgresql.key опять-таки выставляем правильного владельца и права -rw-------.

Стоит отметить, что содержимое получившихся сертификатов, в частности, заполнение поля CN можно всегда посмотреть командой:
openssl x509 -in postgresql.crt -text -noout -nameopt oneline,-esc_msb,utf8

Так или иначе, осталось написать правильные настройки сервера баз данных:

1) в файле postgresql.conf проверяем, что включена опция ssl = on

2) прописываем в pg_hba.conf строку:
#TYPE    DATABASE  USER    ADDRESS   METHOD
hostssl  all       pgUser  myserver  cert clientcert=1 map=myMap
Тут мы указали, что соединение должно быть защищено ssl, для авторизации следует использовать сертификат клиента и при сопоставлении системного имени и имени пользователя баз данных будет использоваться запись с меткой myMap из файла pg_ident.conf

3) в файле pg_ident.conf прописываем строку:
#MAPNAME  SYSTEM-USERNAME  PG-USERNAME
myMap     sysUser          pgUser
На самом деле, как я понял, этот файл содержит довольно простую информацию: какому системному пользователю под каким именем СУБД разрешается подключаться к серверу. То есть, если пользователю позволено подключаться под разными именами, pg_ident.conf будет выглядеть так:
#MAPNAME  SYSTEM-USERNAME  PG-USERNAME
myMap     sysUser          pgUser1
myMap     sysUser          pgUser2

4) перезапускаем сервер PostgreSQL.

Процесс авторизации по этой схеме выглядит, насколько я понял, так:

1) Устанавливается защищенное соединение при помощи сертификатов server.key + server.crt

2) От клиента сервер получает сертификат postgresql.crt, из которого выясняет системное имя пользователя. Имя пользователя базы данных, под которым хочет зайти системный пользователь, указывается явно в строке подключения.

3) По имени пользователя базы данных, имени сервера и режиму соединения (hostssl) определяется строка в файле pg_hba.conf, из которой выясняется имя метки в файле pg_ident.conf.

4) По метке в файле pg_ident.conf устанавливается, может ли данный системный пользователь авторизоваться в СУБД под данным пользователем баз данных.

Вообще говоря, требование размещения клиентских сертификатов в каталоге ~/.postgresql/ не является таким уж обязательным. Просто их там по умолчанию ищет библиотека libpq. В произвольных же скриптах можно указывать явно, какие файлы использовать в качестве пары ключ-сертификат. Вот, например, скрипт на Python-е:
import psycopg2

connString = "host=myserver"             + \
             " user=pgUser"              + \
             " dbname=postgres"          + \
             " sslcert=./postgresql.crt" + \
             " sslkey=./postgresql.key"

conn = psycopg2.connect(connString)

понедельник, 17 февраля 2014 г.

Python: Числовые ребусы

Программка для разгадывания числовых ребусов. Методом грубой силы (т.е. простым перебором).

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import sys

#equation = u'удар+удар==драка'
#equation = u'кошка+кошка+кошка==собака'
#equation = u'кто+умён+тот==силён'
#equation = u'где*где-всюду==везде'
equation = u'масло+ноль==омлет'

def replace_all(text, srch, repl):
    for i in range(0, len(srch)):
        text = text.replace(srch[i], repl[i])
    return text

letters = []

for l in equation:
    if l not in ('+', '=', '-', '*'):
        if l not in letters:
            letters.append(l)

tops, counters = [], []
for i in range(0, len(letters)):
    tops.append(10 - i)
    counters.append(0)

canExit = False
while(not(canExit)):

    numbers = ['0','1','2','3','4','5','6','7','8','9']
    substs = []
    for i in range(0, len(tops)):
        substs.append(numbers[counters[i]])
        del numbers[counters[i]]

    text = replace_all(equation, letters, substs)
    try:
        if eval( text ):
            print text
    except:
        None

    N = 0
    inc = 1
    while (N < len(counters) and inc > 0):
        counters[N] += inc
        inc = 0
        if (counters[N] == tops[N]):
            counters[N] = 0
            inc = 1
            N += 1

    canExit = True
    for i in range(0, len(tops)):
        canExit &= (counters[i] == tops[i]-1)

воскресенье, 12 августа 2012 г.

SMS+python - закрывая тему

Наконец-то руки дошли разобраться с получением списка SMS-сообщений из моего Huawei E1550. Всё это дело (и отправку, и прием) свёл в один маленький модуль с претенциозным названием smsutils.py.

Пользоваться им совсем просто:
#!/usr/bin/python
# -*- coding: UTF-8 -*-

import smsutils

# отправляем SMS
smsutils.SendSMS('Привет, мир!', '79031234567', '/dev/ttyUSB0')

# читаем SMS с карты
result = smsutils.GetSMS('/dev/ttyUSB0')

for r in result:
    print r[0], ' ', r[1], ' ', r[2], '\n', r[3]

Исходный код smsutils.py таков:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
#
# Набор вспомогательных функций для работы с SMS
#
# большое спасибо:
#     http://www.varesano.net/blog/fabio/serial%20rs232%20connections%20python
#     http://www.dreamfabric.com/sms/

import serial, datetime, time, random


# Преобразование номера телефона в международном формате в формат SMS
#
# Исходная строка, содержащая телефон в международном формате 79130123456,
# дополняется справа символом F                                                            - 79130123456F,
# разбивается на пары символов                                                             - 79 13 01 23 45 6F,
# в каждой паре символы меняются местами                                                   - 97 31 10 32 54 F6,
# слева приписывается идентификатор международного формата (91)                            - 91 97 31 10 32 54 F6,
# слева приписывается количество цифр в телефоне, т.е. 11 в шестнадцатеричном формате (0B) - 0B 91 97 31 10 32 54 F6
#
# Возвращаемое значение - строка, содержащая закодированный номер телефона 0B919731103254F6
#
def PhoneNumberToSMS(number):
    number += 'F'
    result = '0B' + '91'
    i = 0
    while i < len(number):
        result += number[i+1] + number[i]
        i += 2
    return result


# Преобразование строки символов в формат SMS
#
# Каждый двухбайтовый юникодный символ в строке разбивается на пару байт,
# и формируется новая строка, состоящая из шестнадцатеричных представлений этих байтов
#
# Возвращаемое значение - строка, содержащая строку символов в формате SMS
#
def TextToSMS(text):
    b = text
    result = ''
    i = 0
    while i < len(b):
        o = ord(b[i])
        result += ("%0.2X" % (o/256)) + ("%0.2X" % (o%256))
        i += 1
    return result


# Восстановление номера телефона в международном формате в из формата SMS
#
# Исходная строка, содержащая закодированный телефон 9731103254F6
# разбивается на пары символов           - 97 31 10 32 54 F6,
# в каждой паре символы меняются местами - 79 13 01 23 46 6F,
# убирается символ F                     - 79130123456
#
# Возвращаемое значение - строка, содержащая номер телефона 79130123456
#
def SMSToPhoneNumber(data):
    result = ""
    i = 0
    while i < len(data):
        result += data[i+1] + data[i]
        i += 2
    return result[:-1]


# Восстановление строки символов в из формата SMS
#
# Исходная строка разбивается на четверки символов, которые преобразуются в целые числа
# и формируется строка, состоящая из соответствующих этим числам символов
#
# Возвращаемое значение - раскодированная строка
#
def SMSToText(text):
    result = u''
    i = 0
    while i+3 < len(text):
        result += unichr(int(text[i] + text[i+1] + text[i+2] + text[i+3],16))
        i += 4
    return result


# преобразование целого числа в строку из нулей и единиц, соответствующую его двоичной записи
# (использовалось для отладки)
def ByteToBitsString(byte, n):
    result = ''
    for i in range(0,n):
        if byte & (1 << i) != 0:
            result = '1' + result
        else:
            result = '0' + result
    return result


# Восстановление строки из её семибитного кода
#
# На вход подается закодированная строка  - 4DEA10
# эта строка разбивается на пары символов - 4D EA 10,
# каждая пара трактуется как шестнадцатиричное представление байта - 0x4D 0xEA 0x10 = 01001101 11101010 00010000
# из первого байта 01001101 берутся семь младших битов 1001101 и преобразуются в соответствующий символ ASCII - M
# оставшийся бит 0 дополняется слева шестью младшими битами второго байта 101010: 1010100 - T
# оставшиеся два бита 11 дополняются слева пятью младшими битами третьего байта 10000: 1000011 - С
# и т.д.
#
# Возвращаемое значение - раскодированная строка MTC
#
def Decode7bit(text):
    result = ''

    bytes = [int(text[i*2:i*2+2],16) for i in range(0,len(text)/2)]

    symbol = 0
    bits   = 0
    n      = 0

    while n < len(bytes):

        if bits == 7:
            result += chr(symbol)
            symbol = 0
            bits   = 0
        else:
            symbol += (bytes[n] & (0x7F >> bits)) << bits
            result += chr(symbol)
            symbol = (bytes[n] & (0x7F << (7-bits))) >> (7-bits)
            bits   = (8-7) + bits
            n += 1

    if bits > 0 and symbol != 0:
        result += chr(symbol)

    return result


# Класс для работы с часовыми поясами (см следующую функцию)
class smsTZ(datetime.tzinfo):
    hours = 0
    def __init__(self, h):
        self.hours = h
    def utcoffset(self, dt):
        return datetime.timedelta(hours=self.hours)
    def dst(self, dt):
        return datetime.timedelta(0)

# Восстановление даты и времени из их представления SMS
#
# На вход подается закодированная строка  - 11113131516461
# эта строка разбивается на пары символов - 11 11 31 31 51 64 61,
# в каждой паре символы меняются местами  - 11 11 13 13 15 46 16,
# получившиеся строки трактуются как шестнадцатиричные представления байтов - 0x11 0x11 0x13 0x13 0x15 0x46 0x16
# эти байты представляют собой соответственно год, месяц, день, часы, минуты, секунды, часовой пояс
# часовой пояс представляется как количество четвертей часа, т.е. 0x16 = GMT+4 (седьмой бит отвечает за знак)
#
# Возвращаемое значение - дата и время 2011-11-13 13:15:46+04:00
#
def SMSToTimeStamp(text):
    year   = int(text[1] + text[0]) + 2000
    month  = int(text[3] + text[2])
    day    = int(text[5] + text[4])
    hour   = int(text[7] + text[6])
    minute = int(text[9] + text[8])
    second = int(text[11] + text[10])
    tz     = int(text[13] + text[12])
    tz = ( (tz & 0x7F) if (tz & 0x80 == 0) else -(tz & 0x7F) ) / 4
    return datetime.datetime(year, month, day, hour, minute, second, 0, smsTZ(tz))


# Обмен с последовательным портом
def str_send (ser, textline):
    ser.write(textline)

    out = ''
    # let's wait one second before reading output (let's give device time to answer)
    N = 10
    while N > 0:
        time.sleep(1)
        while ser.inWaiting() > 0:
            out += ser.read(1)

        if ('OK' in out) or ('ERROR' in out) or ('>' in out):
            N = 1

        N -= 1

    return out


# отправка пин-кода в открытый порт
def SendPINToPort(ser, pin):
    str_send(ser, 'AT+CPIN="%s"\r' % (pin))


# отправка пин-кода модему
def SendPIN(serial_name, pin):

    # подключаемся к порту
    ser = serial.Serial(serial_name, 115200, timeout=1)
    ser.open()

    # отправляем пин-код
    SendPINToPort(ser, pin)

    # закрываем порт
    ser.close()


# отправка SMS-сообщения
def SendSMS(msg, phone, serial_name, pin=None):

    # если нечего или некуда отправлять, выходим
    if msg == '' or len(phone) != 11:
        return

    # декодируем сообщение в utf-8
    message = msg.decode('utf-8')

    # разрезаем сообщение на кусочки по 66 символов
    chunks = []
    if len(message) > 70:
        while len(message) > 66:
            chunks.append(message[:66])
            message = message[66:]
    if len(message) > 0:
        chunks.append(message)

    # инициализируем служебную информацию
    SMS_SUBMIT_PDU = "11"
    CSMS_reference_number = ""

    # если сообщение требует конкатенации SMS, то подправляем служебную информацию
    # и генерируем четырехсимвольный номер сообщения
    if len(chunks) > 1:
        SMS_SUBMIT_PDU = "51"
        CSMS_reference_number = "%0.4X" % random.randrange(1,65536)

    # подключаемся к порту
    ser = serial.Serial(serial_name, 115200, timeout=1)
    ser.open()

    # устанавливаем формат передачи сообщения - PDU
    status = str_send(ser, 'AT+CMGF=0\r')

    # если в ответ пришел текст, содержащий SIM PIN REQUIRED, значит, модему нужен пин-код
    if 'SIM PIN' in status:
        SendPINToPort(ser, pin)
        str_send(ser, 'AT+CMGF=0\r')


    # отсылаем сообщение по кусочкам
    i = 1
    for chunk in chunks:

        # кодируем кусочек
        emessage = TextToSMS(chunk)

        # если сообщение состоит из нескольких кусочков, то в каждом кусочке надо указать
        # номер сообщения, количество кусочков и порядковый номер кусочка (1,2,3 и т.д.)
        if CSMS_reference_number != "":
            emessage = "06" + "08" + "04" + CSMS_reference_number + \
            ("%0.2X" % len(chunks)) + ("%0.2X" % i) + emessage

        # готовим строку для отправки в порт
        sms =                             \
            "00" +                        \
            SMS_SUBMIT_PDU +              \
            "00" +                        \
            PhoneNumberToSMS(phone) +     \
            "00" +                        \
            "08" +                        \
            "AA" +                        \
            "%0.2X" % (len(emessage)/2) + \
            emessage

        # подготавливаем модем - передаем ему длину отправляемой строки
        str_send(ser, 'AT+CMGS=' + str(len(sms)/2-1) + '\r')

        # отправляем строку
        str_send(ser, sms + '\x1A')

        i += 1

    # закрываем порт
    ser.close()



# чтение SMS сообщений с сим-карты
#
# возвращаемое значение: список, состоящий из кортежей, каждый из которых содержит
# номер слота на сим-карте, в котором находится сообщение (0-19),
# номер телефона отправителя,
# дату отправления сообщения,
# текст сообщения
#
def GetSMS(serial_name, pin=None):

    result = []

    # подключаемся к порту
    ser = serial.Serial(serial_name, 115200, timeout=1)
    ser.open()

    # устанавливаем формат передачи сообщения - PDU
    status = str_send(ser, 'AT+CMGF=0\r')

    # если в ответ пришел текст, содержащий SIM PIN REQUIRED, значит, модему нужен пин-код
    if 'SIM PIN' in status:
        SendPINToPort(ser, pin)
        str_send(ser, 'AT+CMGF=0\r')

    # запрашиваем список сообщений (4 - все сообщения)
    messages = str_send(ser, 'AT+CMGL=4\r')

    if 'ERROR' not in messages:

        strings = messages.split('\n')

        i = 0

        while i < len(strings):

            if '+CMGL: ' in strings[i]:

                message_header = strings[i][7:]
                message_body = strings[i+1]

                offset = 0

                SMSC_length = int(message_body[offset:offset+2],16)
                offset += 2

                SMSC_address = message_body[offset:offset+2*SMSC_length]
                SMSC_typeOfAddress = SMSC_address[:2]
                SMSC_serviceCenterNumber = SMSToPhoneNumber( SMSC_address[2:] )
                offset += 2*SMSC_length

                SMS_deliverBits = int(message_body[offset:offset+2],16)
                offset += 2

                SMS_senderNumberLength = int(message_body[offset:offset+2],16)
                offset += 2

                SMS_senderNumberType = message_body[offset:offset+2]
                offset += 2

                SMS_senderNumber = message_body[offset:offset+SMS_senderNumberLength+(1 if SMS_senderNumberLength & 1 != 0 else 0) ]
                if SMS_senderNumberType == '91':
                    SMS_senderNumber = SMSToPhoneNumber(SMS_senderNumber)
                if int(SMS_senderNumberType[0],16) & 5 == 5:
                    SMS_senderNumber = Decode7bit(SMS_senderNumber)
                offset += SMS_senderNumberLength+(1 if SMS_senderNumberLength & 1 != 0 else 0)

                TP_protocolIdentifier = message_body[offset:offset+2]
                offset += 2

                TP_dataCodingScheme = int(message_body[offset:offset+2],16)
                offset += 2

                TP_serviceCenterTimeStamp = SMSToTimeStamp(message_body[offset:offset+14])
                offset += 14

                TP_userDataLength = int(message_body[offset:offset+2],16)
                offset += 2

                if SMS_deliverBits & 64 != 0:
                    SMS_userDataHeaderLength = int(message_body[offset:offset+2],16)
                    offset += 2
                    SMS_userDataHeader = message_body[offset:offset+2*SMS_userDataHeaderLength]
                    offset += 2*SMS_userDataHeaderLength

                message_text = None
                if (TP_dataCodingScheme == 0):
                    message_text = Decode7bit(message_body[offset:])
                if (TP_dataCodingScheme & 8 != 0):
                    message_text = SMSToText(message_body[offset:])
                if message_text is None:
                    message_text = message_body[offset:]

                # добавляем в результирующий список кортеж, содержащий
                # номер слота на сим-карте, в котором находится сообщение (0-19),
                # номер телефона отправителя,
                # дату отправления сообщения,
                # текст сообщения
                result.append((message_header.split(',')[0], SMS_senderNumber, TP_serviceCenterTimeStamp, message_text))

                i += 2

            else:

                i += 1

    # закрываем порт
    ser.close()

    return result



# удаление SMS сообщения в слоте с номером slot с сим-карты
def DeleteSMS(serial_name, slot, pin=None):

    # подключаемся к порту
    ser = serial.Serial(serial_name, 115200, timeout=1)
    ser.open()

    # удаляем сообщение
    status = str_send(ser, 'AT+CMGD=%s\r' % (slot))

    # если в ответ пришел текст, содержащий SIM PIN REQUIRED, значит, модему нужен пин-код
    if 'SIM PIN' in status:
        SendPINToPort(ser, pin)
        str_send(ser, 'AT+CMGD=%s\r' % (slot))

    # закрываем порт
    ser.close()

понедельник, 26 марта 2012 г.

Всякая всячина о Python

Модули


Q: Как получить список загруженных модулей?
A:
outText = ""
for key in sys.modules.keys():
outText += key + "\n"


Q: Как загрузить модуль, который лежит не в папке скрипта?
A:Добавить путь поиска модулей:
sys.path.append('/mypath')
Но это не очень хороший способ. Если в другом проекте нужно будет использовать другой модуль с тем же именем, начнется каша.

Q: Как загрузить модуль с именем, содержащим недопустимые символы?
A:
module=__import__('my-module-name')
my_module_name = getattr(module, 'my-module-name')


Q: Как удалить модуль из списка загруженных?
A:
try:
del sys.modules['my_module_name']
except:
pass


Q: Как загрузить модуль с нетривиальным именем, находящийся в нетривиальном месте?
A:Воспользоваться вот такой милой процедурой:
import imp

def import_module(name, path):

# Fast path: see if the module has already been imported.
try:
return sys.modules[name]
except KeyError:
pass

# If any of the following calls raises an exception,
# there's a problem we can't handle -- let the caller handle it.
fp, pathname, description = imp.find_module(name, path)

try:
return imp.load_module(name, fp, pathname, description)
finally:
# Since we may exit via an exception, close fp explicitly.
if fp:
fp.close()


mod_python


Q: Как получить реальный путь до скрипта на сервере?
A:
def index(req):

import os
from mod_python import apache

req.add_common_vars()
pathname = os.path.dirname(req.subprocess_env['SCRIPT_FILENAME'])
return pathname


Q: Как посмотреть доступный список переменных окружения на сервере?
A: С помощью такого скрипта:
# -*- coding: UTF-8 -*-

from mod_python import apache

def index(req):

req.add_common_vars()

result = ""
for key in req.subprocess_env.keys():
result += key + "=" + req.subprocess_env[key] + "\n"
return result

воскресенье, 18 марта 2012 г.

Как подсмотреть список доступных переменных окружения в mod_python

Всё-таки в PHP было удобно. Пишешь файлик, содержащий <?php phpinfo();?>, и - вуаля - всё как на ладони. Для питона конструкция сложнее:
# -*- coding: UTF-8 -*-

from mod_python import apache

def index(req):

req.add_common_vars()

result = ""
for key in req.subprocess_env.keys():
result += key + "=" + req.subprocess_env[key] + "\n"
return result

вторник, 14 февраля 2012 г.

Многострочный текст в SVG

Понадобился. И оказалось, что SVG не умеет переносить текст! К счастью, в моём случае SVG создаётся при помощи python, а там не очень сложно написать функцию, разбивающую текст на строки длиной не больше заданного количества символов (главное, чтобы резала по пробелам):
def TextToLines(txt, maxLen):

result = []

lines0 = txt.split("\n")
for line in lines0:
chunks = line.split(" ")
newline, l = "", 0
for chunk in chunks:
if newline != "":
l = 1
else:
l = 0
if len(newline.decode("UTF-8")) + l + len(chunk.decode("UTF-8")) > maxLen:
result.append(newline)
newline = ""
if newline != "":
newline += " "
newline += chunk
if newline != "":
result.append(newline)

return result

Соответственно, эта процедура применяется так:
textLines = TextToLines(мойТекст, 66)
innerText, y = "", 15
for line in textLines:
innerText += """<text x="10" y="%(y)d" font-size="10pt" fill="black">%(text)s</text>""" % {"text":line, "y":y}
y += 15

Ну и дальше результат из innerText вставляем в нужное место формирующегося документа.

Не очень изящно, скажем прямо. Может, со временем найдется решение получше.

среда, 27 июля 2011 г.

python: Переименовать файлы *.fb2

Возникла задача: переименовать файлы *.fb2 в формат "<Автор> <Название>.FB2"

Так как *.fb2 - это на самом деле xml, то решение простое:
1. Получить список файлов в каталоге
2. Загрузить в парсер каждый файл
3. Найти там имя автора и название книги
4. Переименовать файл

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import os

import xml.dom.minidom
from xml.dom.minidom import Node

path = os.path.expanduser(u"~/Загрузки/fb2/")

dirList = (file for file in os.listdir(path) if os.path.isfile(os.path.join(path, file)) and file.endswith("fb2"))

for fname in dirList:

    print fname

    doc = xml.dom.minidom.parse(path + fname)

    last_name = ""
    book_title = ""

    for title_node in doc.getElementsByTagName("title-info"):

        for author_node in title_node.getElementsByTagName("author"):
            for node2 in author_node.getElementsByTagName("last-name"):
                for node3 in node2.childNodes:
                    if node3.nodeType == Node.TEXT_NODE:
                        last_name = node3.data

        for book_node in title_node.getElementsByTagName("book-title"):
            for node2 in book_node.childNodes:
                if node2.nodeType == Node.TEXT_NODE:
                    book_title = node2.data

    if last_name != "" and book_title != "":
        new_fname = u'%s %s.fb2' % (last_name, book_title)
        os.rename(path + fname, path + new_fname)
        print "%s = %s" % (fname, new_fname)

суббота, 18 июня 2011 г.

Одновременная работа mod_python.publisher и mod_python.psp

в etc/apache2/sites-available/default пишем следующее:
<Directory /var/www>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
allow from all
AddHandler mod_python .py
PythonHandler mod_python.publisher | .py
AddHandler mod_python .psp
PythonHandler mod_python.psp | .psp
PythonDebug On
</Directory>

ну и, конечно, перезапускаем апач:
sudo /etc/init.d/apache2 restart

кстати, настройка mod_python подробно расписана тут и тут (для тех, кто не осилил исходный мануал)

воскресенье, 24 апреля 2011 г.

Отправка SMS через usb-модем с использованием Python

После окончательного перехода на убунту отказалась работать .NET-фреймворковская программка, через которую работала отправка SMS-сообщений. Возникла естественная мысль переписать её, скажем, на питоне. Благодаря подсказкам это оказалось совсем тривиально:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import serial
import time
import random

# процедура для отправки строки в модем и получения ответа
def str_send (ser, textline):
print "<<" + textline
ser.write(textline)

out = ''
N = 10
while N > 0:
time.sleep(1)
while ser.inWaiting() > 0:
out += ser.read(1)

if ('OK' in out) or ('ERROR' in out) or ('>' in out):
print ">>" + out
N = 1

N -= 1

# функция преобразования телефонного номера в формат, пригодный для SMS
def PhoneNumberToSMS(number):
number += 'F'
result = '0B' + '91'
i = 0
while i < len(number):
result += number[i+1] + number[i]
i += 2
return result

# функция, кодирующая юникодную строку в формат SMS
def TextToSMS(text):
b = text
result = ''
i = 0
while i < len(b):
o = ord(b[i])
result += ("%0.2X" % (o/256)) + ("%0.2X" % (o%256))
i += 1
return result

# вводим с консоли сообщение и переводим его в юникод
message = raw_input('Текст сообщения:\n')
message = message.decode('utf-8')

# если сообщение большое - режем его на кусочки для механизма конкатенации SMS
chunks = []

if len(message) > 70:
while len(message) > 66:
chunks.append(message[:66])
message = message[66:]

if len(message) > 0:
chunks.append(message)

# готовим номер группы сообщений и устанавливаем 6-й бит SMS_SUBMIT_PDU
SMS_SUBMIT_PDU = "11"
CSMS_reference_number = ""
if len(chunks) > 1:
SMS_SUBMIT_PDU = "51"
CSMS_reference_number = "%0.4X" % random.randrange(1,65536)

# связываемся с модемом
ser = serial.Serial("/dev/ttyUSB0", 115200, timeout=1)
ser.open()

# устанавливаем нужный формат передачи данных
str_send(ser, 'AT+CMGF=0\r')

# передаем кусочки сообщения
i = 1
for chunk in chunks:
emessage = TextToSMS(chunk)
if CSMS_reference_number != "":
emessage = "06" + "08" + "04" + CSMS_reference_number + \
("%0.2X" % len(chunks)) + ("%0.2X" % i) + emessage
sms = \
"00" + \
SMS_SUBMIT_PDU + \
"00" + \
PhoneNumberToSMS("7383XXXXXXX") + \
"00" + \
"08" + \
"AA" + \
"%0.2X" % (len(emessage)/2) + \
emessage
str_send(ser, 'AT+CMGS=' + str(len(sms)/2-1) + '\r')
str_send(ser, sms + '\x1A')
i += 1

# отвязываемся от модема
ser.close()

NB. import serial заработает только если будет установлен модуль pySerial (aptitude install python-serial)

Python CGI

Полезная информация:
http://www.tutorialspoint.com/python/python_cgi_programming.htm
http://kairblog.ru/post/7033/

воскресенье, 30 января 2011 г.

Хранилище ссылок

Так как приходится скакать между несколькими компьютерами (N - дома плюс M - в конторе), на которых установлены разные браузеры - Opera, FireFox, Chrome, возникла мысль сделать "Избранное" доступным для всех этих случаев одновременно.

Идея проста: т.к. браузеры вполне легко обрабатывают javascript в командной строке, то можно в их закладки вместо обычной ссылки http://... добавить кусочек кода, который бы сохранял адрес текущего окна в какой-нибудь базе, т.е. примерно такой скрипт:
javascript:window.location.href='http://myStorage/add?l='+encodeURIComponent(location)+'&t='+encodeURIComponent(document.title);void(0);
Соответственно, по адресу http://myStorage/add лежит программка, которая 1. сохранит ссылку, 2. перекинет пользователя обратно на исходную страницу.
(честно говоря, все эти переброски утомляют, и было бы здорово обойтись ajax-ом, но возникло препятствие в виде запрета межсайтового скриптинга)

Тогда можно прикрутив к этой базе веб-интерфейс получить страницу ссылок, доступную откуда угодно, этакое "сетевое Избранное".

В итоге родилось примерно следующее хранилище. Например, в хроме две его ссылки выглядят так: