import axon
from pprint import pprint
from collections import namedtuple
import io

"""
Explanation: Что такое AXON. Примеры использования.
End of explanation
"""


text = """
RU: {capital:"Москва"}
US: {capital:"Вашингтон"}
GB: {capital:"Берлин"}
""" 
for val in axon.iloads(text):
    print(val)

"""
Explanation: Данная статья является продолжением Что такое AXON. Если первая статья была посвящена знакомству с нотацией AXON, то это посвящена вариантам использования на основе библиотеки pyaxon.
Загрузка объектов из потока
Сообщение в нотации AXON это поток объектов или поток пар ключ:объект в текстовом формате. Это позволяет не загружать предварительно в память весь поток, но загружать объекты из потока по очереди.
Для этого имеются две функции iload для итеративной загрузки из файла и iloads для итеративной загрузки из текста. 
Две другие функции load и load &ndash; для полной загрузки объектов из файла и из текста, соответственно. В результате получается список закруженных объектов (list) или словарь (ordered dict).
Поток пар key:value
End of explanation
"""


text = """
{FR:"Paris" RU:"Moscow"}
{CN:"Beigin" JP:"Tokio"}
{US:"Washington" CA:"Ottava"}
"""
for val in axon.iloads(text):
    print(val)

"""
Explanation: Поток словарей (dict)
End of explanation
"""


text = """
# последовательность словарей
# с сохранением порядка ключей
[RU:"Москва" FR:"Париж"]
[CN:"Пекин" JP:"Токио"]
[US:"Вашингтон" CA:"Оттава"]
"""
for val in axon.iloads(text):
    print(val)

"""
Explanation: Поток словарей (ordered dict)
End of explanation
"""


text = """
# кортежи экономят память в python,
# если мутируемость не нужна
("RU" "Moscow")
("US" "Washington")
("GB" "London")
"""
for val in axon.iloads(text):
    print(val)

"""
Explanation: Поток кортежей (tuple)
End of explanation
"""


text = """
["RU" "Moscow"]
["US" "Washington"]
["GB" "London"]
"""
for val in axon.iloads(text):
    print(val)

"""
Explanation: Поток списков (list)
End of explanation
"""


text = """
# это AXON без форматирования
country{id:"RU" capital:"Moscow"}
country{id:"USA" capital:"Washington"}
country{id:"GB" capital:"London"}
"""
for val in axon.iloads(text):
    print(val)

"""
Explanation: Поток элементов/объектов
End of explanation
"""


Country = namedtuple("Country", "id capital")

@axon.factory("country")
def factory_country(attrs, vals):
    return Country(**attrs)

text = """
# это AXON без форматирования
country{id:"RU" capital:"Moscow"}
country{id:"USA" capital:"Washington"}
country{id:"GB" capital:"London"}
"""
for val in axon.iloads(text, mode='strict'):
    print(val)

"""
Explanation: Если вместо элементов нужны объекты определенного типа, то нужно зарегистрировать фабричные функции:
End of explanation
"""


@axon.reduce(Country)
def reduce_Country(ob):
    return axon.node("country", {'id':ob.id, 'capital':ob.capital})

vals = axon.loads(text, mode='strict')
print(axon.dumps(vals))                

"""
Explanation: Для того чтобы обратно преобразовать в AXON необходимо зарегистрировать функции для редуцирования:
End of explanation
"""


text = """
graph {
    nodes: [
       &n1 node {id:1 val:4}
       &n2 node {id:2 val:7}
       &n3 node {id:3 val:2}
       &n4 node {id:4 val:5}
    ]
    edges: [
        edge {val:12 *n1 *n2}
        edge {val:8 *n1 *n4}
        edge {val:-2 *n2 *n3}
        edge {val:5 *n3 *n4}
    ]
}
"""

"""
Explanation: Метки и ссылки
Проблема копирования объектов возникает при загрузке объектов с зависимостями. Для примера рассмотрим пример с обычным графом.
End of explanation
"""


vals = axon.loads(text)

"""
Explanation: Метки объектов имеют префикс &amp;, а ссылки префикс *.
End of explanation
"""


graph = vals[0]
nodes, edges = graph.nodes, graph.edges
print(id(nodes[0]) == id(edges[0][0]) == id(edges[1][0]))
print(id(nodes[1]) == id(edges[0][1]) == id(edges[2][0]))
print(id(nodes[2]) == id(edges[2][1]) == id(edges[3][0]))
print(id(nodes[3]) == id(edges[1][1]) == id(edges[3][1]))

print("Дамп в компактной форме:")
print("=======================")
print(axon.dumps(vals))

"""
Explanation: Убеждаемся, что копирования не происходит:
End of explanation
"""


from xml.etree import ElementTree

"""
Explanation: XML -> AXON
Пусть нужно загрузить сторонние данные из XML документа и вставить в AXON. Для работы возьмем библиотеку xml.etree из стандартной библиотеки python.
End of explanation
"""


@axon.reduce(ElementTree.Element)
def element_reduce(elem):
    children = elem.getchildren()
    if elem.attrib:
        if children:
            return axon.node(elem.tag, elem.attrib, children)
        else:
            return axon.node(elem.tag, elem.attrib, None)
    elif children:
            return axon.node(elem.tag, None, children)
    else:
        return axon.node(elem.tag)
        
@axon.reduce(ElementTree.ElementTree)
def etree_reduce(element):
    return element_reduce(element.getroot())

"""
Explanation: Зарегистрируем функции для редуцирования при преобразования в AXON формат.
End of explanation
"""


xml_doc = """
<persons>
<person firstname="Елена" lastname="Иванова" />
<person firstname="Анна" lastname="Сидорова" />
</persons>
"""
xml = ElementTree.fromstring(xml_doc)

axon_text = """
person {firstname:"Иван" lastname:"Иванов"}
person {firstname:"Николай" lastname:"Сидоров"}
"""

"""
Explanation: Возьмем XML документ (без пространств имен).
End of explanation
"""


vals = axon.loads(axon_text)
vals.extend(xml.getchildren())
axon_text2 = axon.dumps(vals)
print(axon_text2)

"""
Explanation: Загружаем и объединяем:
End of explanation
"""


def update_attribs(d):
    for key, val in d.items():
        d[key] = str(val)

class ElementTreeBuilder(axon.Builder):
    def node(self, name, attrs, vals):
        if attrs:
            attrs = dict(attrs)
            update_attribs(attrs)
        else:
            attrs = {}
        e = ElementTree.Element(name, attrs)
        if vals:
            e.extend(vals)
        return e

axon.register_builder('etree', ElementTreeBuilder())    

"""
Explanation: Можно загрузить из AXON в XML. Для этого зарегистрируем построитель объектов. Изначально в библиотеке имеются три построителя: 

safe (безопасный в том смысле, что для представления используются только встроенные типы), 
strict (используются только зарегистрированные пользователем фабричные функции, иначе генерируется исключение) и
mixed (как в strict, только если нет зарегистрированной фабричной функции, то используются встроенные объекты).
End of explanation
"""


vals = axon.loads(axon_text2, mode='etree')
root = ElementTree.Element("persons")
root.extend(vals)
ElementTree.dump(root)

"""
Explanation: Теперь можно загрузить и получить дерево XML документа.
End of explanation
"""


import json 

# fp = io.open('data/auctions.json', 'rt')
# vals = json.load(fp)
# fp.close()
# axon.dump('data/auctions.axon', vals)

def load_and_iterate_json(path):
    fp = io.open(path, 'rt')
    vals = json.load(fp)
    print(len(vals))
    fp.close()
    for val in vals:
        pass
    
def iload_and_iterate_axon(path):
     for val in axon.iload(path):
         pass    
    
def load_and_iterate_axon(path):
    vals = axon.load(path)
    for val in vals:
        pass


%time load_and_iterate_json('data/auctions.json')
%time load_and_iterate_axon('data/auctions.axon')
%time iload_and_iterate_axon('data/auctions.axon')

import random
test_vals_1 = []
for i in range(100000):
    test_vals_1.append([random.randint(0, 1000000), random.random(), random.randint(0, 1000000), random.random()])
    
fp = io.open('data/test_1.json', 'wt')    
json.dump(test_vals_1, fp)
fp.close()
axon.dump('data/test_1.axon', test_vals_1)


%time load_and_iterate_json('data/test_1.json')
%time load_and_iterate_axon('data/test_1.axon')
%time iload_and_iterate_axon('data/test_1.axon')

import random
test_vals_2 = []
for i in range(100000):
    test_vals_2.append({'a':random.randint(0, 1000000), 'b':random.random(), 'c':random.randint(0, 1000000), 'd':random.random()})
    
fp = io.open('data/test_2.json', 'wt')    
json.dump(test_vals_2, fp)
fp.close()
axon.dump('data/test_2.axon', test_vals_2)


%time load_and_iterate_json('data/test_2.json')
%time load_and_iterate_axon('data/test_2.axon')
%time iload_and_iterate_axon('data/test_2.axon')

"""
Explanation: Сравнение производительности
AXON и JSON
Скорость загрузки/дампа для AXON ожидаемо ниже чем для AXON, так как структура объектной нотации AXON сложнее JSON.
Для теста используется файл из 69933 записей (list of dicts). Запись имеет вид:
json
{"auc":233524008,"item":98799,"owner":"???","ownerRealm":"???","bid":8500000,"buyout":8500000,"quantity":1,"timeLeft":"VERY_LONG","rand":0,"seed":1639860480}
End of explanation
"""


