Mutable variant of namedtuple -- recordclass, which support assignments, compact dataclasses and other memory saving variants.
import sys
from memory_profiler import profile
from recordclass import dataobject
from random import random

class NodeSlots:
    __slots__ = 'ob', 'left', 'right'
    def __init__(self, ob, left, right):
        self.ob = ob
        self.left = left
        self.right = right

class NodeDO(dataobject):
    __fields__ = 'ob', 'left', 'right'
    
count = 0

def add_nodes_do(level):
    global count
    if level > 0:
        o = random()
        left = add_nodes_do(level-1)
        right = add_nodes_do(level-1)
        node = NodeDO(o, left, right)
        count += 1
    else:
        node = None
    return node

def add_nodes_slots(level):
    global count
    if level > 0:
        o = random()
        left = add_nodes_slots(level-1)
        right = add_nodes_slots(level-1)
        node = NodeSlots(o, left, right)
        count += 1
    else:
        node = None
    return node

@profile
def test_do():
    global count
    count = 0
    root = add_nodes_do(22)
    print(count, "nodes")
    del root
    return

@profile
def test_slots():
    global count
    count = 0
    root = add_nodes_slots(22)
    print(count, "nodes")
    del root
    return

if __name__ == '__main__':
    tag = sys.argv[1]
#     print(tag)
    if tag == 'do':
        test_do()
    elif tag == 'slots':
        test_slots()
    else:
        print("Unknown tag")
    print("done")

examples/do_mem_profile.py
intellimath/recordclass
#!/usr/bin/env python3
# coding: utf-8


from recordclass import dataobject
from time import time
from random import randrange
import sys
import gc

ijw = [(randrange(100), randrange(100), randrange(1000)) for _ in range(10000000)]

class EdgeDO(dataobject, fast_new=True):
    __fields__ = 'node1', 'node2', 'weight'

class EdgeSlots:
    __slots__ = 'node1', 'node2', 'weight'

    def __init__(self, node1, node2, weight):
         self.node1 = node1
         self.node2 = node2
         self.weight = weight
            
    def __str__(self):
        return f'EdgeSlots(node1={self.node1}, node2={self.node2}, weight={self.weight})'
            
def list_size(lst):
    return sum(sys.getsizeof(o) for o in lst)

def test(C):
    for args in ijw:
        obj = C(*args)
        prev = obj

        
print('__slots__ timinig:')
t0 = time()
list_slots = [EdgeSlots(*args) for args in ijw]
t1 = time()
print(t1 - t0)
print(list_slots[0])
size_list_slots = list_size(list_slots) / 1000000
print('size (__slots__): ', size_list_slots)

del list_slots[:]
del list_slots

print('dataobject timinig:')
t0 = time()
list_do = [EdgeDO(*args) for args in ijw]
t1 = time()
print(list_do[0])
print(t1 - t0)
size_list_do = list_size(list_do) / 1000000
print('size (dataobject):', size_list_do)

del list_do[:]
del list_do

print((size_list_do/size_list_slots)*100, "%")

t0 = time()
test(EdgeDO)
t1 = time()
print(t1-t0)

t0 = time()
test(EdgeSlots)
t1 = time()
print(t1-t0)






examples/millions_of_objects.py
intellimath/recordclass
from __future__ import print_function
    
import time
import sys
import random
import datetime
from datetime import date
from typing import NamedTuple, Dict
from dataclasses import dataclass
from recordclass import RecordClass, dataobject
from itertools import chain
from collections import deque
try:
    from reprlib import repr
except ImportError:
    pass

import pandas as pd
import numpy as np

import gc

def total_size(o, handlers={}, verbose=False):
    """ Returns the approximate memory footprint an object and all of its contents.

    Automatically finds the contents of the following builtin containers and
    their subclasses:  tuple, list, deque, dict, set and frozenset.
    To search other containers, add handlers to iterate over their contents:

        handlers = {SomeContainerClass: iter,
                    OtherContainerClass: OtherContainerClass.get_elements}

    """
    dict_handler = lambda d: chain.from_iterable(d.items())
    all_handlers = {tuple: iter,
                    list: iter,
                    deque: iter,
                    dict: dict_handler,
                    set: iter,
                    frozenset: iter,
                   }
    all_handlers.update(handlers)     # user handlers take precedence
    seen = set()                      # track which object id's have already been seen
    default_size = sys.getsizeof(0)       # estimate sizeof object without __sizeof__

    def sizeof(o):
        if id(o) in seen:       # do not double count the same object
            return 0
        seen.add(id(o))
        s = sys.getsizeof(o, default_size)

        if verbose:
            print(s, type(o), repr(o), file=sys.stderr)

        for typ, handler in all_handlers.items():
            if isinstance(o, typ):
                s += sum(map(sizeof, handler(o)))
                break
        return s

    return sizeof(o)


##### NamedTuple #####
class PricesNamedTuple(NamedTuple("Prices", 
                         [('open', float), 
                          ('high', float), 
                          ('low', float), 
                          ('close', float)])):
    pass

class TradeDayNamedTuple(NamedTuple("TradeDay", 
                          (("symbol", str), 
                           ("dt", date), 
                           ("prices", PricesNamedTuple)))):

    def return_change(self):
        return int((self.prices.close - self.prices.open) / self.prices.open)

    def update_symbol(self, symb):
        self = self._replace(symbol = symb)



##### DataClass #####
@dataclass
class PricesDataClass: 
    open: float
    high: float
    low: float
    close: float

@dataclass
class TradeDayDataClass:
    symbol: str
    dt: date
    prices: PricesDataClass

    def return_change(self):
        return int((self.prices.close - self.prices.open) / self.prices.open)

    def update_symbol(self, symb):
        self.symbol = symb


##### RecordClass #####        
class PricesRecordClass(RecordClass):
    open: float
    high: float
    low: float
    close: float

class TradeDayRecordClass(RecordClass):
    symbol: str
    dt: date
    prices: PricesRecordClass

    def return_change(self):
        return round((self.prices.close - self.prices.open) / self.prices.open, 2)

    def update_symbol(self, symb):
        self.symbol = symb

##### dataobject #####        
class PricesDataobject(dataobject, fast_new=True):
    open: float
    high: float
    low: float
    close: float

class TradeDayDataobject(dataobject, fast_new=True):
    symbol: str
    dt: date
    prices: PricesDataobject

    def return_change(self):
        return round((self.prices.close - self.prices.open) / self.prices.open, 2)

    def update_symbol(self, symb):
        self.symbol = symb

##### Regular Python class #####
class PricesClass():
    open: float
    high: float
    low: float
    close: float

    def __init__(self, _open, _high, _low, _close):
        self.open = _open
        self.high = _high
        self.low = _low
        self.close = _close

class TradeDayClass():
    symbol: str
    dt: date
    prices: PricesRecordClass

    def __init__(self, _symbol, _dt, _prices):
        self.symbol = _symbol
        self.dt = _dt
        self.prices = _prices

    def return_change(self):
        return round((self.prices.close - self.prices.open) / self.prices.open, 2)

    def update_symbol(self, symb):
        self.symbol = symb


##### Regular Python class with slots #####
class PricesClassSlots():
    __slots__ = 'open', 'high', 'low', 'close'
    open: float
    high: float
    low: float
    close: float

    def __init__(self, _open, _high, _low, _close):
        self.open = _open
        self.high = _high
        self.low = _low
        self.close = _close

class TradeDayClassSlots():
    __slots__ = 'symbol', 'dt', 'prices'
    symbol: str
    dt: date
    prices: PricesRecordClass

    def __init__(self, _symbol, _dt, _prices):
        self.symbol = _symbol
        self.dt = _dt
        self.prices = _prices

    def return_change(self):
        return round((self.prices.close - self.prices.open) / self.prices.open, 2)

    def update_symbol(self, symb):
        self.symbol = symb


##### Python dict #####
def PricesDict(_open, _high, _low, _close):
    return {"open": _open, "high": _high, "low": _low, "close": _close}


def TradeDayDict(symbol, dt, prices):
    return {"symbol": symbol, "dt": dt, "prices": prices}

stats = {}
def run_test(objType):
    gc.collect()
    tstats = stats[objType] = {}
    print("====== %s Performance Report ======" % objType)
    print("Time it takes to create 'day' object is: ")
    TradeDay = eval("TradeDay%s" % objType)
    Prices = eval("Prices%s" % objType)
    data: Dict[str, TradeDay] = {}
    obj_count = 100000
#     stats['obj_count'] = obj_count

    st = time.time()
    for i in range(0, obj_count):
        data[i] = TradeDay("MA", datetime.date.today(), Prices(random.random(), 30.0, 5.0, 20.0))
    tstats['created'] = obj_count / (time.time() - st)
    print("%s day %s created     at: %8s per second" % (obj_count, objType, int(tstats['created'])))

    st = time.time()
    for k, v in data.items():
        d = v
    tstats['top_read'] = obj_count / (time.time() - st)
    print("%s day %s top-read    at: %8s per second" % (obj_count, objType, int(tstats['top_read'])))

    st = time.time()
    if objType == 'Dict':
        for k, v in data.items():
            d = v['prices']['open']
    else:
        for k, v in data.items():
            d = v.prices.open
    tstats['sub_read'] = obj_count / (time.time() - st)
    print("%s day %s sub-read    at: %8s per second" % (obj_count, objType, int(tstats['sub_read'])))

    st = time.time()
    if objType == 'Dict':
        for k, v in data.items():
            vprices = v['prices'] 
            d = int((vprices['close'] - vprices['open']) / vprices['open'])
    else:
        for k, v in data.items():
            vprices = v.prices
            d = int((vprices.close - vprices.open) / vprices.open)
    tstats['change'] = obj_count / (time.time() - st)
    print("%s day %s change      at: %8s per second" % (obj_count, objType, int(tstats['change'])))

    st = time.time()
    if objType == 'Dict':
        for k, v in data.items():
            v['symbol'] = "AAA"
    elif objType == 'NamedTuple':
        for k, v in data.items():
            v = v._replace(symbol="AAA")
    else:
        for k, v in data.items():
            v.symbol = "AAA"
    tstats['mutate'] = obj_count / (time.time() - st)
    print("%s day %s mutate      at: %8s per second" % (obj_count, objType, int(tstats['mutate'])))

    st = time.time()
    if objType == 'Dict':
        for k, v in data.items():
            vprices = v['prices'] 
            d = int((vprices['close'] - vprices['open']) / vprices['open'])
    else:
        for k, v in data.items():
            d = v.return_change()
    tstats['class_read'] = obj_count / (time.time() - st)
    print("%s day %s class_read      at: %8s per second" % (obj_count, objType, int(tstats['class_read'])))

    st = time.time()
    if objType == 'Dict':
        for k, v in data.items():
            v['symbol'] = "AAA"
    else:
        for k, v in data.items():
            v.update_symbol("AAA")
    tstats['class_update'] = obj_count / (time.time() - st)
    print("%s day %s class_update      at: %8s per second" % (obj_count, objType, int(tstats['class_update'])))

    tstats['size'] = total_size(data) / obj_count
    print("The size of single {}:{}: bytes".format(objType, int(tstats['size'])))


if __name__ == '__main__':
    run_test('Dict')
    run_test('Class')
    run_test('ClassSlots')
    run_test('DataClass')
    run_test('RecordClass')
    run_test('Dataobject')
    run_test('NamedTuple')
    
    pd.set_option("float_format", lambda x: "%.0f" % x)
    df = pd.DataFrame(stats)
    print(df)
    for tn in df.index:
        maxval = np.max([df[key][tn] for key in df.keys()])
        maxval = float(maxval)
        for key in df.keys():
            df[key][tn] = round(df[key][tn] / maxval, 2)
    pd.set_option("float_format", lambda x: "%.2f" % x)
    print(df)

examples/performance_comparison.py
intellimath/recordclass
#!/usr/bin/env python

import random
import time
import psutil
import os

from recordclass import make_dataclass, litelist

def memory_usage_psutil():
    # return the memory usage in percentage like top
    process = psutil.Process(os.getpid())
    mem = process.memory_info().rss
    return mem

Cls = make_dataclass('Cls', ['a','b','c'])

N = 1000000
while True:
    print(memory_usage_psutil())
    rnd = random.randint
    lst = litelist()
    for i in range(N):
        o = Cls(rnd(1,1024), rnd(1,1024), rnd(1,1024))
        lst.append(o)
    print(memory_usage_psutil())
    del a
    time.sleep(1.)
  

examples/test_leak_dataobject.py
intellimath/recordclass
#!/usr/bin/env python

from random import randint
import time
import psutil
import os

from recordclass import make_dataclass

def memory_usage_psutil():
    # return the memory usage in percentage like top
    process = psutil.Process(os.getpid())
    mem = process.memory_percent()
    return mem

X = make_dataclass('X', fields=['a','b','c'])
#X = new_datatype('X', fields=3)

while True:
  a = X(randint(1,100), randint(1,100), randint(1,100))
  x = a.a
  a.a = 1
  y = a.b
  a.b = 2
  z = a.c
  a.c = 2
  r = repr(a)
  del a
  time.sleep(.01)
  print(memory_usage_psutil())
  

examples/test_leak_datatype.py
intellimath/recordclass
#!/usr/bin/env python

from random import randint
import time
import psutil
import os
import gc

from recordclass import make_dataclass, make_arrayclass

def memory_usage_psutil():
    # return the memory usage in percentage like top
    process = psutil.Process(os.getpid())
    mem = process.memory_percent()
    return mem

N = 1000000
X = make_dataclass('X', fields=['a','b','c'])
Y = make_arrayclass('Y', fields=N)

gc.collect()
time.sleep(1.)
print(memory_usage_psutil())

lst = Y()
for i in range(N):
  lst[i] = X(1,2,3)

gc.collect()
time.sleep(1.)
print(memory_usage_psutil())

del lst
del Y
del X
gc.collect()
time.sleep(1.)
print(memory_usage_psutil())


examples/test_leak_datatype2.py
intellimath/recordclass
#!/usr/bin/env python

import random
import time
import psutil
import os

from recordclass import make_dataclass, litelist

def memory_usage_psutil():
    # return the memory usage in percentage like top
    process = psutil.Process(os.getpid())
    mem = process.memory_info().rss / 1024**2
    return mem

Cls = make_dataclass('Cls', ['a','b','c','d','e','f','g','h'])

class Cls2:
    __slots__ = 'a','b','c','d','e','f','g','h'
    def __init__(self, a, b, c, d, e, f, g, h):
        self.a = a
        self.b = b
        self.c = c
        self.d = d
        self.e = e
        self.f = f
        self.g = g
        self.h = h

N = 100000
while True:
    print(memory_usage_psutil())
    rnd = random.randint
    # lst = litelist([])
    lst = []
    for i in range(N):
        # o = Cls(rnd(1,1024), rnd(1,1024), rnd(1,1024), 
        #         rnd(1,1024), rnd(1,1024), rnd(1,1024),
        #         rnd(1,1024), rnd(1,1024))
        o = Cls2(rnd(1,1024), rnd(1,1024), rnd(1,1024), 
                rnd(1,1024), rnd(1,1024), rnd(1,1024),
                rnd(1,1024), rnd(1,1024))
        lst.append(o)
    print(memory_usage_psutil())
    del lst
    time.sleep(0.2)
  

examples/test_memory_usage_dataobject.py
intellimath/recordclass
#!/usr/bin/env python
# coding: utf-8

# In[1]:


import random
import sys
from collections import deque
from recordclass import dataobject
import time
import matplotlib.pyplot as plt
import math
import gc
import numpy as np


# In[2]:


def random_strings(num_strings):
    random.seed(2)
    symbols = "abcdefghijklmnopqrstuvwxyz"

    for i in range(num_strings):
        length = random.randint(5, 15)
        yield "".join(random.choices(symbols, k=length))


# In[3]:


class Node:
    #__slots__ = ("char", "count", "lo", "eq", "hi")
    __slots__ = ("char", "lo", "eq", "hi")

    def __init__(self, char):
        self.char = char
#         self.count = 0

        self.lo = None
        self.eq = None
        self.hi = None

class TernarySearchTree():
    """Ternary search tree that stores counts for n-grams
    and their subsequences.
    """

    def __init__(self, splitchar=None):
        self.root = None
        self.splitchar = splitchar

    def insert(self, string):
        self.root = self._insert(string, self.root)

    def _insert(self, string, node):
        """Insert string at a given node.
        """
        if not string:
            return node

        char, *rest = string

        if node is None:
            node = Node(char)

        if char == node.char:
            if not rest:
#                 node.count += 1
                return node
            else:
#                 if rest[0] == self.splitchar:
#                     node.count += 1
                node.eq = self._insert(rest, node.eq)

        elif char < node.char:
            node.lo = self._insert(string, node.lo)

        else:
            node.hi = self._insert(string, node.hi)

        return node

def train(N):
    tree = TernarySearchTree("#")
    grams = deque(maxlen=4)

    for token in random_strings(N):
        grams.append(token)
        tree.insert("#".join(grams))
    return tree


# In[4]:


class Node2(dataobject):
#     __fields__ = ("char", "count", "lo", "eq", "hi")
    __fields__ = ("char", "lo", "eq", "hi")
    __options__ = {'argsonly':True}
#     lo = None
#     eq = None
#     hi = None

class TernarySearchTree2():
    """Ternary search tree that stores counts for n-grams
    and their subsequences.
    """

    def __init__(self, splitchar=None):
        self.root = None
        self.splitchar = splitchar

    def insert(self, string):
        self.root = self._insert(string, self.root)

    def _insert(self, string, node):
        """Insert string at a given node.
        """
        if not string:
            return node

        char, *rest = string

        if node is None:
            node = Node2(char)

        if char == node.char:
            if not rest:
#                 node.count += 1
                return node
            else:
#                 if rest[0] == self.splitchar:
#                     node.count += 1
                node.eq = self._insert(rest, node.eq)

        elif char < node.char:
            node.lo = self._insert(string, node.lo)

        else:
            node.hi = self._insert(string, node.hi)

        return node

def train2(N):
    tree = TernarySearchTree2("#")
    grams = deque(maxlen=4)

    for token in random_strings(N):
        grams.append(token)
        tree.insert("#".join(grams))
    return tree


# In[5]:


q2 = math.sqrt(2)


# In[12]:


import gc
gc.collect()


# In[13]:


gc.collect()
ns = []
times = []
times_del = []
n = 1024 * 32
n2 = 1024 * 1024 * 2
while n < n2 + 1:
    gc.collect()
    gc.disable()
    t0 = time.time()
    tree = train(n)
    dt = time.time() - t0
    t0 = time.time()
    gc.enable()
    del tree
    gc.collect()
    gc.collect()
    dt2 = time.time() - t0
    #g = [d['collected'] for d in gc.get_stats()]
    ns.append(n)
    times.append(dt)
    times_del.append(dt2)
    print(n, "%.2f %.2f" % (dt, dt2))
    n = int(n * q2)


# In[14]:


gc.collect()
ns2 = []
times2 = []
times2_del = []
n = 1024 * 32
n2 = 1024 * 1024 * 2
while n < n2+1:
    gc.collect()
    gc.disable()
    t0 = time.time()
    tree2 = train2(n)
    dt = time.time() - t0
    t0 = time.time()
    del tree2
    dt2 = time.time() - t0
    gc.enable()
    gc.collect()
    #g = [d['collected'] for d in gc.get_stats()]
    ns2.append(n)
    times2.append(dt)
    times2_del.append(dt2)
    print(n, "%.2f %.2f" % (dt, dt2))
    n = int(n * q2)


# In[15]:


import seaborn as sb
import statsmodels
x = np.log(ns)
y = np.log(np.array(times) - np.array(times2))
sb.regplot(x, y, robust=True, fit_reg=True)
dy = y[-1] - y[1]
dx = x[-1] - x[1]
print(dy, dx)
print(dy/dx)


# In[16]:


plt.figure(figsize=(10, 4))
plt.subplot(1,2,1)
plt.title('Create tree')
plt.loglog(ns, times, marker='s', label='slots')
plt.loglog(ns2, times2, marker='o', label='dataobject')
plt.xlabel("N")
plt.ylabel("time")
plt.legend()
plt.subplot(1,2,2)
plt.title('Delete tree')
plt.loglog(ns, times_del, marker='s', label='slots')
plt.loglog(ns2, times2_del, marker='o', label='dataobject')
plt.xlabel("N")
plt.ylabel("time")
plt.legend()
plt.show()


# In[ ]:





examples/tree_trigram.py
intellimath/recordclass
# The MIT License (MIT)
#
# Copyright (c) <2015-2023> <Shibzukhov Zaur, szport at gmail dot com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.


from .datatype import datatype
from ._dataobject import dataobject, astuple, asdict, clone, update, make, Factory
from ._litelist import litelist, litelist_fromargs
from ._litetuple import litetuple, mutabletuple
from .recordclass import recordclass
from .typing import RecordClass
from .dataclass import make_dataclass, make_class, join_dataclasses
from .dictclass import make_dictclass
from .arrayclass import make_arrayclass
from .adapter import as_dataclass

structclass = make_dataclass

__version__ = '0.21'

lib/recordclass/__init__.py
intellimath/recordclass
# coding: utf-8

# The MIT License (MIT)

# Copyright (c) «2021-2023» «Shibzukhov Zaur, szport at gmail dot com»

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software - recordclass library - and associated documentation files
# (the "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish, distribute,
# sublicense, and/or sell copies of the Software, and to permit persons to whom
# the Software is furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

def as_dataclass(*, use_dict=False, use_weakref=False, hashable=False,
                    sequence=False, mapping=False, iterable=False, readonly=False,
                    module=None, fast_new=True, rename=False, gc=False):

    """Returns a new dataobject-based class with named fields, smaller memory footprint and 
    faster instance creation.
    
        @as_dataclass()
        class Point:
            x:int
            y:int
    """
    def _adapter(cls, use_dict=use_dict, use_weakref=use_weakref, hashable=hashable,
                      sequence=sequence, mapping=mapping, iterable=iterable, readonly=readonly,
                      fast_new=fast_new, rename=rename, gc=gc):
        from ._dataobject import dataobject
        from .datatype import datatype
        from sys import intern as _intern
        
        ns = {}
        if '__fields__' not in cls.__dict__:
            ns['__fields__'] = tuple(cls.__dict__.get('__annotations__', ()))
            
        ns['__annotations__'] = cls.__dict__.get('__annotations__', {})

        if sequence or mapping:
            iterable = True

        if '__iter__' in ns:
            iterable = True

        if readonly:
            hashable = True
        
        for k,v in cls.__dict__.items():
            if not k.startswith("___"):
                ns[k] = v
        
        typename = cls.__name__

        new_cls = datatype(typename, (dataobject,), ns, 
                       gc=gc, fast_new=fast_new, readonly=readonly, iterable=iterable,
                       mapping=mapping, sequence=sequence, use_dict=use_dict, 
                       use_weakref=use_weakref, hashable=hashable)

        return new_cls
    return _adapter

lib/recordclass/adapter.py
intellimath/recordclass
# coding: utf-8
 
# The MIT License (MIT)

# Copyright (c) «2017-2023» «Shibzukhov Zaur, szport at gmail dot com»

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software - recordclass library - and associated documentation files 
# (the "Software"), to deal in the Software without restriction, including 
# without limitation the rights to use, copy, modify, merge, publish, distribute, 
# sublicense, and/or sell copies of the Software, and to permit persons to whom 
# the Software is furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

__all__ = 'make_arrayclass', 

import sys as _sys

_intern = _sys.intern

int_type = type(1)
    
def make_arrayclass(typename, n_fields, *, namespace=None, 
             use_weakref=False, hashable=False, readonly=False, gc=False,
             module=None, immutable_type=False):

    from ._dataobject import dataobject
    from .datatype import datatype
    
    if not isinstance(n_fields, int_type):
        raise TypeError("argument fields is not integer")

    bases = (dataobject,)

    typename = _intern(typename)

    if namespace is None:
        ns = {}
    else:
        ns = namespace

    ns['__fields__'] = n_fields

    def __repr__(_self):
        return typename + '(' + \
               ', '.join(repr(o) for o in _self) + ')'
    __repr__.__qual_name__ =  f'{typename}.__repr__' 
    ns['__repr__'] = __repr__
    ns['__str__'] = __repr__    

    if module is None:
        try:
            module = _sys._getframe(1).f_globals.get('__name__', '__main__')
        except (AttributeError, ValueError):
            pass

    ns['__module__'] = module

    cls = datatype(typename, bases, ns,
                   use_dict=False, use_weakref=use_weakref, hashable=hashable, 
                   sequence=True, mapping=False, iterable=True, readonly=readonly, 
                   gc=gc, fast_new=True, immutable_type=immutable_type)

    return cls

lib/recordclass/arrayclass.py
intellimath/recordclass
# coding: utf-8

# The MIT License (MIT)

# Copyright (c) «2017-2023» «Shibzukhov Zaur, szport at gmail dot com»

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software - recordclass library - and associated documentation files
# (the "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish, distribute,
# sublicense, and/or sell copies of the Software, and to permit persons to whom
# the Software is furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

from .utils import process_fields
from .utils import check_name, collect_info_from_bases
from ._dataobject import dataobject

__all__ = 'make_dataclass', 'join_dataclasses', 'DataclassStorage'

def make_dataclass(typename, fields, defaults=None, *, bases=None, namespace=None,
                   use_dict=False, use_weakref=False, hashable=False,
                   sequence=False, mapping=False, iterable=False, readonly=False, invalid_names=(),
                   deep_dealloc=False, module=None, fast_new=True, rename=False, gc=False, 
                   immutable_type=False, copy_default=False):

    """Returns a new class with named fields and small memory footprint.

    >>> from recordclass import make_dataclass, asdict
    >>> Point = make_dataclass('Point', 'x y')
    >>> Point.__doc__                   # docstring for the new class
    'Point(x, y)'
    >>> p = Point(1, 2)                 # instantiate with positional args or keywords
    >>> p[0] + p[1]                     # indexable like a plain tuple
    3
    >>> x, y = p                        # unpack like a regular tuple
    >>> x, y
    (1, 2)
    >>> p.x + p.y                       # fields also accessable by name
    3
    >>> d = asdict()                    # convert to a dictionary
    >>> d['y'] = 3                         # assign new value
    >>> Point(**d)                      # convert from a dictionary
    Point(x=1, y=-1)
    """
    from ._dataobject import dataobject
    from .datatype import datatype
    import sys as _sys

    fields, annotations, defaults = process_fields(fields, defaults, rename, invalid_names)
    typename = check_name(typename)
    
    if namespace is None:
        ns = {}
    else:
        ns = namespace.copy()

    n_fields = len(fields)
    n_defaults = len(defaults) if defaults else 0

    ns['__fields__'] = fields
    ns['__annotations__'] = annotations
    ns['__defaults__'] = defaults

    if module is None:
        try:
            module = _sys._getframe(1).f_globals.get('__name__', '__main__')
        except (AttributeError, ValueError):
            pass

    ns['__module__'] = module

    cls = datatype(typename, bases, ns, 
                   readonly=readonly, iterable=iterable,
                   mapping=mapping, sequence=sequence,
                   use_dict=use_dict, use_weakref=use_weakref,
                   gc=gc, fast_new=fast_new,
                   hashable=hashable, immutable_type=immutable_type, 
                   copy_default=copy_default)

    return cls

make_class = make_dataclass

class DataclassStorage:
    #
    def __init__(self):
        self._storage = {}
    #
    def clear_storage(self):
        self._storage.clear()
    #
    def make_dataclass(self, name, fields, defaults=None, **kw):
        if type(fields) is str:
            fields = fields.replace(',', ' ').split()
            fields = ' '.join(fn.strip() for fn in fields)
        else:
            fields = ' '.join(fields)
        key = (name, fields)
        cls = self._storage.get(key, None)
        if cls is None:
            cls = make_dataclass(name, fields, defaults, **kw)
            self._storage[key] = cls
        return cls
    make_class = make_dataclass

def join_dataclasses(name, classes, *, readonly=False, use_dict=False, gc=False,
                 use_weakref=False, hashable=True, sequence=False, fast_new=False, iterable=True, module=None):

    from ._dataobject import dataobject

    if not all(issubclass(cls, dataobject) for cls in classes):
        raise TypeError('All arguments should be children of dataobject')
    if not all(hasattr(cls, '__fields__') for cls in classes):
        raise TypeError('Some of the base classes has not __fields__')

    _attrs = []
    for cls in classes:
        for a in cls.__fields__:
            if a in _attrs:
                raise AttributeError(f'Duplicate attribute %s in the base classes {a}')
            _attrs.append(a)

    return make_dataclass(name, _attrs,
                          readonly=readonly, use_dict=use_dict, gc=gc, use_weakref=use_weakref,
                          hashable=hashable, sequence=sequence, iterable=iterable, module=module)
    
lib/recordclass/dataclass.py
intellimath/recordclass
# coding: utf-8

# The MIT License (MIT)

# Copyright (c) «2017-2023» «Shibzukhov Zaur, szport at gmail dot com»

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software - recordclass library - and associated documentation files
# (the "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish, distribute,
# sublicense, and/or sell copies of the Software, and to permit persons to whom
# the Software is furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

__all__ = 'datatype',

import sys as _sys
_PY36 = _sys.version_info[:2] >= (3, 6)
_PY37 = _sys.version_info[:2] >= (3, 7)
_PY310 = _sys.version_info[:2] >= (3, 10)
_PY311 = _sys.version_info[:2] >= (3, 11)
    
import typing
if _PY37:
    def _is_classvar(a_type):
        return (a_type is typing.ClassVar
                or (type(a_type) is typing._GenericAlias
                    and a_type.__origin__ is typing.ClassVar))
else:
    def _is_classvar(a_type):
        return isinstance(a_type, typing._ClassVar) #or issubclass(a_type, typing.ClassVar)

def _matching_annotations_and_defaults(annotations, defaults):
    first_default = False
    for name in annotations:
        if name in defaults:
            first_default = True
        else:
            if first_default:
                raise TypeError('A field without default value appears after a field with default value')
                
_ds_cache = {}
_ds_ro_cache = {}

class Field(dict):
    pass
                
class datatype(type):
    """
    Metatype for creating classes based on dataobject.
    """
    def __new__(metatype, typename, bases, ns, *,
                gc=False, fast_new=True, readonly=False, iterable=False,
                deep_dealloc=False, sequence=False, mapping=False,
                use_dict=False, use_weakref=False, hashable=False, 
                immutable_type=False, copy_default=False):

        from .utils import check_name, collect_info_from_bases
        from ._dataobject import dataobject
        from ._dataobject import dataobjectproperty
        from sys import intern as _intern
        if _PY311:
            from ._dataobject import member_new

        options = {}
        if gc:
            options['gc'] = gc
        if fast_new:
            options['fast_new'] = fast_new
        if readonly:
            options['readonly'] = readonly
        if iterable:
            options['iterable'] = iterable
        if deep_dealloc:
            options['deep_dealloc'] = deep_dealloc
        if sequence:
            options['sequence'] = sequence
        if mapping:
            options['mapping'] = mapping
        if hashable:
            options['hashable'] = hashable
        if use_dict:
            options['use_dict'] = use_dict
        if use_weakref:
            options['use_weakref'] = use_weakref
        if copy_default:
            options['copy_default'] = copy_default
        if immutable_type:
            options['immutable_type'] = immutable_type
            
        
        if bases:
            base0 = bases[0]
            if not issubclass(base0, dataobject):
                raise TypeError("First base class should be subclass of dataobject")
        else:
            bases = (dataobject,)

        annotations = ns.get('__annotations__', {})
        classvars = {fn for fn,tp in annotations.items() \
                        if _is_classvar(tp)}
        
        int_type = type(1)

        if '__fields__' in ns:
            fields = ns['__fields__']
            fields_dict = {}
            if not isinstance(fields, int_type):
                for fn in fields:
                    if fn in classvars:
                        raise TypeError(f'__fields__ contain  {fn}:ClassVar')
                    if fn in annotations:
                        fields_dict[fn] = f = Field(type=annotations[fn])
                    else:
                        fields_dict[fn] = f = Field()
        else:
            fields_dict = {fn:Field(type=tp) \
                           for fn,tp in annotations.items() \
                           if fn not in classvars}
            fields = tuple(fields_dict)
            
        has_fields = True
        if isinstance(fields, int_type):
            has_fields = False
            n_fields = fields
            sequence = options['sequence'] = True
            iterable = options['iterable'] = True
            fields = ()
        else:
            fields = [_intern(check_name(fn)) for fn in fields]

        if sequence:
            options['sequence'] = True
        if mapping:
            options['mapping'] = True
            
        if sequence or mapping:
            iterable = True
            options['iterable'] = True
            
        if '__iter__' in ns:
            iterable = options['iterable'] = True
        # else:
        #     for base in bases:
        #         if '__iter__' in base.__dict__:
        #             iterable = True
        #             options['iterable'] = True
        #             break

        if readonly:
            hashable = True
        if hashable:
            options['hashable'] = hashable

        if not _PY311 and immutable_type:
            import warnings
            warnings.warn("immutable_type parameter can be used only for python >= 3.11")

        if has_fields:
            if annotations:
                annotations = {fn:annotations[fn] \
                               for fn in fields \
                               if fn in annotations}

            if '__dict__' in fields:                
                fields.remove('__dict__')
                if '__dict__' in annotations:
                    del annotations['__dict__']
                use_dict = options['use_dict'] = True
                import warnings
                warnings.warn("Use 'use_dict=True' instead")

            if '__weakref__' in fields:
                fields.remove('__weakref__')
                if '__weakref__' in annotations:
                    del annotations['__weakref__']
                use_weakref = options['use_weakref'] = True
                import warnings
                warnings.warn("Use 'use_weakref=True' instead")

            if '__defaults__' in ns:
                defaults_dict = ns['__defaults__']
            else:
                defaults_dict = {f:ns[f] for f in fields if f in ns}
            _matching_annotations_and_defaults(annotations, defaults_dict)

            for fn,val in defaults_dict.items():
                f = fields_dict.get(fn, None)
                if f is not None:
                    f['default'] = val

            if readonly:
                if type(readonly) is bool:
                    for f in fields_dict.values():
                        f['readonly'] = True
                else:
                    for fn in readonly:
                        fields_dict[fn]['readonly'] = True
            fields = [f for f in fields if f in fields_dict]

            if bases and (len(bases) > 1) or bases[0] is not dataobject:
                fields = collect_info_from_bases(bases, fields, fields_dict, options)
                for fn in classvars:
                    if fn in fields:
                        raise TypeError(f"field '{fn}' is a class variable and an instance field at the same time")
                use_dict = options.get('use_dict', False)
                use_weakref = options.get('use_weakref', False)
                copy_default = options.get('copy_default', False)
                gc = options.get('gc', False)
                iterable = options.get('iterable', False)
                defaults_dict = {fn:fd['default'] for fn,fd in fields_dict.items() if 'default' in fd} 
                annotations = {fn:fd['type'] for fn,fd in fields_dict.items() if 'type' in fd} 

            fields = tuple(fields)

            if has_fields and not fast_new and ('__new__' not in ns or '__init__' not in ns):
                __new__ = _make_new_function(typename, fields, defaults_dict, annotations, use_dict)
                __new__.__qualname__ = typename + '.' + '__new__'
                if not __new__.__doc__:
                    __new__.__doc__ = _make_cls_doc(typename, fields, annotations, defaults)

                ns['__new__'] = __new__

        module = ns.get('__module__', None)
        if module is None:
            try:
                module = _sys._getframe(2).f_globals.get('__name__', '__main__')
                ns['__module'] = module
            except (AttributeError, ValueError):
                pass
        else:
            pass
                    
        if has_fields:
            options['fields_dict'] = fields_dict
            default_vals = tuple([fields_dict[fn].get('default',None) for fn in fields])
            ns['__fields__'] = fields
            ns['__defaults__'] = defaults_dict
            ns['__default_vals__'] = default_vals
            ns['__annotations__'] = annotations

            if _PY310:
                ns['__match_args__'] = fields

            if '__doc__' not in ns:
                ns['__doc__'] = _make_cls_doc(typename, fields, annotations, default_vals, use_dict)
        
        ns['__options__'] = options

        if has_fields and not _PY311:
            for i, name in enumerate(fields):
                fd = fields_dict[name]
                fd_readonly = fd.get('readonly', False)
                if fd_readonly:
                    ds = _ds_ro_cache.get(i, None)
                else:
                    ds = _ds_cache.get(i, None)
                if ds is None:
                    if fd_readonly:
                        ds = dataobjectproperty(i, True)
                    else:
                        ds = dataobjectproperty(i, False)
                ns[name] = ds

        cls = type.__new__(metatype, typename, bases, ns)

        if has_fields and _PY311:
            for i, name in enumerate(fields):
                fd = fields_dict[name]
                fd_readonly = fd.get('readonly', False)
                if fd_readonly:
                    ds = member_new(cls, name, i, 1)
                else:
                    ds = member_new(cls, name, i, 0)
                setattr(cls, name, ds)

        cls.__configure__(sequence=sequence, mapping=mapping, readonly=readonly,
                          hashable=hashable, iterable=iterable, use_dict=use_dict, use_weakref=use_weakref,
                          gc=gc, deep_dealloc=deep_dealloc, immutable_type=immutable_type,
                          copy_default=copy_default,
                         )

        return cls

    def __configure__(cls,  gc=False, fast_new=True, readonly=False, iterable=False,
                            deep_dealloc=False, sequence=False, mapping=False,
                            use_dict=False, use_weakref=False, hashable=False, 
                            mapping_only=False, immutable_type=False, copy_default=False):

        from ._dataobject import (_datatype_collection_mapping, _datatype_hashable, _datatype_iterable,
                                  _datatype_use_weakref, _datatype_use_dict, _datatype_enable_gc, 
                                  _datatype_deep_dealloc, _datatype_vectorcall, _pytype_modified, 
                                  _datatype_immutable, _dataobject_type_init, _datatype_copy_default,
                                  _datatype_from_basetype_hashable, _datatype_from_basetype_iterable)
        from .utils import _have_pyinit, _have_pynew


        is_pyinit = '__init__' in cls.__dict__
        is_pynew = '__new__' in cls.__dict__
        if not is_pyinit:
            is_pyinit = _have_pyinit(cls.__bases__)
        if not is_pynew:
            is_pynew = _have_pynew(cls.__bases__)

        if immutable_type and (is_pynew or is_pyinit):
            raise TypeError('if immutable_type=True then __init__ or __new__ are not allowed')
        
        _dataobject_type_init(cls)

        _datatype_collection_mapping(cls, sequence, mapping, readonly)
        if hashable:
            _datatype_hashable(cls)
        else:
            _datatype_from_basetype_hashable(cls)
        if iterable:
            _datatype_iterable(cls)
        else:
            _datatype_from_basetype_iterable(cls)
        if use_dict:
            _datatype_use_dict(cls)
        if use_weakref:
            _datatype_use_weakref(cls)
        if gc:
            _datatype_enable_gc(cls)
        if deep_dealloc:
            _datatype_deep_dealloc(cls)
        if not copy_default and not is_pyinit and not is_pynew:
            _datatype_vectorcall(cls)
        if copy_default and not is_pyinit and not is_pynew:
            _datatype_copy_default(cls)
        if _PY311 and immutable_type:
            _datatype_immutable(cls)
        _pytype_modified(cls)

    def __delattr__(cls, name):
        from ._dataobject import dataobjectproperty
        if name in cls.__dict__:
            o = getattr(cls, name)
            if type(o) is dataobjectproperty or name in {'__fields__', '__defaults__', '__annotations__'}:
                raise AttributeError(f"Attribute {name} of the class {cls.__name__} can't be deleted")
        type.__delattr__(cls, name)

    def __setattr__(cls, name, ob):
        if name in {'__fields__', '__defaults__', '__annotations__'}:
            raise AttributeError(f"Attribute {name} of the class {cls.__name__} can't be modified")
        type.__setattr__(cls, name, ob)

def _make_new_function(typename, fields, defaults_dict, annotations, use_dict):

    from ._dataobject import dataobject

    if fields and defaults_dict:
        fields2 = [fn for fn in fields if fn not in defaults_dict] + \
                  ["%s=%r" % (fn,None) for fn in fields if fn in defaults_dict]
    else:
        fields2 = fields

    joined_fields = ', '.join(fields)
    joined_fields2 = ', '.join(fields2)

    new_func_def = f"""\
def __new__(_cls_, {joined_fields2}):
    "Create new instance: {typename}({joined_fields2})"
    return _method_new(_cls_, {joined_fields})
"""
    
    new_func_def_use_dict = f"""\
def __new__(_cls_, {joined_fields2}, **kw):
    "Create new instance: {typename}({joined_fields2}, **kw)"
    return _method_new(_cls_, {joined_fields}, **kw)
"""
    
    if use_dict:
        new_func_def = new_func_def_use_dict

    # if has_init:
    #     _method_new = new_basic
    # else:
    _method_new = dataobject.__new__

    namespace = dict(_method_new=_method_new)

    code = compile(new_func_def, "", "exec")
    eval(code, namespace)

    __new__ = namespace['__new__']

    if annotations:
        __new__.__annotations__ = annotations

    if defaults_dict:
        __new__.__defaults__ = tuple(defaults_dict.values())

    return __new__

def _type2str(tp):
    if hasattr(tp, '__name__'):
        return tp.__name__
    else:
        return str(tp)

def _make_cls_doc(typename, fields, annotations, defaults, use_dict):

    fields2 = []
    for i, fn in enumerate(fields):
        if fn in annotations:
            tp = annotations[fn]
            fn_txt = "%s:%s" % (fn, (tp if type(tp) is str else _type2str(tp)))            
        else:
            fn_txt = fn
        defval = defaults[i]
        if defval is not None:
            fn_txt += "=%s" % repr(defval)
        fields2.append(fn_txt)

    joined_fields2 = ', '.join(fields2)

    if use_dict:
        doc = f"""{typename}({joined_fields2}, **kw)\n--\nCreate class {typename} instance"""
    else:
        doc = f"""{typename}({joined_fields2})\n--\nCreate class {typename} instance"""

    return doc

lib/recordclass/datatype.py
intellimath/recordclass
# coding: utf-8
 
# The MIT License (MIT)

# Copyright (c) «2015-2023» «Shibzukhov Zaur, szport at gmail dot com»

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software - recordclass library - and associated documentation files 
# (the "Software"), to deal in the Software without restriction, including 
# without limitation the rights to use, copy, modify, merge, publish, distribute, 
# sublicense, and/or sell copies of the Software, and to permit persons to whom 
# the Software is furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

__all__ = 'recordclass', 'RecordclassStorage'

from .datatype import datatype
from ._dataobject import dataobject

def _add_namedtuple_api(typename, readonly):
    from ._dataobject import astuple, asdict

    def _make(_cls, iterable):
        ob = _cls(*iterable)
        return ob

    _make.__doc__ = f'Make a new {typename} object from a sequence or iterable'

    if readonly:
        def _replace(_self, **kwds):
            result = _self._make(map(kwds.pop, _self.__fields__, _self))
            if kwds:
                kwnames = tuple(kwds)
                raise AttributeError(f'Got unexpected field names: {kwnames}')
            return result
    else:
        def _replace(_self, **kwds):
            for name, val in kwds.items():
                setattr(_self, name, val)
            return _self

    _replace.__doc__ = f'Return a new {typename} object replacing specified fields with new values'

    def _asdict(self):
        'Return a new dict which maps field names to their values.'
        return asdict(self)

    for method in (_make, _replace, _asdict,):
        method.__qualname__ = typename + "." + method.__name__        

    _make = classmethod(_make)

    ns = { '_make': _make, 
           '_replace': _replace, 
           '_asdict': _asdict
         }
    return ns

def recordclass(typename, fields, defaults=None, *,
                rename=False, readonly=False, hashable=False, gc=False,
                use_dict=False, use_weakref=False, fast_new=True, mapping=False, 
                immutable_type=False, copy_default=False, module=None):
    """Returns a new class with named fields, small memory footprint and namedtuple-lie API.

    >>> Point = recordclass('Point', 'x y')
    >>> Point.__doc__                   # docstring for the new class
    'Point(x, y)'
    >>> p = Point(1,2)                  # instantiate with positional args or keywords
    >>> print(p)
    Point(x=1, y=2)
    >>> p[0] + p[1]                     # indexable like a plain tuple
    3
    >>> x, y = p                        # unpack like a regular tuple
    >>> x, y
    (1, 2)
    >>> p.x + p.y                       # fields also accessable by name
    3
    >>> d = p._asdict()                 # convert to a dictionary
    >>> d['y'] =-1                      # assign new value
    >>> Point(**d)                      # convert from a dictionary
    Point(x=1, y=-1)
    """
    from .dataclass import make_dataclass
    import sys as _sys
    
    ns = _add_namedtuple_api(typename, readonly)
    if readonly:
        hashable = True
    
    if module is None:
        try:
            _module = _sys._getframe(1).f_globals.get('__name__', '__main__')
        except (AttributeError, ValueError):
            pass
    else:
        _module = module

    invalid_names = ('_make', '_replace', '_asdict')

    return make_dataclass(typename, fields, defaults=defaults, namespace=ns,
                use_dict=use_dict, use_weakref=use_weakref, hashable=hashable, 
                sequence=True, mapping=mapping, iterable=True, rename=rename, 
                readonly=readonly, module=_module, invalid_names = invalid_names,
                fast_new=fast_new, gc=False, immutable_type=immutable_type,
                copy_default=copy_default)

class RecordclassStorage:
    #
    def __init__(self):
        self._storage = {}
    #
    def clear_storage(self):
        self._storage.clear()
    #
    def recordclass(self, name, fields, defaults=None, **kw):
        if type(fields) is str:
            fields = fields.replace(',', ' ').split()
            fields = ' '.join(fn.strip() for fn in fields)
        else:
            fields = ' '.join(fields)
        key = (name, fields)
        cls = self._storage.get(key, None)
        if cls is None:
            cls = recordclass(name, fields, defaults, **kw)
            self._storage[key] = cls
        return cls


lib/recordclass/recordclass.py
intellimath/recordclass
import unittest
import pickle, copy
import keyword
import re
import sys
import gc
import weakref

from recordclass import make_dataclass, make_arrayclass, dataobject, make, clone, update
from recordclass import datatype, asdict, astuple, join_dataclasses

if 'PyPy' in sys.version:
    is_pypy = True
else:
    is_pypy = False
    from recordclass.utils import headgc_size, ref_size, pyobject_size, pyvarobject_size, pyssize

TPickle2 = make_dataclass("TPickle2", ('x','y','z'))
TPickle3 = make_dataclass("TPickle3", ('x','y','z'), use_dict=True)

##########################################################################
        
class DataobjectTest(unittest.TestCase):

    def test_bad_makeclass(self):
        with self.assertRaises(TypeError):        
            A = make_dataclass("A", 'x y', defaults=(0,0,0))
            # a = A(x=1, y=2, z=3)
    
    def test_bad_makeclass2(self):
        with self.assertRaises(TypeError):        
            A = make_dataclass("A", 'x y', defaults=(0,0,0), fast_new=False)
            # a = A(x=1, y=2, z=3)

    def test_bad_call(self):
        A = make_dataclass("A", 'x y', defaults=(0,), fast_new=False)
        with self.assertRaises(TypeError):        
            a = A(x=1, y=2, z=3)

    def test_bad_call2(self):
        A = make_dataclass("A", 'x y', defaults=(0,))
        with self.assertRaises(TypeError):        
            a = A(x=1, y=2, z=3)

    def test_caching(self):
        from recordclass.dataclass import DataclassStorage
        ds = DataclassStorage()
        A = ds.make_dataclass('A', ('x', 'y'))
        B = ds.make_dataclass('A', ['x', 'y'])
        self.assertEqual(A, B)

    def test_dataclass_asdict(self):
        A = make_dataclass("A", {'x':int, 'y':int})
        a = A(x=1,y=2)
        d = asdict(a)
        self.assertEqual(d, {'x':1, 'y':2})

    def test_dataclass_empty_astuple(self):
        A = make_dataclass("A", ())
        a = A()
        self.assertEqual(len(a), 0)
        t = astuple(a)
        self.assertEqual(t, ())

    def test_dataclass_empty_astuple(self):
        class A(dataobject):
            pass
        a = A()
        t = astuple(a)
        self.assertEqual(t, ())

    def test_dataclass_astuple(self):
        A = make_dataclass("A", {'x':int, 'y':int})
        a = A(x=1,y=2)
        t = astuple(a)
        self.assertEqual(t, (1, 2))

    def test_dataclass_astuple_iterable(self):
        A = make_dataclass("A", {'x':int, 'y':int}, iterable=True)
        a = A(x=1,y=2)
        t = astuple(a)
        self.assertEqual(t, (1, 2))
        d = asdict(a)
        self.assertEqual(d, {'x':1, 'y':2})

    def test_dataobject_assign(self):
        A = make_dataclass("A", ('x', 'y'), sequence=True)
        a = A(1,2)
        if not is_pypy:
            self.assertEqual(gc.is_tracked(a), False)
        a[0] = 100
        a[1] = 200
        self.assertEqual(a.x, 100)
        self.assertEqual(a.y, 200)
        a.x = -100
        a.y = -200
        self.assertEqual(a[0], -100)
        self.assertEqual(a[1], -200)
        a = None

    def test_dataobject_type(self):
        A = make_dataclass("A", 'x y')
        A.color = 'red'

    # def test_dataobject_immutable_type(self):
    #     A = make_dataclass("A", 'x y', immutable_type=True)
    #     # with self.assertRaises(TypeError):     
    #     A.color = 'red'
    
    def test_dataobject_local_dict(self):
        A = make_dataclass("A", ('x', 'y'), use_dict=True)
        a = A(1,2)
        a.a = 1
        # print(a.__dict__)
        self.assertEqual(a.a, 1)
        self.assertEqual(a.__dict__, {'a':1})

    def test_datatype(self):
        A = make_dataclass("A", ('x', 'y'))
        a = A(1,2)
        # self.assertEqual(repr(a), "A(x=1, y=2)")
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)
        self.assertEqual(asdict(a), {'x':1, 'y':2})
        if not is_pypy:
            self.assertEqual(sys.getsizeof(a), pyobject_size+2*ref_size)
        # with self.assertRaises(TypeError):     
        #     weakref.ref(a)
        with self.assertRaises(AttributeError):     
            a.__dict__
        with self.assertRaises(AttributeError):     
            a.z = 3
        with self.assertRaises(AttributeError):     
            a.z            
        a = None

    def test_datatype_nosq_nomp(self):
        A = make_dataclass("A", ('x', 'y'))
        a = A(1,2)
        if not is_pypy:
            self.assertEqual(gc.is_tracked(a), False)
        with self.assertRaises(TypeError):     
            a[0]
        with self.assertRaises(TypeError):     
            a['x']

    def test_datatype_sq_nomp(self):
        A = make_dataclass("A", ('x', 'y'), sequence=True)

        a = A(1,2)
        if not is_pypy:
            self.assertEqual(gc.is_tracked(a), False)
        self.assertEqual(a[0], 1)
        self.assertEqual(a[1], 2)
        with self.assertRaises(TypeError):     
            a['x']

    def test_datatype_nosq_mp(self):
        A = make_dataclass("A", ('x', 'y'), mapping=True)

        a = A(1,2)
        if not is_pypy:
            self.assertEqual(gc.is_tracked(a), False)
        self.assertEqual(a['x'], 1)
        self.assertEqual(a['y'], 2)
        with self.assertRaises(TypeError):     
            a[0]

    def test_datatype_sq_mp(self):
        A = make_dataclass("A", ('x', 'y'), sequence=True, mapping=True)

        a = A(1,2)
        if not is_pypy:
            self.assertEqual(gc.is_tracked(a), False)
        self.assertEqual(a['x'], 1)
        self.assertEqual(a['y'], 2)
        self.assertEqual(a[0], 1)
        self.assertEqual(a[1], 2)
        a[0] = 100
        self.assertEqual(a[0], 100)

    def test_datatype_copy(self):
        A = make_dataclass("A", ('x', 'y'))
        a = A(1,2)
        b = a.__copy__()
        self.assertEqual(a, b)
        del b

    def test_datatype_copy_dict(self):
        A = make_dataclass("A", ('x', 'y'), use_dict=True)

        a = A(1,2, z=3,w=4)
        if not is_pypy:
            self.assertEqual(gc.is_tracked(a), False)
        b = a.__copy__()
        if not is_pypy:
            self.assertEqual(gc.is_tracked(b), False)
        self.assertEqual(a, b)
        del b

    def test_datatype_subscript(self):
        A = make_dataclass("A", ('x', 'y'), mapping=True)
        a = A(1,2)
        self.assertEqual(a['x'], 1)
        self.assertEqual(a['y'], 2)
        a['x'] = 100
        self.assertEqual(a['x'], 100)
        a = None
        
#     def test_datatype_dict(self):
#         A = make_dataclass("A", ('x', 'y'), use_dict=True, use_weakref=True)

#         a = A(1,2)
#         self.assertEqual(len(a), 2)
#         if not is_pypy:
#             self.assertEqual(gc.is_tracked(a), False)
#         self.assertEqual(repr(a), "A(x=1, y=2)")
#         self.assertEqual(a.x, 1)
#         self.assertEqual(a.y, 2)
#         self.assertEqual(asdict(a), {'x':1, 'y':2})
#         weakref.ref(a)
#         self.assertEqual(a.__dict__, {})
        
#         a.z = 3
#         self.assertEqual(a.z, a.__dict__['z'])
#         a = None

    def test_datatype_dict2(self):
        A = make_dataclass("A", ('x', 'y'), use_dict=True)
        a = A(1,2,v=3,z=4)
        self.assertEqual(a.__dict__, {'v':3, 'z':4})
        self.assertEqual(len(a), 4)
        b = A(1,2,z=3)
        self.assertEqual(b.__dict__, {'z':3})
        self.assertEqual(repr(b), "A(x=1, y=2, **{'z': 3})")
        self.assertEqual(len(b), 3)

    def test_defaults(self):
        A = make_dataclass("A", ('x', 'y', 'z'), defaults=(100, 200, 300))
                
        a1 = A()
        self.assertEqual(repr(a1), "A(x=100, y=200, z=300)")
        self.assertEqual(a1.x, 100)
        self.assertEqual(a1.y, 200)
        self.assertEqual(a1.z, 300)
        self.assertEqual(asdict(a1), {'x':100, 'y':200, 'z':300})
        a2 = A(1,z=400)
        self.assertEqual(repr(a2), "A(x=1, y=200, z=400)")
        self.assertEqual(a2.x, 1)
        self.assertEqual(a2.y, 200)
        self.assertEqual(a2.z, 400)
        self.assertEqual(asdict(a2), {'x':1, 'y':200, 'z':400})
        a3 = A(1,2,z=400)
        self.assertEqual(repr(a3), "A(x=1, y=2, z=400)")
        self.assertEqual(a3.x, 1)
        self.assertEqual(a3.y, 2)
        self.assertEqual(a3.z, 400)
        self.assertEqual(asdict(a3), {'x':1, 'y':2, 'z':400})

    def test_dictlike_1(self):
        A = make_dataclass("A", 'x y', mapping=True)
        a = A(x=1, y=2)
        self.assertEqual(a['x'], 1)
        self.assertEqual(a['y'], 2)
        a['x'] = 100
        a['y'] = 200
        self.assertEqual(a['x'], 100)
        self.assertEqual(a['y'], 200)
        
    def test_empty_fields_asdict(self):
        A = make_dataclass("A", ())
        a = A()
        d = asdict(a)
        self.assertEqual(d, {})

    def test_enable_gc(self):
        A = make_dataclass("A", ('x', 'y', 'z'))
        B = make_dataclass("B", ('x', 'y', 'z'), gc=True)
        a = A(1,2,3)
        b = B(1,2,3)
        self.assertEqual(a.x, b.x)
        self.assertEqual(a.y, b.y)
        self.assertEqual(a.z, b.z)
        if not is_pypy:
            self.assertEqual(sys.getsizeof(b)-sys.getsizeof(a), headgc_size)
                           
    def test_subclass(self):
        A = make_dataclass("A", ('x', 'y'))
                
        class B(A):
            pass

        self.assertEqual(type(A), type(B))
        if not is_pypy:
            self.assertEqual(B.__dictoffset__, 0)
            self.assertEqual(B.__weakrefoffset__, 0)
        b = B(1,2)
        if not is_pypy:
            self.assertEqual(gc.is_tracked(b), False)
        self.assertEqual(repr(b), "B(x=1, y=2)")
        self.assertEqual(b.x, 1)
        self.assertEqual(b.y, 2)
        self.assertEqual(asdict(b), {'x':1, 'y':2})
        # self.assertEqual(sys.getsizeof(b), pyobject_size + 2*ref_size)
        if not is_pypy:
            self.assertEqual(A.__basicsize__, B.__basicsize__)
            with self.assertRaises(TypeError):     
                weakref.ref(b)
        with self.assertRaises(AttributeError):     
            b.__dict__        

    def test_subclass2(self):
        A = make_dataclass("A", ('x', 'y'))

        class B(A):
            __fields__ = ('z',)
                
        class C(B):
            pass

        self.assertEqual(type(A), type(B))
        self.assertEqual(type(C), type(B))
        if not is_pypy:
            self.assertEqual(C.__dictoffset__, 0)
            self.assertEqual(C.__weakrefoffset__, 0)
        c = C(1,2,3)
        if not is_pypy:
            self.assertEqual(gc.is_tracked(c), False)
        self.assertEqual(repr(c), "C(x=1, y=2, z=3)")
        self.assertEqual(c.x, 1)
        self.assertEqual(c.y, 2)
        self.assertEqual(c.z, 3)
        self.assertEqual(asdict(c), {'x':1, 'y':2, 'z':3})
        # self.assertEqual(sys.getsizeof(c), pyobject_size + 3*ref_size)
        if not is_pypy:
            with self.assertRaises(TypeError):     
                weakref.ref(c)
        with self.assertRaises(AttributeError):     
            c.__dict__
        c = None
        
    def test_keyword_args(self):
        A = make_dataclass("A", ('x', 'y', 'z'), defaults=3*(None,))

        a1 = A(x=1)
        self.assertEqual(repr(a1), "A(x=1, y=None, z=None)")
        self.assertEqual(a1.x, 1)
        self.assertEqual(a1.y, None)
        self.assertEqual(a1.z, None)
        a2 = A(x=1,y=2)
        self.assertEqual(repr(a2), "A(x=1, y=2, z=None)")
        self.assertEqual(a2.x, 1)
        self.assertEqual(a2.y, 2)
        self.assertEqual(a2.z, None)
        a3 = A(x=1,y=2,z=3)
        self.assertEqual(repr(a3), "A(x=1, y=2, z=3)")
        self.assertEqual(a3.x, 1)
        self.assertEqual(a3.y, 2)
        self.assertEqual(a3.z, 3)

    def test_keyword_args_defaults(self):
        A = make_dataclass("A", ('x', 'y', 'z'), defaults=(100, 200, 300))

        a1 = A(x=1)
        self.assertEqual(repr(a1), "A(x=1, y=200, z=300)")
        self.assertEqual(a1.x, 1)
        self.assertEqual(a1.y, 200)
        self.assertEqual(a1.z, 300)
        a2 = A(x=1,y=2)
        self.assertEqual(repr(a2), "A(x=1, y=2, z=300)")
        self.assertEqual(a2.x, 1)
        self.assertEqual(a2.y, 2)
        self.assertEqual(a2.z, 300)
        a3 = A(x=1,y=2,z=3)
        self.assertEqual(repr(a3), "A(x=1, y=2, z=3)")
        self.assertEqual(a3.x, 1)
        self.assertEqual(a3.y, 2)
        self.assertEqual(a3.z, 3)

    def test_keyword_args_defaults2(self):
        A = make_dataclass("A", ('x', 'y', 'z'), fast_new=True)

        a1 = A(x=1)
        self.assertEqual(repr(a1), "A(x=1, y=None, z=None)")
        self.assertEqual(a1.x, 1)
        self.assertEqual(a1.y, None)
        self.assertEqual(a1.z, None)
        a2 = A(x=1,y=2)
        self.assertEqual(repr(a2), "A(x=1, y=2, z=None)")
        self.assertEqual(a2.x, 1)
        self.assertEqual(a2.y, 2)
        self.assertEqual(a2.z, None)
        a3 = A(x=1,y=2,z=3)
        self.assertEqual(repr(a3), "A(x=1, y=2, z=3)")
        self.assertEqual(a3.x, 1)
        self.assertEqual(a3.y, 2)
        self.assertEqual(a3.z, 3)

    def test_keyword_args_defaults3(self):
        A = make_dataclass("A", ('x', 'y', 'z'), fast_new=True)

        a1 = A(1, y=2,)
        self.assertEqual(repr(a1), "A(x=1, y=2, z=None)")
        self.assertEqual(a1.x, 1)
        self.assertEqual(a1.y, 2)
        self.assertEqual(a1.z, None)
        a2 = A(1, **{'y':2,'z':3})
        self.assertEqual(repr(a2), "A(x=1, y=2, z=3)")
        self.assertEqual(a2.x, 1)
        self.assertEqual(a2.y, 2)
        self.assertEqual(a2.z, 3)
        a3 = A(x=1, **{'y':2,'z':3})
        self.assertEqual(repr(a3), "A(x=1, y=2, z=3)")
        self.assertEqual(a3.x, 1)
        self.assertEqual(a3.y, 2)
        self.assertEqual(a3.z, 3)
        
    def test_missing_args(self):
        A = make_dataclass("A", ("a", "b", "c"), fast_new=True, sequence=True)
        # print(A.__options__)
        a=A(1)
        self.assertEqual(a[0], 1)
        self.assertEqual(a[1], None)
        self.assertEqual(a[2], None)

    def test_missing_args2(self):
        A = make_dataclass("A", ('x', 'y', 'z'))
        a=A(1)
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, None)
        self.assertEqual(a.z, None)
        
    def test_missing_args3(self):
        A = make_dataclass("A", ('a','b','c'), fast_new=True)
        a=A(1)
        self.assertEqual(a.a, 1)
        self.assertEqual(a.b, None)
        self.assertEqual(a.c, None)
        
    def test_missing_args4(self):
        A = make_dataclass("A", ('a','b','c'), defaults=(-1,), fast_new=True)
        a=A(1)
#         with self.assertRaises(TypeError):     
#             a=A(1)
        self.assertEqual(a.a, 1)
        self.assertEqual(a.b, None)
        self.assertEqual(a.c, -1)

    def test_missing_args5(self):
        A = make_dataclass("A", ('a','b','c'), defaults=(-1,-2), fast_new=True)
#         print(A.__defualts__)
        a=A(1)
        self.assertEqual(a.a, 1)
        self.assertEqual(a.b, -1)
        self.assertEqual(a.c, -2)
        
    def test_tuple2(self):
        A = make_dataclass("A", ('x', 'y', 'z'), iterable=True)
        a=A(1, 2.0, "a")
        self.assertEqual(tuple(a), (1, 2.0, "a"))

    def test_hash(self):
        A = make_dataclass("A", ("a", "b", "c"), hashable=True)
        a = A(-1, -2.0, "b")
        # print(a.__hash__)
        hash(a)

    def test_hash_subcl(self):
        A = make_dataclass("A", ("a", "b", "c"), hashable=True)
        class B(A):
            pass
        b = B(1, 2.0, "a")
        hash(b)
        
    def test_no_hash(self):
        A = make_dataclass("A", ("a", "b", "c"))
        a = A(1, 2.0, "a")
        self.assertEqual(hash(a), id(a))

    def test_no_hash2(self):
        A = make_dataclass("A", ("a", "b", "c"), hashable=True)
        class B(A, hashable=False):
            pass
        b = B(1, 2.0, "a")
        hash(b)
#         print(hash(a))
        # with self.assertRaises(TypeError):
        #     hash(b)
            
    def test_reduce(self):
        A = make_dataclass("A", ("x","y","z"))
        a = A(1,2,3)
        o,t = a.__reduce__()
        self.assertEqual(o, A)
        self.assertEqual(t, (1,2,3))

    def test_pickle2(self):
        p = TPickle2(10, 20, 30)
        for module in (pickle,):
            loads = getattr(module, 'loads')
            dumps = getattr(module, 'dumps')
            for protocol in range(-1, module.HIGHEST_PROTOCOL + 1):
                tmp = dumps(p, protocol)
                q = loads(tmp)
                self.assertEqual(p, q)

    def test_pickle3(self):
        p = TPickle3(10, 20, 30)
        p.a = 1
        p.b = 2
        for module in (pickle,):
            loads = getattr(module, 'loads')
            dumps = getattr(module, 'dumps')
            for protocol in range(-1, module.HIGHEST_PROTOCOL + 1):
                tmp = dumps(p, protocol)
                q = loads(tmp)
                self.assertEqual(p, q)

    def test_pickle4(self):
        p = TPickle3(10, 20, 30)
        p.a = 1
        p.b = 2
        for module in (pickle,):
            loads = getattr(module, 'loads')
            dumps = getattr(module, 'dumps')
            for protocol in range(-1, module.HIGHEST_PROTOCOL + 1):
                tmp = dumps(p, protocol)
                q = loads(tmp)
                self.assertEqual(p, q)
        
    def test_iter2(self):
        A = make_dataclass("A", ('x', 'y', 'z'), iterable=True)
        a=A(1, 2.0, "a")
        self.assertEqual(list(iter(a)), [1, 2.0, "a"])

    def test_iter3(self):
        A = make_dataclass("A", ('x', 'y', 'z'))
        a=A(1, 2.0, "a")
        with self.assertRaises(TypeError):
            iter(a)
            
    def test_iterable_base(self):
        class iterable_do(dataobject, iterable=True):
            pass
        
        class A(iterable_do):
            __fields__ = 'x', 'y', 'z'
            
        a=A(1, 2.0, "a")
        self.assertEqual(list(a), [1, 2.0, "a"])

    def test_iterable_base2(self):
        class iterable_do(dataobject, iterable=True):
            pass
        
        class A(iterable_do):
            __fields__ = 'x', 'y', 'z'
            
        a=A(1, 2.0, "a")
        self.assertEqual(list(a), [1, 2.0, "a"])

    def test_fields_dict(self):
        A = make_dataclass("A", {'x':int, 'y':int})
        a = A(x=1,y=2)
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)

    if not is_pypy:
        def test_refleak_on_assignemnt_do(self):
            Test = make_dataclass("Test", "x")
            a={}
            c = sys.getrefcount(a)
            b=Test(a)
            self.assertEqual(sys.getrefcount(a), c+1)
            b.x = None
            self.assertEqual(sys.getrefcount(a), c)

    def test_join_dataclasses(self):
        C1 = make_dataclass('C1', 'a b')
        C2 = make_dataclass('C2', 'c d')
        C = join_dataclasses('C', [C1, C2])
        CC = make_dataclass('CC', 'a b c d')
        cc = CC(1,2,3,4)
        c = C(1,2,3,4)
        self.assertNotEqual(c, cc)

    def test_join_dataclasses_intersection(self):
        C1 = make_dataclass('C1', 'a b')
        C2 = make_dataclass('C2', 'b c')
        with self.assertRaises(AttributeError):
            C = join_dataclasses('C', [C1, C2])

    def test_make0(self):
        A = make_dataclass("A", ())
        a = make(A, ())
        self.assertEqual(A.__fields__, ())
        self.assertEqual(len(a), 0)
            
    def test_make1(self):
        A = make_dataclass("A", {'x':int, 'y':int})
        a = make(A, (1,2))
        self.assertEqual(a, A(1, 2))
        
    def test_make2(self):
        A = make_dataclass("A", {'x':int, 'y':int})
        a = make(A, (1,), y=2)
        self.assertEqual(a, A(1, 2))

    def test_make3(self):
        A = make_dataclass("A", {'x':int, 'y':int}, use_dict=True)
        a = make(A, (1,2), **{'z':3})
        self.assertEqual(a, A(1, 2, z=3))
        
    def test_clone1(self):
        A = make_dataclass("A", {'x':int, 'y':int})
        a = A(1,2)
        b = clone(a, x=100, y=200)
        self.assertEqual(b, A(100, 200))

    def test_clone2(self):
        A = make_dataclass("A", {'x':int, 'y':int}, use_dict=True)
        a = A(1, 2, z=3)
        b = clone(a, x=100, y=200, **{'z':300})
        self.assertEqual(b, A(100, 200, z=300))

    def test_update1(self):
        A = make_dataclass("A", {'x':int, 'y':int})
        a = A(1,2)
        update(a, x=100, y=200)
        self.assertEqual(a, A(100, 200))

    def test_update2(self):
        A = make_dataclass("A", {'x':int, 'y':int}, use_dict=True)
        a = A(1, 2, z=3)
        update(a, x=100, y=200, z=300)
        self.assertEqual(a, A(100, 200, z=300))

    def test_update3(self):
        A = make_dataclass("A", {'x':int, 'y':int}, readonly=True)
        a = A(1, 2)
        with self.assertRaises(TypeError):        
            update(a, x=100)
    
    def test_readonly(self):
        A = make_dataclass("A", {'x':int, 'y':int}, readonly=True)
        a = A(1,2)
        with self.assertRaises(AttributeError):        
            a.x = -1
        with self.assertRaises(AttributeError):        
            a.y = -2

    # def test_del_property(self):
    #     A = make_dataclass("A", 'x y')
    #     with self.assertRaises(AttributeError):        
    #         del A.x
    #     with self.assertRaises(AttributeError):        
    #         delattr(A, 'x')

    # def test_del_value(self):
    #     A = make_dataclass("A", 'x y')
    #     a = A(1, 2)
    #     with self.assertRaises(AttributeError):        
    #         del a.x
    #     with self.assertRaises(AttributeError):        
    #         delattr(a, 'x')
            
    # def test_getitem(self):
    #     A = make_dataclass("A", 'x y')
    #     a = A(x=1, y=2)
    #     with self.assertRaises(TypeError):        
    #         a.__getitem__('x')
    #     with self.assertRaises(TypeError):        
    #         a.__getitem__('y')
    #     with self.assertRaises(TypeError):        
    #         a.__getitem__(0)
    #     with self.assertRaises(TypeError):        
    #         a.__getitem__(1)

    # def test_setitem(self):
    #     A = make_dataclass("A", 'x y')
    #     a = A(x=1, y=2)
    #     with self.assertRaises(TypeError):        
    #         a.__setitem__('x', 100)
    #     with self.assertRaises(TypeError):        
    #         a.__setitem__('y', 200)
    #     with self.assertRaises(TypeError):        
    #         a.__setitem__(0, 100)
    #     with self.assertRaises(TypeError):        
    #         a.__setitem__(1, 200)
            
#     def test_getitem_sq(self):
#         A = make_dataclass("A", 'x y', sequence=True)
#         a = A(x=1, y=2)
#         self.assertEqual(a.__getitem__(0), 1)
#         self.assertEqual(a.__getitem__(1), 2)
#         with self.assertRaises(TypeError):        
#             a.__getitem__('x')
#         with self.assertRaises(TypeError):        
#             a.__getitem__('y')

#     def test_setitem_sq(self):
#         A = make_dataclass("A", 'x y', sequence=True)
#         a = A(x=1, y=2)
#         a.__setitem__(0, 100)
#         a.__setitem__(1, 200)
#         self.assertEqual(a.__getitem__(0), 100)
#         self.assertEqual(a.__getitem__(1), 200)
#         with self.assertRaises(TypeError):        
#             a.__setitem__('x', 100)
#         with self.assertRaises(TypeError):        
#             a.__setitem__('y', 200)
            
#     def test_getitem_sq_ro(self):
#         A = make_dataclass("A", 'x y', sequence=True, readonly=True)
#         a = A(x=1, y=2)
#         self.assertEqual(a.__getitem__(0), 1)
#         self.assertEqual(a.__getitem__(1), 2)
#         with self.assertRaises(TypeError):        
#             a.__getitem__('x')
#         with self.assertRaises(TypeError):        
#             a.__getitem__('y')

#     def test_setitem_sq_ro(self):
#         A = make_dataclass("A", 'x y', sequence=True, readonly=True)
#         a = A(x=1, y=2)
#         with self.assertRaises(TypeError):        
#             a.__setitem__(0, 100)
#         with self.assertRaises(TypeError):        
#             a.__setitem__(1, 200)
#         with self.assertRaises(TypeError):        
#             a.__setitem__('x', 100)
#         with self.assertRaises(TypeError):        
#             a.__setitem__('y', 200)
            
#     def test_getitem_mp(self):
#         A = make_dataclass("A", 'x y', mapping=True)
#         a = A(x=1, y=2)
#         self.assertEqual(a.__getitem__('x'), 1)
#         self.assertEqual(a.__getitem__('y'), 2)
#         with self.assertRaises(TypeError):        
#             a.__getitem__(0)
#         with self.assertRaises(TypeError):        
#             a.__getitem__(1)

#     def test_setitem_mp(self):
#         A = make_dataclass("A", 'x y', mapping=True)
#         a = A(x=1, y=2)
#         a.__setitem__('x', 100)
#         a.__setitem__('y', 200)
#         self.assertEqual(a.__getitem__('x'), 100)
#         self.assertEqual(a.__getitem__('y'), 200)
#         with self.assertRaises(TypeError):        
#             a.__setitem__(0, 100)
#         with self.assertRaises(TypeError):        
#             a.__setitem__(1, 200)
            
#     def test_getitem_mp_ro(self):
#         A = make_dataclass("A", 'x y', mapping=True, readonly=True)
#         a = A(x=1, y=2)
#         self.assertEqual(a.__getitem__('x'), 1)
#         self.assertEqual(a.__getitem__('y'), 2)
#         with self.assertRaises(TypeError):        
#             a.__getitem__(0)
#         with self.assertRaises(TypeError):        
#             a.__getitem__(1)

#     def test_setitem_mp_ro(self):
#         A = make_dataclass("A", 'x y', mapping=True, readonly=True)
#         a = A(x=1, y=2)
#         with self.assertRaises(TypeError):        
#             a.__setitem__(0, 100)
#         with self.assertRaises(TypeError):        
#             a.__setitem__(1, 200)
#         with self.assertRaises(TypeError):        
#             a.__setitem__('x', 100)
#         with self.assertRaises(TypeError):        
#             a.__setitem__('y', 200)

#     def test_getitem_mp_sq(self):
#         A = make_dataclass("A", 'x y', mapping=True, sequence=True)
#         a = A(x=1, y=2)
#         self.assertEqual(a.__getitem__('x'), 1)
#         self.assertEqual(a.__getitem__('y'), 2)
#         self.assertEqual(a.__getitem__(0), 1)
#         self.assertEqual(a.__getitem__(1), 2)

#     def test_setitem_mp_sq(self):
#         A = make_dataclass("A", 'x y', mapping=True, sequence=True)
#         a = A(x=1, y=2)
#         a.__setitem__('x', 100)
#         a.__setitem__('y', 200)
#         self.assertEqual(a.__getitem__('x'), 100)
#         self.assertEqual(a.__getitem__('y'), 200)
#         self.assertEqual(a.__getitem__(0), 100)
#         self.assertEqual(a.__getitem__(1), 200)
#         a.__setitem__(0, -100)
#         a.__setitem__(1, -200)
#         self.assertEqual(a.__getitem__('x'), -100)
#         self.assertEqual(a.__getitem__('y'), -200)
#         self.assertEqual(a.__getitem__(0), -100)
#         self.assertEqual(a.__getitem__(1), -200)

#     def test_setitem_mp_sq_ro(self):
#         A = make_dataclass("A", 'x y', mapping=True, sequence=True, readonly=True)
#         a = A(x=1, y=2)
#         self.assertEqual(a.__getitem__('x'), 1)
#         self.assertEqual(a.__getitem__('y'), 2)
#         self.assertEqual(a.__getitem__(0), 1)
#         self.assertEqual(a.__getitem__(1), 2)
#         with self.assertRaises(TypeError):        
#             a.__setitem__(0, 100)
#         with self.assertRaises(TypeError):        
#             a.__setitem__(1, 200)
#         with self.assertRaises(TypeError):        
#             a.__setitem__('x', 100)
#         with self.assertRaises(TypeError):        
#             a.__setitem__('y', 200)
        
    def test_getkey(self):
        A = make_dataclass("A", 'x y')
        a = A(x=1, y=2)
        with self.assertRaises(TypeError):        
            a['x']
        with self.assertRaises(TypeError):        
            a['y']

    def test_setkey(self):
        A = make_dataclass("A", 'x y')
        a = A(x=1, y=2)
        with self.assertRaises(TypeError):        
            a['x'] = 100
        with self.assertRaises(TypeError):        
            a['y'] = 200
            
    def test_getkey_sq(self):
        A = make_dataclass("A", 'x y', sequence=True)
        a = A(x=1, y=2)
        with self.assertRaises(TypeError):        
            a['x']
        with self.assertRaises(TypeError):        
            a['y']

    def test_setkey_sq(self):
        A = make_dataclass("A", 'x y', sequence=True)
        a = A(x=1, y=2)
        with self.assertRaises(TypeError):        
            a['x'] = 100
        with self.assertRaises(TypeError):        
            a['y'] = 200
            
    def test_getkey_sq_ro(self):
        A = make_dataclass("A", 'x y', sequence=True, readonly=True)
        a = A(x=1, y=2)
        with self.assertRaises(TypeError):        
            a['x']
        with self.assertRaises(TypeError):        
            a['y']

    def test_setkey_sq_ro(self):
        A = make_dataclass("A", 'x y', sequence=True, readonly=True)
        a = A(x=1, y=2)
        with self.assertRaises(TypeError):        
            a['x'] = 100
        with self.assertRaises(TypeError):        
            a['y'] = 200
            
    def test_getkey_mp(self):
        A = make_dataclass("A", 'x y', mapping=True)
        a = A(x=1, y=2)
        self.assertEqual(a['x'], 1)
        self.assertEqual(a['y'], 2)

    def test_setkey_mp(self):
        A = make_dataclass("A", 'x y', mapping=True)
        a = A(x=1, y=2)
        a['x'] = 100
        a['y'] = 200
        self.assertEqual(a['x'], 100)
        self.assertEqual(a['y'], 200)
            
    def test_getkey_mp_ro(self):
        A = make_dataclass("A", 'x y', mapping=True, readonly=True)
        a = A(x=1, y=2)
        self.assertEqual(a['x'], 1)
        self.assertEqual(a['y'], 2)

    def test_setkey_mp_ro(self):
        A = make_dataclass("A", 'x y', mapping=True, readonly=True)
        a = A(x=1, y=2)
        with self.assertRaises(TypeError):        
            a['x'] =  100
        with self.assertRaises(TypeError):        
            a['y'] = 200

    def test_getkey_mp_sq(self):
        A = make_dataclass("A", 'x y', mapping=True, sequence=True)
        a = A(x=1, y=2)
        self.assertEqual(a['x'], 1)
        self.assertEqual(a['y'], 2)
        self.assertEqual(a[0], 1)
        self.assertEqual(a[1], 2)

    def test_setkey_mp_sq(self):
        A = make_dataclass("A", 'x y', mapping=True, sequence=True)
        a = A(x=1, y=2)
        a['x'] = 100
        a['y'] = 200
        self.assertEqual(a['x'], 100)
        self.assertEqual(a['y'], 200)
        self.assertEqual(a[0], 100)
        self.assertEqual(a[1], 200)
        a[0] = -100
        a[1] = -200
        self.assertEqual(a['x'], -100)
        self.assertEqual(a['y'], -200)
        self.assertEqual(a[0], -100)
        self.assertEqual(a[1], -200)

    def test_setkey_mp_sq_ro(self):
        A = make_dataclass("A", 'x y', mapping=True, sequence=True, readonly=True)
        a = A(x=1, y=2)
        self.assertEqual(a['x'], 1)
        self.assertEqual(a['y'], 2)
        self.assertEqual(a[0], 1)
        self.assertEqual(a[1], 2)
        with self.assertRaises(TypeError):        
            a[0] = 100
        with self.assertRaises(TypeError):        
            a[1] = 200
        with self.assertRaises(TypeError):        
            a['x'] = 100
        with self.assertRaises(TypeError):        
            a['y'] = 200
            
def main():
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(DataobjectTest))
    return suite

lib/recordclass/test/test_dataobject.py
intellimath/recordclass
import unittest
from recordclass import litelist, litelist_fromargs

import gc
import pickle
import sys

class litelistTest(unittest.TestCase):

    def test_len(self):
        a = litelist([])
        self.assertEqual(len(a), 0)
        a = litelist([1])
        self.assertEqual(len(a), 1)
        
    def test_items(self):
        a = litelist([1,2,3])
        self.assertEqual(a[0], 1)
        self.assertEqual(a[-1], 3)
        a[1] = 100
        self.assertEqual(a[1], 100)

    def test_remove(self):
        a = litelist([1,2,3])
        a.remove(2)
        self.assertEqual(a[0], 1)
        self.assertEqual(a[-1], 3)
        a = litelist([1,2,3])
        a.remove(1)
        self.assertEqual(a[0], 2)
        self.assertEqual(a[-1], 3)
        a = litelist([1,2,3])
        a.remove(3)
        self.assertEqual(a[0], 1)
        self.assertEqual(a[-1], 2)
        
    def test_gc(self):
        a = litelist([1,2,3])
        # self.assertEqual(sys.getsizeof(a), a.__sizeof__())

    def test_append(self):
        a = litelist([])
        a.append(1)
        self.assertEqual(a[0], 1)
        a.append(2)
        self.assertEqual(a[1], 2)
        a.append(3)
        self.assertEqual(a[2], 3)

    def test_extend1(self):
        a = litelist([])
        a.extend([1,2,3])
        self.assertEqual(a[0], 1)
        self.assertEqual(a[1], 2)
        self.assertEqual(a[2], 3)

    def test_extend2(self):
        a = litelist([1,2,3])
        a.extend([4,5,6])
        self.assertEqual(a[3], 4)
        self.assertEqual(a[4], 5)
        self.assertEqual(a[5], 6)
        
    def test_repr(self):
        a = litelist([])
        self.assertEqual(repr(a), "litelist([])")
        a = litelist([1])
        self.assertEqual(repr(a), "litelist([1])")
        a = litelist([1, 2])
        self.assertEqual(repr(a), "litelist([1, 2])")

    def test_iter(self):
        a = litelist([1,2,3])
        self.assertEqual(list(a), [1,2,3])
        self.assertEqual(tuple(a), (1,2,3))

    def test_iter2(self):
        from recordclass._litelist import litelistiter
        a = litelist([1,2,3])
        self.assertTrue(isinstance(iter(a), litelistiter))        

    def test_getslice1(self):
        a = litelist([1,2,3])
        self.assertEqual(len(a[1:1]), 0)
        self.assertEqual(repr(a[1:1]), "litelist([])")
        self.assertEqual(len(a[1:2]), 1)
        self.assertEqual(repr(a[1:2]), "litelist([2])")
        self.assertEqual(len(a[:-1]), 2)
        self.assertEqual(repr(a[:-1]), "litelist([1, 2])")

    def test_getslice2(self):
        a = litelist([1,2,3])
        self.assertEqual(repr(a[:]), "litelist([1, 2, 3])")

    def test_setslice1(self):
        a = litelist([1,2,3])
        a[1:1] = []
        self.assertEqual(repr(a), "litelist([1, 2, 3])")

    def test_setslice2(self):
        a = litelist([1,2,3])
        a[1:2] = [100]
        self.assertEqual(repr(a), "litelist([1, 100, 3])")

    def test_setslice3(self):
        a = litelist([1,2,3])
        a[:-1] = [100,200]
        self.assertEqual(repr(a), "litelist([100, 200, 3])")

    def test_setslice4(self):
        a = litelist([1,2,3])
        a[:] = [100,200,300]
        self.assertEqual(repr(a), "litelist([100, 200, 300])")
        
    def test_delitem1(self):
        a = litelist([1,2,3,4,5])
        del a[1]
        self.assertEqual(repr(a), "litelist([1, 3, 4, 5])")

    def test_delitem2(self):
        a = litelist([1,2,3,4,5])
        del a[0]
        self.assertEqual(repr(a), "litelist([2, 3, 4, 5])")

    def test_delitem3(self):
        a = litelist([1,2,3,4,5])
        del a[4]
        self.assertEqual(repr(a), "litelist([1, 2, 3, 4])")

    def test_delitem4(self):
        a = litelist([1,2,3,4,5])
        del a[-1]
        self.assertEqual(repr(a), "litelist([1, 2, 3, 4])")
        
    def test_iterator_pickle(self):
        # Userlist iterators don't support pickling yet since
        # they are based on generators.
        data = litelist([4, 5, 6, 7])
        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
            itorg = iter(data)
            d = pickle.dumps(itorg, proto)
            it = pickle.loads(d)
            self.assertEqual(type(itorg), type(it))
            self.assertEqual(list(litelist(it)), list(data))

        it = pickle.loads(d)
        next(it)
        d = pickle.dumps(it)
        self.assertEqual(list(litelist(it)), list(data[1:]))

    def test_refleak_on_assignemnt(self):
        a = (1,2,3)
        c = sys.getrefcount(a)
        ll = litelist([a,2,3])
        self.assertEqual(sys.getrefcount(a), c+1)
        b = ll[0]
        self.assertEqual(sys.getrefcount(a), c+2)
        ll[0] = None        
        self.assertEqual(sys.getrefcount(a), c+1)
        
    def test_litelist_fromargs1(self):
        a = litelist_fromargs()
        self.assertEqual(len(a), 0)
        self.assertEqual(repr(a), "litelist([])")

    def test_litelist_fromargs2(self):
        a = litelist_fromargs(1,2,3,4,5)
        self.assertEqual(len(a), 5)
        self.assertEqual(repr(a), "litelist([1, 2, 3, 4, 5])")
        
    def test_incremental_add(self):
        ll = litelist([])
        for i in range(10000):
            ll.append(i)
        
        
def main():
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(litelistTest))
    return suite


lib/recordclass/test/test_litelist.py
intellimath/recordclass
"""Unit tests for recordclass.py."""
import unittest, doctest, operator
from recordclass import recordclass
from recordclass.recordclass import RecordclassStorage
from collections import OrderedDict
import pickle, copy
import keyword
import re
import sys

# try:
#     from test import support
# except:
#     from test import test_support as support

if 'PyPy' in sys.version:
    is_pypy = True
else:
    is_pypy = False
    from recordclass.utils import headgc_size, ref_size, pyobject_size, pyvarobject_size, pyssize


TestNT = recordclass('TestNT', 'x y z')    # type used for pickle tests

class RecordClassTest(unittest.TestCase):

    def test_factory(self):
        Point = recordclass('Point', 'x y')
        self.assertEqual(Point.__name__, 'Point')
        self.assertEqual(Point.__doc__.split('\n')[0], 'Point(x, y)')
        self.assertEqual(Point.__module__, __name__)
        self.assertEqual(Point.__fields__, ('x', 'y'))

        self.assertRaises(ValueError, recordclass, 'abc%', 'efg ghi')       # type has non-alpha char
        self.assertRaises(ValueError, recordclass, 'class', 'efg ghi')      # type has keyword
        self.assertRaises(ValueError, recordclass, '9abc', 'efg ghi')       # type starts with digit

        self.assertRaises(ValueError, recordclass, 'abc', 'efg g%hi')       # field with non-alpha char
        self.assertRaises(ValueError, recordclass, 'abc', 'abc class')      # field has keyword
        self.assertRaises(ValueError, recordclass, 'abc', '8efg 9ghi')      # field starts with digit
#         self.assertRaises(ValueError, recordclass, 'abc', '_efg ghi')       # field with leading underscore
        self.assertRaises(ValueError, recordclass, 'abc', 'efg efg ghi')    # duplicate field

        recordclass('Point0', 'x1 y2')   # Verify that numbers are allowed in names
        recordclass('_', 'a b c')        # Test leading underscores in a typename

        nt = recordclass('nt', 'the quick brown fox')                       # check unicode input
        self.assertNotIn("u'", repr(nt.__fields__))
        nt = recordclass('nt', ('the', 'quick'))                           # check unicode input
        self.assertNotIn("u'", repr(nt.__fields__))

        # self.assertRaises(TypeError, Point._make, [11])                     # catch too few args
        self.assertRaises(TypeError, Point._make, [11, 22, 33])             # catch too many args

    @unittest.skipIf(sys.flags.optimize >= 2,
                     "Docstrings are omitted with -O2 and above")
    def test_factory_doc_attr(self):
        Point = recordclass('Point', 'x y')
        self.assertEqual(Point.__doc__.split('\n')[0], 'Point(x, y)')

#     def test_name_fixer(self):
#         for spec, renamed in [
#             [('efg', 'g%hi'),  ('efg', '_1')],                              # field with non-alpha char
#             [('abc', 'class'), ('abc', '_1')],                              # field has keyword
#             [('8efg', '9ghi'), ('_0', '_1')],                               # field starts with digit
#             [('abc', '_efg'), ('abc', '_1')],                               # field with leading underscore
#             [('abc', 'efg', 'efg', 'ghi'), ('abc', 'efg', '_2', 'ghi')],    # duplicate field
#             [('abc', '', 'x'), ('abc', '_1', 'x')],                         # fieldname is a space
#         ]:
#             self.assertEqual(recordclass('NT', spec, rename=True).__fields__, renamed)

            
    def test_defaults(self):
        Point = recordclass('Point', 'x y', defaults=(10, 20))              # 2 defaults
        self.assertEqual(Point(1, 2), Point(1, 2))
        self.assertEqual(Point(1), Point(1, 20))
        self.assertEqual(Point(), Point(10, 20))

        Point = recordclass('Point', 'x y', defaults=(20,))                 # 1 default
        self.assertEqual(Point(1, 2), Point(1, 2))
        self.assertEqual(Point(1), Point(1, 20))

        Point = recordclass('Point', 'x y', defaults={})                     # 0 defaults
        self.assertEqual(Point(1, 2), Point(1, 2))
        # with self.assertRaises(TypeError):
        #     Point(1)
            
        # with self.assertRaises(TypeError):                                  # catch too few args
        #     Point()
        with self.assertRaises(TypeError):                                  # catch too many args
            Point(1, 2, 3)
        with self.assertRaises(TypeError):                                  # too many defaults
            Point = recordclass('Point', 'x y', defaults=(10, 20, 30))
        with self.assertRaises(TypeError):                                  # non-iterable defaults
            Point = recordclass('Point', 'x y', defaults=10)
        with self.assertRaises(TypeError):                                  # another non-iterable default
            Point = recordclass('Point', 'x y', defaults=False)

        Point = recordclass('Point', 'x y', defaults=None)                   # default is None
        # self.assertIsNone(Point.__new__.__defaults__, None)
        self.assertEqual(Point(10, 20), Point(10, 20))
        # with self.assertRaises(TypeError):                                  # catch too few args
        #     Point(10)

        Point = recordclass('Point', 'x y', defaults=[10, 20])               # allow non-tuple iterable
        # self.assertEqual(Point.__new__.__defaults__, (10, 20))
        self.assertEqual(Point(1, 2), Point(1, 2))
        self.assertEqual(Point(1), Point(1, 20))
        self.assertEqual(Point(), Point(10, 20))
            
    def test_instance(self):
        Point = recordclass('Point', 'x y')
        p = Point(11, 22)
        self.assertEqual(p, Point(x=11, y=22))
        self.assertEqual(p, Point(11, y=22))
        self.assertEqual(p, Point(y=22, x=11))
        self.assertEqual(p, Point(*(11, 22)))
        self.assertEqual(p, Point(**dict(x=11, y=22)))
        self.assertRaises(TypeError, eval, 'Point(XXX=1, y=2)', locals())   # wrong keyword argument
        # self.assertRaises(TypeError, eval, 'Point(x=1)', locals())          # missing keyword argument
        self.assertEqual(repr(p), 'Point(x=11, y=22)')
        #self.assertNotIn('__weakref__', dir(p))
        self.assertEqual(p, Point._make([11, 22]))                          # test _make classmethod
        self.assertEqual(p.__fields__, ('x', 'y'))                             # test __fields__ attribute
        self.assertEqual(p._replace(x=1), Point(1, 22))                          # test _replace method
        self.assertRaises(AttributeError, eval, 'p._replace(z=1)', locals())          # inval keyword argument
        self.assertEqual(p._asdict(), dict(x=1, y=22))                     # test _asdict method
#         self.assertEqual(vars(p), p._asdict())                              # verify that vars() works

        p.x = 1
        self.assertEqual(p.x, 1)

        # verify that field string can have commas
        Point = recordclass('Point', 'x, y')
        p = Point(x=11, y=22)
        self.assertEqual(repr(p), 'Point(x=11, y=22)')

        # verify that fieldspec can be a non-string sequence
        Point = recordclass('Point', ('x', 'y'))
        p = Point(x=11, y=22)
        self.assertEqual(repr(p), 'Point(x=11, y=22)')
        
    def test_readonly_instance(self):
        Point = recordclass('Point', ('x', 'y'), readonly=True)
        p = Point(11, 22)
        self.assertRaises(AttributeError, eval, 'p._replace(z=1)', locals())          # inval keyword argument
        
    def test_gc(self):
        Point = recordclass('Point', 'x y')
        Point_gc = recordclass('Point_gc', 'x y', gc=True)
        a = Point(1,2)
        b = Point_gc(1,2)
        self.assertEqual(tuple(a), tuple(b))

    def test_tupleness(self):
        Point = recordclass('Point', 'x y')
        p = Point(11, 22)

        self.assertEqual(tuple(p), (11, 22))                                # coercable to a real tuple
        self.assertEqual(list(p), [11, 22])                                 # coercable to a list
        self.assertEqual(max(p), 22)                                        # iterable
        self.assertEqual(max(*p), 22)                                       # star-able
        x, y = p
        self.assertEqual(tuple(p), (x, y))                                         # unpacks like a tuple
        self.assertEqual((p[0], p[1]), (11, 22))                            # indexable like a tuple
#         self.assertRaises(IndexError, p.__getitem__, 3)

        self.assertEqual(p.x, x)
        self.assertEqual(p.y, y)
        self.assertRaises(AttributeError, eval, 'p.z', locals())

    def test_odd_sizes(self):
        Zero = recordclass('Zero', '')
        self.assertEqual(Zero(), Zero())
        self.assertEqual(Zero._make([]), Zero())
        self.assertEqual(repr(Zero()), 'Zero()')
        self.assertEqual(Zero()._asdict(), {})
        self.assertEqual(Zero().__fields__, ())

        Dot = recordclass('Dot', 'd')
        self.assertEqual(Dot(1), Dot(1,))
        self.assertEqual(Dot._make([1]), Dot(1,))
        self.assertEqual(Dot(1).d, 1)
        self.assertEqual(repr(Dot(1)), 'Dot(d=1)')
        self.assertEqual(Dot(1)._asdict(), {'d':1})
        self.assertEqual(Dot(1)._replace(d=999), Dot(999,))
        self.assertEqual(Dot(1).__fields__, ('d',))

        # n = 5000
        n = 254 # SyntaxError: more than 255 arguments:
        import string, random
        names = list(set(''.join([random.choice(string.ascii_letters)
                                  for j in range(10)]) for i in range(n)))
        n = len(names)
        Big = recordclass('Big', names)
        b = Big(*range(n))
        self.assertEqual(b, Big(*tuple(range(n))))
        self.assertEqual(Big._make(range(n)), Big(*tuple(range(n))))
        for pos, name in enumerate(names):
            self.assertEqual(getattr(b, name), pos)
        repr(b)                                 # make sure repr() doesn't blow-up
        d = b._asdict()
        d_expected = dict(zip(names, range(n)))
        self.assertEqual(d, d_expected)
        b2 = b._replace(**dict([(names[1], 999),(names[-5], 42)]))
        b2_expected = list(range(n))
        b2_expected[1] = 999
        b2_expected[-5] = 42
        self.assertEqual(b2, Big(*tuple(b2_expected)))
        self.assertEqual(b.__fields__, tuple(names))
        
    # def test_annotations(self):
    #     C = recordclass('C', [('x',int),('y',int)])
    #     self.assertEqual(C.__new__.__annotations__, {'x':int, 'y':int})
    #     D = recordclass('D', [('x',int),'y'])
    #     self.assertEqual(D.__new__.__annotations__, {'x':int})

    def test_pickle(self):
        p = TestNT(x=10, y=20, z=30)
        for module in (pickle,):
            loads = getattr(module, 'loads')
            dumps = getattr(module, 'dumps')
            for protocol in range(-1, module.HIGHEST_PROTOCOL + 1):
                tmp = dumps(p, protocol)
                q = loads(tmp)
                self.assertEqual(p, q)
                self.assertEqual(p.__fields__, q.__fields__)
                self.assertNotIn(b'OrderedDict', dumps(p, protocol))

    def test_copy(self):
        p = TestNT(x=10, y=20, z=30)
        for copier in copy.copy, copy.deepcopy:
            q = copier(p)
            self.assertEqual(p, q)
            self.assertEqual(p.__fields__, q.__fields__)

#     def test_name_conflicts(self):
#         # Some names like "self", "cls", "tuple", "itemgetter", and "property"
#         # failed when used as field names.  Test to make sure these now work.
#         T = recordclass('T', 'itemgetter property self cls tuple')
#         t = T(1, 2, 3, 4, 5)
#         self.assertEqual(t, T(1,2,3,4,5))
#         newt = t._replace(itemgetter=10, property=20, self=30, cls=40, tuple=50)
#         self.assertEqual(newt, T(10,20,30,40,50))

#         # Broader test of all interesting names in a template
#         # with support.captured_stdout() as template:
#         #     T = recordclass('T', 'x')
#         # words = set(re.findall('[A-Za-z]+', template.getvalue()))
#         # words -= set(keyword.kwlist)
#         # words = list(words)
#         # if 'None' in words:
#         #     words.remove('None')
#         T = recordclass('T', words)
#         # test __new__
#         values = tuple(range(len(words)))
#         t = T(*values)
#         self.assertEqual(t, T(*values))
#         t = T(**dict(zip(T.__fields__, values)))
#         self.assertEqual(t, T(*values))
#         # test _make
#         t = T._make(values)
#         self.assertEqual(t, T(*values))
#         # exercise __repr__
#         repr(t)
#         # test _asdict
#         self.assertEqual(t._asdict(), dict(zip(T.__fields__, values)))
#         # test _replace
#         t = T._make(values)
#         newvalues = tuple(v*10 for v in values)
#         newt = t._replace(**dict(zip(T.__fields__, newvalues)))
#         self.assertEqual(newt, T(*newvalues))
#         # test __fields__
#         self.assertEqual(T.__fields__, tuple(words))
#         # test __getnewargs__
#         #self.assertEqual(t.__getnewargs__(), newvalues)

    def test_repr(self):
        A = recordclass('A', 'x')
        self.assertEqual(repr(A(1)), 'A(x=1)')
        # repr should show the name of the subclass
        class B(A):
            pass
        self.assertEqual(repr(B(1)), 'B(x=1)')
        
    def test_hash(self):
        A = recordclass('A', 'x y', readonly=True)
        # print(A.__options__)
        a = A(1, 2)
        self.assertNotEqual(hash(a), id(a))
        B = recordclass('B', 'x y', hashable=True)
        # print(B.__options__)
        b = B(1, 2)
        hash_b = hash(b)
        #self.assertEqual(hash_b, hash(tuple(b)))
        b.x = -1
        self.assertNotEqual(hash(b), hash_b)
        self.assertNotEqual(hash(b), id(b))

    def test_hash2(self):
        A = recordclass('A', 'x y', hashable=True)
#         print(dir(A))
        a = A(1,2)
        hash(a)

    def test_mapping(self):
        A = recordclass("A", "x y", mapping=True)
        a=A(1,2)
        a['x'] = 100
        a['y'] = 200
        self.assertEqual(a.x, 100)
        self.assertEqual(a.y, 200)

    def test_mapping2(self):
        A = recordclass("A", "x y")
        a=A(1,2)
        with self.assertRaises(TypeError):
            a['x'] = 100
            a['y'] = 200

    def test_hash_subcls(self):
        A = recordclass('A', 'x y', hashable=True)
        class B(A): pass
#         print(dir(B))
        b = B(1,2)
        hash(b)

    def test_hash_subcls2(self):
        A = recordclass('A', 'x y', hashable=True)
        class B(A):
            def __hash__(self):
                return 0
#         print(dir(B))
        b = B(1,2)
        hash(b)
        
    def test_caching(self):
        rs = RecordclassStorage()
        A = rs.recordclass('A', ('x', 'y'))
        B = rs.recordclass('A', ['x', 'y'])
        self.assertEqual(A, B)
        

def main():
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(RecordClassTest))
    return suite

lib/recordclass/test/test_recordclass.py
intellimath/recordclass
import unittest
import sqlite3 as sql
from recordclass import dataobject, make_dataclass
from recordclass.tools.sqlite import dataclass_row_factory

class DataobjectSqliteTest(unittest.TestCase):

    def test_row_factory_1(self):
        class Planet(dataobject):
            name:str
            radius:int

        con = sql.connect(":memory:")
        cur = con.execute("SELECT 'Earth' AS name, 6378 AS radius")
        cur.row_factory = dataclass_row_factory(Planet)
        row = cur.fetchone()
        print(type(row), row)
        self.assertEqual(row.name, 'Earth')
        self.assertEqual(row.radius, 6378)
        self.assertEqual(type(row).__name__, 'Planet')
        
    def test_row_factory_2(self):
        class Planet(dataobject, mapping=True):
            name:str
            radius:int

        con = sql.connect(":memory:")
        cur = con.execute("SELECT 'Earth' AS name, 6378 AS radius")
        cur.row_factory = dataclass_row_factory(Planet)
        row = cur.fetchone()
        print(type(row), row)
        self.assertEqual(row['name'], 'Earth')
        self.assertEqual(row['radius'], 6378)
        self.assertEqual(type(row).__name__, 'Planet')
        
def main():
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(DataobjectSqliteTest))
    return suite
        
lib/recordclass/test/test_sqlite.py
intellimath/recordclass
import unittest
import pickle, copy
import keyword
import re
import sys
import gc
import weakref

from recordclass import make_dataclass, datatype, as_dataclass
from recordclass import dataobject, datatype
from recordclass import asdict, make

from typing import ClassVar

# _PY36 = _sys.version_info[:2] >= (3, 6)
_PY37 = sys.version_info[:2] >= (3, 7)
_PY311 = sys.version_info[:2] >= (3, 11)

if 'PyPy' in sys.version:
    is_pypy = True
else:
    is_pypy = False
    from recordclass.utils import headgc_size, ref_size, pyobject_size, pyvarobject_size, pyssize

    _t = ()
    _t1 = (1,)
    _o = object()
    headgc_size = sys.getsizeof(_t) - _t.__sizeof__()
    ptr_size = sys.getsizeof(_t1) - sys.getsizeof(_t)
    pyobject_size = _o.__sizeof__()
    pyvarobject_size = _t.__sizeof__()
    del _t, _t1, _o

class TestPickle2(dataobject):
    __fields__ = 'x', 'y', 'z'

class TestPickle3(dataobject):
    __fields__  = 'x', 'y', 'z', '__dict__'

class TestPickle22(dataobject):
    x:int
    y:int
    z:int

class TestPickle33(dataobject):
    x:int
    y:int
    z:int
    __dict__:dict
    
class extended_dataobject(dataobject):
    def __init__(self, *args, **kwargs): 
        super().__init__(*args, **kwargs)

class Param(extended_dataobject):
    x:dict = {'a':1, 'b':2}
    y:tuple = (1,2)                

class TestPickle44(dataobject):
    x:int
    y:int

    def __init__(self, x, y):
        # print("__init__")
        self.x = 2*x
        self.y = 3*y
    #
    def __reduce__(self):
        tp, args = dataobject.__reduce__(self)
        return make, (tp, args)

class DataObjectTest3(unittest.TestCase):

    def test_dataobject_lists(self):
        class A(dataobject):
            x:object
            y:object

        # print(A.__fields__)
        a = A([1,2,3],[3,4,5])
        # print(a)

    def test_datatype_tp(self):
        class A(dataobject):
            __fields__ = 'x', 'y'
            x:int
            y:int

        a = A(1,2)
        self.assertEqual(repr(a), "A(x=1, y=2)")
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)
        self.assertEqual(asdict(a), {'x':1, 'y':2})
        self.assertEqual(A.__annotations__, {'x':int, 'y':int})
        # self.assertEqual(sys.getsizeof(a), pyobject_size+2*ptr_size)
        if not is_pypy:
            with self.assertRaises(TypeError):     
                weakref.ref(a)
        with self.assertRaises(AttributeError):     
            a.__dict__
        with self.assertRaises(AttributeError):     
            a.z = 3
        with self.assertRaises(AttributeError):     
            a.z
        a = None

    def test_datatype2_tp(self):
        class A(dataobject):
            x:int
            y:int

        a = A(1,2)
        self.assertEqual(repr(a), "A(x=1, y=2)")
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)
        self.assertEqual(asdict(a), {'x':1, 'y':2})
        self.assertEqual(A.__annotations__, {'x':int, 'y':int})
        self.assertEqual(A.__fields__, ('x', 'y'))
        # self.assertEqual(sys.getsizeof(a), pyobject_size+2*ptr_size)
        if not is_pypy:
            with self.assertRaises(TypeError):     
                weakref.ref(a)
        with self.assertRaises(AttributeError):     
            a.__dict__
        with self.assertRaises(AttributeError):     
            a.z = 3
        with self.assertRaises(AttributeError):     
            a.z
        a = None

    def test_datatype2_tp2(self):
        @as_dataclass()
        class A:
            x:int
            y:int

        a = A(1,2)
        self.assertEqual(repr(a), "A(x=1, y=2)")
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)
        self.assertEqual(asdict(a), {'x':1, 'y':2})
        self.assertEqual(A.__annotations__, {'x':int, 'y':int})
        self.assertEqual(A.__fields__, ('x', 'y'))
        if not is_pypy:
            self.assertEqual(sys.getsizeof(a), pyobject_size+2*ptr_size)
        if not is_pypy:
            with self.assertRaises(TypeError):     
                weakref.ref(a)
        # print('*')
        with self.assertRaises(TypeError):     
            a.__dict__
        with self.assertRaises(AttributeError):     
            a.z = 3
        with self.assertRaises(AttributeError):     
            a.z
        a = None

    def test_datatype3_tp(self):
        class A(dataobject):
            x:int
            y:int

            def dummy(self):
                pass

        self.assertEqual(A.__fields__, ('x','y'))

    def test_datatype4_tp(self):
        class A(dataobject, sequence=True):
            x:int
            y:int

        a = A(1,2)
        self.assertEqual(repr(a), "A(x=1, y=2)")
        self.assertEqual(a[0], 1)
        self.assertEqual(a[1], 2)

    def test_datatype5_tp(self):
        class A(dataobject, mapping=True):
            x:int
            y:int

        a = A(1,2)
        self.assertEqual(repr(a), "A(x=1, y=2)")
        self.assertEqual(a['x'], 1)
        self.assertEqual(a['y'], 2)

    def test_datatype6_tp(self):
        class A(dataobject, sequence=True, mapping=True):
            x:int
            y:int

        a = A(1,2)
        if not is_pypy:
            self.assertEqual(A.__weakrefoffset__, 0)
            self.assertEqual(A.__dictoffset__, 0)
        self.assertEqual(repr(a), "A(x=1, y=2)")
        self.assertEqual(a[0], 1)
        self.assertEqual(a[1], 2)
        self.assertEqual(a['x'], 1)
        self.assertEqual(a['y'], 2)

    def test_datatype7_tp(self):
        class A(dataobject):
            __fields__ = 'x', 'y'

        a = A(1,2)
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)
#         self.assertEqual(sys.getsizeof(a), 32)
        if not is_pypy:
            with self.assertRaises(TypeError):     
                weakref.ref(a)
        with self.assertRaises(AttributeError):     
            a.__dict__
        with self.assertRaises(AttributeError):     
            a.z = 3
        with self.assertRaises(AttributeError):     
            a.z
        a = None

    def test_datatype_dict_tp(self):
        class A(dataobject):
            __fields__ = 'x', 'y', '__dict__', '__weakref__'
            x:int
            y:int

        a = A(1,2)
        self.assertEqual(repr(a), "A(x=1, y=2)")
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)
        self.assertEqual(asdict(a), {'x':1, 'y':2})
        self.assertEqual(A.__annotations__, {'x':int, 'y':int})
        # self.assertEqual(sys.getsizeof(a), pyobject_size+(2+2)*ptr_size)
        if not is_pypy:
            self.assertNotEqual(A.__dictoffset__, 0)
            self.assertNotEqual(A.__weakrefoffset__, 0)
            weakref.ref(a)
        self.assertEqual(a.__dict__, {})

        a.z = 3
        # print(repr(a))
        self.assertEqual(a.z, a.__dict__['z'])
        self.assertEqual(repr(a), "A(x=1, y=2, **{'z': 3})")
        #a = None

    def test_subclass_tp(self):
        class A(dataobject):
            x:int
            y:int

        class B(A):
            pass

        self.assertEqual(type(A), type(B))
        self.assertEqual(B.__dictoffset__, 0)
        self.assertEqual(B.__weakrefoffset__, 0)
        b = B(1,2)
        self.assertEqual(repr(b), "B(x=1, y=2)")
        self.assertEqual(b.x, 1)
        self.assertEqual(b.y, 2)
        self.assertEqual(asdict(b), {'x':1, 'y':2})
        self.assertEqual(B.__annotations__, {'x':int, 'y':int})
#         self.assertEqual(sys.getsizeof(a), 32)
        self.assertEqual(A.__basicsize__, B.__basicsize__)
        if not is_pypy:
            with self.assertRaises(TypeError):     
                weakref.ref(b)
        with self.assertRaises(AttributeError):     
            b.__dict__        
        #a = None

    def test_subclass2_tp(self):
        class A(dataobject):
            x:int
            y:int

        class B(A):
            z:int

        class C(B):
            pass

        self.assertEqual(type(A), type(B))
        self.assertEqual(type(C), type(B))
        self.assertEqual(C.__dictoffset__, 0)
        self.assertEqual(C.__weakrefoffset__, 0)
        c = C(1,2,3)
        self.assertEqual(repr(c), "C(x=1, y=2, z=3)")
        self.assertEqual(c.x, 1)
        self.assertEqual(c.y, 2)
        self.assertEqual(c.z, 3)
        self.assertEqual(asdict(c), {'x':1, 'y':2, 'z':3})
        self.assertEqual(C.__annotations__, {'x':int, 'y':int, 'z':int})
        # self.assertEqual(sys.getsizeof(c), pyobject_size+3*ptr_size)
        with self.assertRaises(TypeError):     
            weakref.ref(c)
        with self.assertRaises(AttributeError):     
            c.__dict__

    def test_subclass3_tp(self):
        class A(dataobject):
            x:int
            y:int

        class B:
            def norm_1(self):
                return abs(self.x) + abs(self.y)

        class C(A, B):
            pass

        self.assertEqual(type(C), type(A))
        self.assertEqual(C.__dictoffset__, 0)
        self.assertEqual(C.__weakrefoffset__, 0)
        c = C(1,2)
        self.assertEqual(repr(c), "C(x=1, y=2)")
        self.assertEqual(c.x, 1)
        self.assertEqual(c.y, 2)
        self.assertEqual(c.norm_1(), 3)
        self.assertEqual(asdict(c), {'x':1, 'y':2})
        self.assertEqual(C.__annotations__, {'x':int, 'y':int})
        if not is_pypy:
            with self.assertRaises(TypeError):     
                weakref.ref(c)
        with self.assertRaises(AttributeError):     
            c.__dict__

    def test_subclass4_tp(self):
        class A(dataobject):
            x:int
            y:int

        class B(A):
            z:int

        class N:
            def norm_1(self):
                return abs(self.x) + abs(self.y) + abs(self.z)

        class C(B, N):
            pass

        self.assertEqual(type(A), type(B))
        self.assertEqual(type(C), type(B))
        self.assertEqual(C.__dictoffset__, 0)
        self.assertEqual(C.__weakrefoffset__, 0)
        c = C(1,2,3)
        self.assertEqual(repr(c), "C(x=1, y=2, z=3)")
        self.assertEqual(c.x, 1)
        self.assertEqual(c.y, 2)
        self.assertEqual(c.z, 3)
        self.assertEqual(c.norm_1(), 6)
        self.assertEqual(asdict(c), {'x':1, 'y':2, 'z':3})
        self.assertEqual(C.__annotations__, {'x':int, 'y':int, 'z':int})
#         self.assertEqual(sys.getsizeof(c), 40)
        if not is_pypy:
            with self.assertRaises(TypeError):     
                weakref.ref(c)
        with self.assertRaises(AttributeError):     
            c.__dict__

    def test_defaults_tp(self):
        class A(dataobject):
            x:int = 100
            y:int = 200
            z:int = 300

        # a1 = A()
        # self.assertEqual(repr(a1), "A(x=100, y=200, z=300)")
        # self.assertEqual(a1.x, 100)
        # self.assertEqual(a1.y, 200)
        # self.assertEqual(a1.z, 300)
        # a2 = A(1)
        # self.assertEqual(repr(a2), "A(x=1, y=200, z=300)")
        # self.assertEqual(a2.x, 1)
        # self.assertEqual(a2.y, 200)
        # self.assertEqual(a2.z, 300)
        # a3 = A(1,2)
        # self.assertEqual(repr(a3), "A(x=1, y=2, z=300)")
        # self.assertEqual(a3.x, 1)
        # self.assertEqual(a3.y, 2)
        # self.assertEqual(a3.z, 300)

    # def test_subclass_defaults_tp(self):
    #     class A(dataobject):
    #         x:int
    #         y:int

    #     class B(A):
    #         y:int=0

    #     self.assertEqual(A.__fields__, ('x', 'y'))
    #     self.assertEqual(A.__defaults__, (None,None))

    #     self.assertEqual(B.__fields__, ('x', 'y'))
    #     self.assertEqual(B.__defaults__, (None, 0))
    #     b = B(1)
    #     self.assertEqual(b.x, 1)
    #     self.assertEqual(b.y, 0)
    #     self.assertEqual(repr(b), "B(x=1, y=0)")        

    def test_subclass_defaults_2_tp(self):
        class A(dataobject):
            x:int=0
            y:int=1

        class B(A):
            z:int=2

        self.assertEqual(A.__fields__, ('x', 'y'))
        self.assertEqual(A.__default_vals__, (0,1))

        self.assertEqual(B.__fields__, ('x', 'y', 'z'))
        self.assertEqual(B.__default_vals__, (0, 1, 2))
        b = B()
        self.assertEqual(b.x, 0)
        self.assertEqual(b.y, 1)
        self.assertEqual(b.z, 2)
        self.assertEqual(repr(b), "B(x=0, y=1, z=2)")        
        b = B(1)
        self.assertEqual(b.x, 1)
        self.assertEqual(b.y, 1)
        self.assertEqual(b.z, 2)
        self.assertEqual(repr(b), "B(x=1, y=1, z=2)")        
        
    def test_keyword_args_tp(self):
        class A(dataobject):
            x:int
            y:int
            z:int

        class B(dataobject):
            x:int
            y:int
            z:int

        a = A(1,2,3)
        self.assertEqual(repr(a), "A(x=1, y=2, z=3)")
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)
        self.assertEqual(a.z, 3)
        b = B(1,2,3)
        self.assertEqual(repr(b), "B(x=1, y=2, z=3)")
        self.assertEqual(b.x, 1)
        self.assertEqual(b.y, 2)
        self.assertEqual(b.z, 3)
        c = B(1,2,3)
        self.assertEqual(repr(c), "B(x=1, y=2, z=3)")
        self.assertEqual(c.x, 1)
        self.assertEqual(c.y, 2)
        self.assertEqual(c.z, 3)

    def test_keyword_args2_tp(self):
        class A(dataobject):
            __fields__ = 'x', 'y', 'z'

        a1 = A(x=1, y=2, z=3)
        self.assertEqual(a1.x, 1)
        self.assertEqual(a1.y, 2)
        self.assertEqual(a1.z, 3)
        a3 = A(1,"a",3)
        self.assertEqual(a3.x, 1)
        self.assertEqual(a3.y, "a")
        self.assertEqual(a3.z, 3)            

    def test_keyword_args_defaults2_tp(self):
        class A(dataobject):
            __fields__ = ('x', 'y', 'z')
            x = 100
            y = 200
            z = 300

        a1 = A(x=1)
        self.assertEqual(a1.x, 1)
        self.assertEqual(a1.y, 200)
        self.assertEqual(a1.z, 300)
        a2 = A(x=1,y=2.0)
        self.assertEqual(a2.x, 1)
        self.assertEqual(a2.y, 2.0)
        self.assertEqual(a2.z, 300)
        a3 = A(x=1,y=2.0,z="a")
        self.assertEqual(a3.x, 1)
        self.assertEqual(a3.y, 2.0)
        self.assertEqual(a3.z, "a")

    def test_keyword_args_defaults_tp(self):
        class A(dataobject):
            x:int = 100
            y:int = 200
            z:int = 300

        a1 = A(x=1)
        self.assertEqual(repr(a1), "A(x=1, y=200, z=300)")
        self.assertEqual(a1.x, 1)
        self.assertEqual(a1.y, 200)
        self.assertEqual(a1.z, 300)
        a2 = A(x=1,y=2)
        self.assertEqual(repr(a2), "A(x=1, y=2, z=300)")
        self.assertEqual(a2.x, 1)
        self.assertEqual(a2.y, 2)
        self.assertEqual(a2.z, 300)
        a3 = A(x=1,y=2,z=3)
        self.assertEqual(repr(a3), "A(x=1, y=2, z=3)")
        self.assertEqual(a3.x, 1)
        self.assertEqual(a3.y, 2)
        self.assertEqual(a3.z, 3)

    def test_datatype_dict2_tp(self):
        class A(dataobject, use_dict=True, use_weakref=True):
            __fields__ = 'x', 'y'

        a = A(1,2)
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)
#         self.assertEqual(sys.getsizeof(a), 48)
#         self.assertEqual(A.__dictoffset__, 32)
#         self.assertEqual(A.__weakrefoffset__, 40)
        weakref.ref(a)
        self.assertEqual(a.__dict__, {})

        a.z = 3
        self.assertEqual(a.z, a.__dict__['z'])
        a = None        

    def test_defaults2_tp(self):
        class A(dataobject):
            __fields__ = ('x', 'y', 'z')
            x = 100
            y = 200
            z = 300

        a1 = A()
        self.assertEqual(a1.x, 100)
        self.assertEqual(a1.y, 200)
        self.assertEqual(a1.z, 300)
        a2 = A(1)
        self.assertEqual(a2.x, 1)
        self.assertEqual(a2.y, 200)
        self.assertEqual(a2.z, 300)
        a3 = A(1,2)
        self.assertEqual(a3.x, 1)
        self.assertEqual(a3.y, 2)
        self.assertEqual(a3.z, 300)

    def test_defaults3_tp(self):
        class A(dataobject):
            __fields__ = ('x', 'y', 'z')
            x = 100
            y = 200
            z = 300

    def test_iter2_tp(self):
        class A(dataobject):
            __fields__ = 3

        a=A(1, 2.0, "a")
        self.assertEqual(list(iter(a)), [1, 2.0, "a"])

    def test_iter3_tp(self):
        class A(dataobject, iterable=True):
            __fields__ = ('x', 'y', 'z')

        a=A(1, 2.0, "a")
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2.0)
        self.assertEqual(a.z, "a")        
        self.assertEqual(list(iter(a)), [1, 2.0, "a"])

    def test_iter3_tp2(self):
        class A(dataobject, iterable=True):
            __fields__ = ('x', 'y', 'z')

        a=A(1, 2.0, "a")
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2.0)
        self.assertEqual(a.z, "a")        
        self.assertEqual(list(iter(a)), [1, 2.0, "a"])

    def test_iter4_tp(self):
        class A(dataobject):
            __fields__ = 'x', 'y', 'z'

        a=A(1, 2.0, "a")
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2.0)
        self.assertEqual(a.z, "a")        
        with self.assertRaises(TypeError):
            iter(a)

    def test_enable_gc_tp(self):

        class A(dataobject):
            __fields__ = 'x', 'y', 'z'

#         @clsconfig(gc=True)
        class B(dataobject, gc=True):
            __fields__ = 'x', 'y', 'z'

        a = A(1,2,3)
        b = B(1,2,3)
        self.assertEqual(a.x, b.x)
        self.assertEqual(a.y, b.y)
        self.assertEqual(a.z, b.z)
        # self.assertEqual(sys.getsizeof(b)-sys.getsizeof(a), headgc_size)        

    def test_pickle2_tp(self):
        p = TestPickle2(10, 20, 30)
        for module in (pickle,):
            loads = getattr(module, 'loads')
            dumps = getattr(module, 'dumps')
            for protocol in range(-1, module.HIGHEST_PROTOCOL + 1):
                tmp = dumps(p, protocol)
                q = loads(tmp)
                self.assertEqual(p, q)

    def test_pickle3_tp(self):
        p = TestPickle3(10, 20, 30)
        p.a = 1
        p.b = 2
        for module in (pickle,):
            loads = getattr(module, 'loads')
            dumps = getattr(module, 'dumps')
            for protocol in range(-1, module.HIGHEST_PROTOCOL + 1):
                tmp = dumps(p, protocol)
                q = loads(tmp)
                self.assertEqual(p, q)

    def test_pickle22_tp(self):
        p = TestPickle22(10, 20, 30)
        for module in (pickle,):
            loads = getattr(module, 'loads')
            dumps = getattr(module, 'dumps')
            for protocol in range(-1, module.HIGHEST_PROTOCOL + 1):
                tmp = dumps(p, protocol)
                q = loads(tmp)
                self.assertEqual(p, q)

    def test_pickle33_tp(self):
        p = TestPickle33(10, 20, 30)
        p.a = 1
        p.b = 2
        for module in (pickle,):
            loads = getattr(module, 'loads')
            dumps = getattr(module, 'dumps')
            for protocol in range(-1, module.HIGHEST_PROTOCOL + 1):
                tmp = dumps(p, protocol)
                q = loads(tmp)
                self.assertEqual(p, q)

    def test_pickle_Param_tp(self):
        p = Param(10, 20)
        for module in (pickle,):
            loads = getattr(module, 'loads')
            dumps = getattr(module, 'dumps')
            for protocol in range(-1, module.HIGHEST_PROTOCOL + 1):
                tmp = dumps(p, protocol)
                q = loads(tmp)
                self.assertEqual(p, q)

    def test_pickle44_tp(self):
        p = TestPickle44(10, 20)
        for module in (pickle,):
            loads = getattr(module, 'loads')
            dumps = getattr(module, 'dumps')
            for protocol in range(-1, module.HIGHEST_PROTOCOL + 1):
                tmp = dumps(p, protocol)
                q = loads(tmp)
                self.assertEqual(p, q)
                
    def test_dill(self):
        print('*** DILL ***')
        try:
            import dill
        except:
            return

        import warnings

        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")

            p = Param(10, 20)
            s = dill.dumps(p)
            print(s)
            p1 = dill.loads(s)
            if w:
                print(w)
            self.assertEqual(p.x, p1.x)
            self.assertEqual(p.y, p1.y)

    def test_dill2(self):
        print('*** DILL ***')
        try:
            import dill
        except:
            return

        import warnings

        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")

            p = TestPickle44(10, 20)
            s = dill.dumps(p)
            print(s)
            p1 = dill.loads(s)
            if w:
                print(w)
            self.assertEqual(p.x, p1.x)
            self.assertEqual(p.y, p1.y)

    def test_invalid_defaults_tp(self):
        import copy

        with self.assertRaises(TypeError):        
            class A(dataobject):
                x:int=0
                y:int

    def test_copy_tp(self):
        import copy

        class A(dataobject):
            x:int
            y:int

        a=A(x=1,y=2)
        b = copy.copy(a)
        self.assertEqual(a, b)
        c = copy.deepcopy(a)
        self.assertEqual(a, c)
        
    def test_vcall_1(self):
        class A(dataobject):
            x:int
            y:int
            
        for i in range(1000):
            a = A(1,2)
            self.assertEqual(a.x, 1)
            self.assertEqual(a.y, 2)

    def test_vcall_2(self):
        class A(dataobject):
            x:int
            y:int
            
        for i in range(1000):
            a = A(1,y=2)
            self.assertEqual(a.x, 1)
            self.assertEqual(a.y, 2)
            
    def test_vcall_3(self):
        class A(dataobject):
            x:int
            y:int
            
        for i in range(1000):
            a = A(x=1,y=2)
            self.assertEqual(a.x, 1)
            self.assertEqual(a.y, 2)

    def test_signature_tp(self):
        class A(dataobject, fast_new=False):
            x:int
            y:int=2

        import inspect
        s = inspect.signature(A)
        px = s.parameters['x']
        self.assertEqual(px.name, 'x')
        self.assertEqual(px.annotation, int)
        self.assertEqual(px.default, px.empty)
        py = s.parameters['y']
        self.assertEqual(py.name, 'y')
        self.assertEqual(py.annotation, int)
        self.assertEqual(py.default, 2)

    def test_fast_new_tp(self):
        class A(dataobject, fast_new=True):
            __fields__ = 'x', 'y'

        self.assertTrue('__new__' not in A.__dict__)
        a = A(1,2)
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)
        a = A(1,y=2)
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)
        a = A(1,**{'y':2})
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)
        a = A(x=1,y=2)
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)
        a = A(**{'x':1, 'y':2})
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)

    def test_missing_args1_tp(self):
        class A(dataobject, fast_new=True):
            __fields__ = 'a','b','c'

        a=A(1)
        self.assertEqual(a.a, 1)
        self.assertEqual(a.b, None)
        self.assertEqual(a.c, None)

    def test_missing_args2_tp(self):
        class A(dataobject, fast_new=True):
            __fields__ = 'a','b','c'
            b = 2
            c = 3

        a=A(1)
        self.assertEqual(a.a, 1)
        self.assertEqual(a.b, 2)
        self.assertEqual(a.c, 3)

    if not is_pypy:
        def test_do_finalizer_tp(self):
            count = 0
            class A(dataobject):
                x:int
                y:int

                def __del__(self):
                    nonlocal count
                    count += 1

            for i in range(100):
                a = A(1,2)
                del a

            self.assertEqual(count, 100)

        def test_deep_dealloc(self):
            class LinkedItem(dataobject, fast_new=True):
                val: object
                next: 'LinkedItem'

            class LinkedList(dataobject, deep_dealloc=True):
                start: LinkedItem = None
                end: LinkedItem = None

                def append(self, val):
                    link = LinkedItem(val, None)
                    if self.start is None:
                        self.start = link
                    else:
                        self.end.next = link
                    self.end = link

            self.assertEqual(LinkedItem.__base__, dataobject)

            ll = LinkedList()
            for i in range(10):
                ll.append(i)

            del ll

    def test_readonly(self):
        class A(dataobject, readonly=True):
            x:int
            y:int

        a = A(1,2)
        with self.assertRaises(AttributeError):        
            a.x = -1
        with self.assertRaises(AttributeError):        
            a.y = -2

    def test_readonly_2(self):
        class A(dataobject, readonly=True, use_dict=True):
            x:int
            y:int

        a = A(1,2, z=3)
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)
        self.assertEqual(a.z, 3)

    def test_readonly_3(self):
        class A(dataobject, readonly=True):
            x:int
            y:int
            z:int

        a = A(1,2, z=3)
        self.assertEqual(a.x, 1)
        self.assertEqual(a.y, 2)
        self.assertEqual(a.z, 3)

    def test_default_arg_enum(self):
        from enum import Enum, auto

        class Color(Enum):
            RED = auto()

        class Point(dataobject):
            x: float
            y: float
            color: Color = Color.RED        

        pt = Point(1,2)
        self.assertEqual(pt.color, Color.RED)

    if _PY37:
        def test_classvar_1(self):
            from typing import ClassVar
            class Point(dataobject):
                color:ClassVar[int]
                x: float
                y: float

            self.assertTrue('color' not in Point.__dict__)
            self.assertEqual(Point.__fields__, ('x','y'))
            self.assertEqual(Point.__annotations__, {'x':float,'y':float})   
            pt = Point(1,2)
            self.assertEqual((pt.x, pt.y), (1, 2))

        def test_classvar_2(self):
            from typing import ClassVar
            class Point(dataobject):
                x: float
                y: float
                color:ClassVar[int] = 1

            self.assertEqual(Point.color, 1)
            self.assertEqual(Point.__fields__, ('x','y'))
            self.assertEqual(Point.__annotations__, {'x':float,'y':float})            
            pt = Point(1,2)
            self.assertEqual((pt.x, pt.y), (1, 2))
            
        def test_classvar_3(self):
            from typing import ClassVar
            class Example_State(dataobject):
                x: float=1.0
                y: float=2.0

            with self.assertRaises(TypeError):                        
                class Example_Derived_State(Example_State):
                    x:ClassVar[int] = 10

        def test_classvar_4(self):
            from typing import ClassVar
            class Example_State(dataobject):
                x: float=1.0
                y: float=2.0

            with self.assertRaises(TypeError):                        
                class Example_Derived_State(Example_State):
                    x:ClassVar[int]

    def test_initialize_in_init(self):
        class A(dataobject):
            x:int
            y:int

            def __init__(self, x, y):
                print("__init__")
                self.x = 2*x
                self.y = 3*y

        a = A(1,2)
        self.assertEqual(a.x, 2)
        self.assertEqual(a.y, 6)

    def test_initialize_in_init2(self):
        class A(dataobject):
            x:int
            y:int

            def __init__(self, *args, **kwds):
                print("__init__")
                self.x = 2*kwds['x']
                self.y = 3*kwds['y']

        a = A(x=1,y=2)
        self.assertEqual(a.x, 2)
        self.assertEqual(a.y, 6)

    def test_initialize_in_init3(self):
        class A(dataobject):
            x:int
            y:int

            def __init__(self, x, y=0):
                print("__init__")
                self.x = 2*x
                self.y = 3*y

        a = A(1,y=2)
        self.assertEqual(a.x, 2)
        self.assertEqual(a.y, 6)
        b = A(1)
        self.assertEqual(b.x, 2)
        self.assertEqual(b.y, 0)

    def test_initialize_in_init4(self):
        class A(dataobject):
            x:int
            y:int

            def __init__(self, x):
                print("__init__")
                self.x = 2*x
                self.y = self.x + 2

        b = A(1)
        self.assertEqual(b.x, 2)
        self.assertEqual(b.y, 4)

    def test_initialize_in_init5(self):
        class A0(dataobject):
            x:int
            y:int

            def __init__(self, x, y):
                self.x = x
                self.y = y

        class A(A0):
            def __init__(self, x, y):
                print("__init__")
                pass

        # print("A0", A0.__dict__)
        # print("A", A.__dict__)
        a0 = A0(1,2)
        # print(a0)
        a = A(1,2)
        # print(a)
        self.assertEqual(a.x, None)
        self.assertEqual(a.y, None)

#     def test_post_init_1(self):
#         class A(dataobject):
#             x:int
#             y:int

#             def __post_init__(self):
#                 print("__post_init__")
#                 self.x *= 2
#                 self.y *= 3

#         a = A(2,3)
#         self.assertEqual(a.x, 4)
#         self.assertEqual(a.y, 9)

#     def test_post_init_2(self):
#         class A(dataobject):
#             x:int
#             y:int

#             def __post_init__(self):
#                 print("__post_init__")
#                 raise AttributeError("")

#         with self.assertRaises(AttributeError):        
#             a = A(1,2)

#     def test_post_init_3(self):
#         class A(dataobject):
#             x:int
#             y:int

#             def __init__(self, x, y):
#                 print("__init__")
#                 self.x = x
#                 self.y = y
#             #
#             def __post_init__(self):
#                 print("__post_init__")
#                 self.x *= 2
#                 self.y *= 3

#         a = A(2,3)
#         self.assertEqual(a.x, 4)
#         self.assertEqual(a.y, 9)

#     def test_post_init_4(self):
#         class A(dataobject):
#             x:int
#             y:int

#             def __init__(self, x, y):
#                 print("__init__")
#                 self.x = x
#                 self.y = y
#             #
#             def __post_init__(self):
#                 print("__post_init__")
#                 raise AttributeError("")

#         with self.assertRaises(AttributeError):        
#             a = A(1,2)

    if _PY311:
        def test_immutable_type(self):
            class A(dataobject, immutable_type=True):
                x:int
                y:int

            with self.assertRaises(TypeError):                        
                A.z = 1

    def test_copy_default_1(self):
        class A(dataobject):
            l: list = []

        a = A()
        b = A()
        # print(id(a.l), id(b.l))
        self.assertEqual(a.l, b.l)
        self.assertEqual(id(a.l), id(b.l))

    def test_copy_default_2(self):
        class A(dataobject):
            d: dict = {}

        a = A()
        b = A()
        # print(id(a.d), id(b.d))
        self.assertEqual(a.d, b.d)
        self.assertEqual(id(a.d), id(b.d))

    def test_copy_default_3(self):
        class A(dataobject):
            d: set = set()

        a = A()
        b = A()
        # print(id(a.d), id(b.d))
        self.assertEqual(a.d, b.d)
        self.assertEqual(id(a.d), id(b.d))
    
    def test_copy_default_4(self):
        class A(dataobject, copy_default=True):
            l: list = []

        a = A()
        b = A()
        # print(id(a.l), id(b.l))
        self.assertEqual(a.l, b.l)
        self.assertNotEqual(id(a.l), id(b.l))

    
    def test_copy_default_5(self):
        class A(dataobject, copy_default=True):
            d: dict = {}

        a = A()
        b = A()
        # print(id(a.d), id(b.d))
        self.assertEqual(a.d, b.d)
        self.assertNotEqual(id(a.d), id(b.d))

    def test_copy_default_6(self):
        class A(dataobject, copy_default=True):
            d: set = set()

        a = A()
        b = A()
        # print(id(a.d), id(b.d))
        self.assertEqual(a.d, b.d)
        self.assertNotEqual(id(a.d), id(b.d))
    
    def test_copy_default_7(self):
        class B(dataobject):
            x:int
            y:int

        class A(dataobject, copy_default=True):
            d : B = B(0,0)

        a = A()
        b = A()
        # print(id(a.d), id(b.d))
        self.assertEqual(a.d, b.d)
        self.assertNotEqual(id(a.d), id(b.d))

    def test_copy_default_base_1(self):
        class Base(dataobject, copy_default=True):
            pass
        
        class A(Base):
            l : list = []

        a = A()
        b = A()
        # print(id(a.l), id(b.l))
        self.assertEqual(a.l, b.l)
        self.assertNotEqual(id(a.l), id(b.l))
    
    def test_copy_default_classvar(self):

        class A(dataobject, copy_default=True):
            x : list = []
            y : ClassVar[list] = [1] # attribute y in A.__dict__

        a = A()
        print(type(A.x), type(A.y))
        self.assertTrue(type(A.y) is list)
        self.assertTrue(a.y is A.y)

        b = A()
        self.assertTrue(b.y is A.y)
        self.assertEqual(a.x, b.x)
        self.assertNotEqual(id(a.x), id(b.x))
        self.assertEqual(a.y, b.y)
        self.assertEqual(id(a.y), id(b.y))

    def test_Factory_1(self):
        from recordclass import Factory
        class A(dataobject):
            x: tuple = Factory(lambda: (list(), dict()))

        a = A()
        b = A()
        self.assertEqual(a.x, ([],{}))
        self.assertNotEqual(id(a.x), id(b.x))
        self.assertNotEqual(id(a.x[0]), id(b.x[0]))
        self.assertNotEqual(id(a.x[1]), id(b.x[1]))

    def test_Factory_2(self):
        from recordclass import Factory
        class A(dataobject, copy_default=True):
            l: list = []
            x: tuple = Factory(lambda: (list(), dict()))

        a = A()
        b = A()
        self.assertEqual(a.x, ([],{}))
        self.assertNotEqual(id(a.x), id(b.x))
        self.assertEqual(a.l, [])
        self.assertNotEqual(id(a.l), id(b.l))


def main():
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(DataObjectTest3))
    return suite

lib/recordclass/test/typing/test_dataobject.py
intellimath/recordclass
"""Unit tests for recordclass.py."""
import unittest, doctest, operator
from recordclass.typing import RecordClass

import pickle
import typing
import sys as _sys

class CoolEmployee(RecordClass):
    name: str
    cool: int

class CoolEmployeeWithDefault(RecordClass):
    name: str
    cool: int = 0

class XMeth(RecordClass):
    x: int
    def double(self):
        return 2 * self.x

class XRepr(RecordClass):
    x: int
    y: int = 1
    def __str__(self):
        return f'{self.x} -> {self.y}'
    def __add__(self, other):
        return 0
    
class H(RecordClass, hashable=True):
    x: int
    y: int

class HR(RecordClass, readonly=True):
    x: int
    y: int
        
class RecordClassTypingTest(unittest.TestCase):
    
    def test_recordclass_lists(self):
        class A(RecordClass):
            x:object
            y:object
    
        a = A([1,2,3],[3,4,5])

    def test_typing(self):
        class A(RecordClass):
            a: int
            b: int
            c: object
        
        tmp = A(a=1, b=2, c=[1,2,3])
        # self.assertEqual(repr(tmp), "A(a=1, b=2', c=[1, 2, 3])")
        # self.assertEqual(tmp.__annotations__, {'a': int, 'b': int, 'c': object})

    def test_recordclass_basics(self):
        class Emp(RecordClass):
            name:str
            id:int
        joe = Emp('Joe', 42)
        jim = Emp(name='Jim', id=1)
        self.assertIsInstance(joe, Emp)
        self.assertEqual(joe.name, 'Joe')
        self.assertEqual(joe.id, 42)
        self.assertEqual(jim.name, 'Jim')
        self.assertEqual(jim.id, 1)
        self.assertEqual(Emp.__name__, 'Emp')
        self.assertEqual(Emp.__fields__, ('name', 'id'))
        self.assertEqual(Emp.__annotations__,
                         dict([('name', str), ('id', int)]))

    def test_annotation_usage(self):
        tim = CoolEmployee('Tim', 9000)
        self.assertIsInstance(tim, CoolEmployee)
        self.assertEqual(tim.name, 'Tim')
        self.assertEqual(tim.cool, 9000)
        self.assertEqual(CoolEmployee.__name__, 'CoolEmployee')
        self.assertEqual(CoolEmployee.__fields__, ('name', 'cool'))
        self.assertEqual(CoolEmployee.__annotations__,
                         dict(name=str, cool=int))

    def test_annotation_usage_with_default(self):
        jelle = CoolEmployeeWithDefault('Jelle')
        self.assertIsInstance(jelle, CoolEmployeeWithDefault)
        self.assertEqual(jelle.name, 'Jelle')
        self.assertEqual(jelle.cool, 0)
        cooler_employee = CoolEmployeeWithDefault('Sjoerd', 1)
        self.assertEqual(cooler_employee.cool, 1)

        self.assertEqual(CoolEmployeeWithDefault.__name__, 'CoolEmployeeWithDefault')
        self.assertEqual(CoolEmployeeWithDefault.__fields__, ('name', 'cool'))
        #self.assertEqual(CoolEmployeeWithDefault._field_types, dict(name=str, cool=int))

        with self.assertRaises(TypeError):
            exec("""
class NonDefaultAfterDefault(RecordClass):
    x: int = 3
    y: int
""")

    def test_annotation_usage_with_methods(self):
        self.assertEqual(XMeth(1).double(), 2)
        self.assertEqual(XMeth(42).x, XMeth(42)[0])
        self.assertEqual(str(XRepr(42)), '42 -> 1')
        self.assertEqual(XRepr(1, 2) + XRepr(3), 0)

        with self.assertRaises(TypeError):
            exec("""
class XMethBad(RecordClass):
    x: int
    def __fields__(self):
        return 'no chance for this'
""")

    def test_recordclass_keyword_usage(self):
        class LocalEmployee(RecordClass):
            name:str
            age:int

        nick = LocalEmployee('Nick', 25)
        self.assertEqual(nick.name, 'Nick')
        self.assertEqual(LocalEmployee.__name__, 'LocalEmployee')
        self.assertEqual(LocalEmployee.__fields__, ('name', 'age'))
        self.assertEqual(LocalEmployee.__annotations__, dict(name=str, age=int))
        #self.assertIs(LocalEmployee._field_types, LocalEmployee.__annotations__)
        with self.assertRaises(TypeError):
            RecordClass('Name', [('x', int)], y=str)
        with self.assertRaises(TypeError):
            RecordClass('Name', x=1, y='a')
            
    def test_hash(self):
        a = HR(1, 2)
        #self.assertEqual(hash(a), hash(tuple(a)))
        b = H(1, 2)
        hash_b = hash(b)
        #self.assertEqual(hash_b, hash(tuple(b)))
        b.x = -1
        self.assertNotEqual(hash(b), hash_b)

    def test_hash_subcls(self):
        # print(H.__options__)
        class B(H): pass
        # print(B.__options__)
        b = B(1,2)
        hash(b)

    def test_hash_subcls2(self):
        class B(H):
            def __hash__(self):
                return 0
        b = B(1,2)
        hash(b)

    def test_hash_subcls3(self):
        class B(HR):
            def __hash__(self):
                return 0
        b = B(1,2)
        hash(b)

    def test_hash_subcls4(self):
        class B(HR):
            pass
        b = B(1,2)
        with self.assertRaises(AttributeError):
            b.x = 1
        
    def test_pickle(self):
        global Emp 
        class Emp(RecordClass):
            name:str
            id:int

        jane = Emp('jane', 37)
        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
            z = pickle.dumps(jane, proto)
            jane2 = pickle.loads(z)
            self.assertEqual(jane2, jane)

    def test_pickle2(self):
        global Emp2
        class Emp2(RecordClass):
            name:str
            id:int
        
        jane = Emp2('jane', 37)
        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
            z = pickle.dumps(jane, proto)
            jane2 = pickle.loads(z)
            self.assertEqual(jane2, jane)

    def test_pickle3(self):
        jane = CoolEmployee('jane', 37)
        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
            z = pickle.dumps(jane, proto)
            jane2 = pickle.loads(z)
            self.assertEqual(jane2, jane)
            

def main():
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(RecordClassTypingTest))
    return suite

lib/recordclass/test/typing/test_recordclass.py
intellimath/recordclass
from collections import namedtuple
from recordclass import dataobject, make_dictclass
from recordclass import litetuple, mutabletuple
from timeit import timeit
import sys
import gc

_PY311 = sys.version_info[:2] >= (3, 11)

PointNT = namedtuple("PointNT", "x y z")
nan = float('nan')

class PyPoint:

    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def norm2(self):
        return self.x*self.x + self.y*self.y + self.z*self.z

class PointSlots:
    __slots__ = 'x', 'y', 'z'
    
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def norm2(self):
        return self.x*self.x + self.y*self.y + self.z*self.z

if _PY311:
    class Point(dataobject, sequence=True, immutable_type=True):
        x:int
        y:int
        z:int
    
        def norm2(self):
            return self.x*self.x + self.y*self.y + self.z*self.z
    
    class PointGC(dataobject, sequence=True, gc=True, immutable_type=True):
        x:int
        y:int
        z:int
    
        def norm2(self):
            return self.x*self.x + self.y*self.y + self.z*self.z
else:
    class Point(dataobject, sequence=True):
        x:int
        y:int
        z:int
    
        def norm2(self):
            return self.x*self.x + self.y*self.y + self.z*self.z
    
    class PointGC(dataobject, sequence=True, gc=True):
        x:int
        y:int
        z:int
    
        def norm2(self):
            return self.x*self.x + self.y*self.y + self.z*self.z

class PointMap(dataobject, mapping=True, fast_new=True):
    x:int
    y:int
    z:int

    def norm2(self):
        return self.x*self.x + self.y*self.y + self.z*self.z

    
# PointDict = make_dictclass("PointDict", "x y z", fast_new=True)
    
results = {'id':[], 'size':[], 'new':[], 
           'getattr':[], 'setattr':[], 
           'getitem':[], 'setitem':[],
           'getkey':[], 'setkey':[],
           'iterate':[], 'copy':[],
          }

results['id'].extend(
    ['litetuple', 'mutabletuple', 'tuple', 'namedtuple', 'class+slots', 'dataobject',  
     'dataobject+gc', 'dict', 'dataobject+map', 'class'])

classes = (litetuple, mutabletuple, tuple, PointNT, PointSlots, 
           Point, PointGC, dict, PointMap, PyPoint)

N = 300_000

numbers = 10

def test_new():
    print("new")
    def test(cls):
        for i in range(N):
            ob = cls(i, i, i)
            prev = ob
        
    def test_tuple():
        for i in range(N):
            ob = tuple((i, i, i))
            prev = ob

    def test_dict():
        for i in range(N):
            ob = {'x':i, 'y':i, 'z':i}
            prev = ob
    
    for cls in classes:
        gc.collect()
        if cls is dict:
            res = timeit("test_dict()", number=numbers, globals={'test':test, 'test_dict':test_dict})
        elif cls is tuple:
            res = timeit("test_tuple()", number=numbers, globals={'test':test, 'test_tuple':test_tuple})
        else:
            res = timeit("test(cls)", number=numbers, globals={'cls':cls, 'test':test})
        # res = "%.2f" % res
        results['new'].append(res)

def test_copy():
    print("copy")
    def test(cls):
        ob = cls(0,0,0)
        for i in range(N):
            ob1 = ob.__copy__()

    def test2(cls):
        ob = cls(0,0,0)
        for i in range(N):
            ob1 = ob[:]
            
    def test_tuple():
        ob = (0,0,0)
        for i in range(N):
            ob1 = ob[:]
            
    def test_dict():
        ob = {'x':0,'y':0,'z':0}
        for i in range(N):
            ob1 = ob.copy()
    
    for cls in classes:
        gc.collect()
        if cls is dict:
            res = timeit("test_dict()", number=numbers, globals={'test':test, 'test_dict':test_dict})
        elif cls is tuple:
            res = timeit("test_tuple()", number=numbers, globals={'test':test, 'test_tuple':test_tuple})
        elif cls is PointNT:
            res = timeit("test2(cls)", number=numbers, globals={'cls':cls, 'test2':test2})
        elif cls in (PointSlots, PyPoint):
            res = nan
        else:
            res = timeit("test(cls)", number=numbers, globals={'cls':cls, 'test':test})
        results['copy'].append(res)
        
def test_getattr():
    print("getattr")
    def test(cls):
        p = cls(0,0,0)
        for i in range(N):
            x = p.x
            y = p.y
            z = p.z
            
    for cls in classes:
        gc.collect()
        if cls in (litetuple,mutabletuple,tuple,dict):
            res = nan
        else:
            res = timeit("test(cls)", number=numbers, globals={'cls':cls, 'test':test})
        results['getattr'].append(res)

def test_getkey():
    print("getkey")

    def test_dict():
        p = {'x':0, 'y':0, 'z':0}
        for i in range(N):
            x = p['x']
            y = p['y']
            z = p['z']
            
    def test_dictclass(cls):
        p = cls(0,0,0)
        for i in range(N):
            x = p['x']
            y = p['y']
            z = p['z']
            
    for cls in classes:
        gc.collect()
        if cls is dict:
            res = timeit("test_dict()", number=numbers, globals={'test_dict':test_dict})
        elif cls in (PointMap,):
            res = timeit("test_dictclass(cls)", number=numbers, globals={'cls':cls, 'test_dictclass':test_dictclass})
        else:
            res = nan
        results['getkey'].append(res)
        
def test_getitem():
    print("getitem")
    def test(cls):
        p = cls(0,0,0)
        for i in range(N):
            x = p[0]
            y = p[1]
            z = p[2]

    def test_tuple():
        p = (0,0,0)
        for i in range(N):
            x = p[0]
            y = p[1]
            z = p[2]
            
    for cls in classes:
        gc.collect()
        if cls in (dict, PointSlots, PointMap, PyPoint):
            res = nan
        elif cls is tuple:
            res = timeit("test_tuple()", number=numbers, globals={'test':test, 'test_tuple':test_tuple})
        else:
            res = timeit("test(cls)", number=numbers, globals={'cls':cls, 'test':test})
        results['getitem'].append(res)
        
def test_setattr():
    print("setattr")
    def test(cls):
        p = cls(0,0,0)
        for i in range(N):
            p.x = 1
            p.y = 2
            p.z = 3

    for cls in classes:
        gc.collect()
        if cls in (litetuple, mutabletuple, tuple, PointNT, dict):
            res = nan
        else:
            res = timeit("test(cls)", number=numbers, globals={'cls':cls, 'test':test, 'tuple':tuple, 'PointNT':PointNT})
        results['setattr'].append(res)

def test_setkey():
    print("setkey")
    def test_dict():
        p = {'x':0, 'y':0, 'z':0}
        for i in range(N):
            p['x'] = 1
            p['y'] = 2
            p['z'] = 3

    def test_dictclass(cls):
        p = cls(0,0,0)
        for i in range(N):
            p['x'] = 1
            p['y'] = 2
            p['z'] = 3
            
    for cls in classes:
        gc.collect()
        if cls in (PointMap,):
            res = timeit("test_dictclass(cls)", number=numbers, globals={'cls':cls, 'test_dictclass':test_dictclass})
        elif cls is dict:
            res = timeit("test_dict()", number=numbers, globals={'test_dict':test_dict})
        else:
            res = nan
        results['setkey'].append(res)
        
def test_setitem():
    print("setitem")
    def test(cls):
        p = cls(0,0,0)
        for i in range(N):
            p[0] = 1
            p[1] = 2
            p[2] = 2
            
    for cls in classes:
        gc.collect()
        if cls in (litetuple, dict, tuple, PointNT, PointSlots, PointMap, PyPoint):
            res = nan
        else:
            res = timeit("test(cls)", number=numbers, globals={'cls':cls, 'test':test})
        results['setitem'].append(res)

def test_getmethod():
    print("getmethod")
    def test(cls):
        p = cls(0,0,0)
        for i in range(N):
            a = p.norm2
            
    for cls in classes:
        gc.collect()
        if cls in (litetuple, mutabletuple, dict, tuple, PointNT,):
            res = nan
        else:
            res = timeit("test(cls)", number=numbers, globals={'cls':cls, 'test':test})
        results['getmethod'].append(res)
        
def test_iterate():
    print("iterate")
    def test(cls):
        p = cls(0,0,0)
        for i in range(N):
            for x in iter(p):
                s = x

    def test_tuple():
        p = (0,0,0)
        for i in range(N):
            for x in iter(p):
                s = x
    
    def test_dict():
        p = {'x':0, 'y':0, 'z':0}
        for i in range(N):
            for x in iter(p):
                s = x
    
    for cls in classes:
        gc.collect()
        if cls is dict:
            res = timeit("test_dict()", number=numbers, globals={'test':test, 'test_dict':test_dict})
        elif cls is tuple:
            res = timeit("test_tuple()", number=numbers, globals={'test':test, 'test_tuple':test_tuple})
        elif cls in (PointSlots, PyPoint):
            res = nan
        else:
            res = timeit("test(cls)", number=numbers, globals={'cls':cls, 'test':test})
        results['iterate'].append(res)

def test_all(relative=True):
    from math import isnan
    from prettytable import PrettyTable

    test_new()
    test_getattr()
    test_setattr()
    test_getitem()
    test_setitem()
    test_getkey()
    test_setkey()
    # test_getmethod()
    test_iterate()
    test_copy()

    results['size'].extend([
      sys.getsizeof(litetuple(0,0,0)),   
      sys.getsizeof(mutabletuple(0,0,0)),   
      sys.getsizeof((0,0,0)),   
      sys.getsizeof(PointNT(0,0,0)),   
      sys.getsizeof(PointSlots(0,0,0)),   
      sys.getsizeof(Point(0,0,0)),   
      sys.getsizeof(PointGC(0,0,0)),   
      sys.getsizeof({'x':0,'y':0, 'z':0}),   
      sys.getsizeof(PointMap(0,0,0)),   
      sys.getsizeof(PyPoint(0,0,0)),   
      # sys.getsizeof(PointDict(0,0,0)),   
    ])

    for key in results.keys():
        if key in ('id', 'size'):
            continue
        results[key] = [(round(x,2) if not isnan(x) else ' ')  for x in results[key]]

    pt = PrettyTable()
    pt.field_names = list(results.keys())
    for k in range(len(results['id'])):
        pt.add_row([results[key][k] for key in pt.field_names])

    pt.align = 'l'
    
    return pt

pt = test_all()
print(pt)
# print(pt.get_html_string())


lib/recordclass/tools/perfcounts.py
intellimath/recordclass
from recordclass import make_dataclass

def make_row_factory(cls_factory, **kw):
    def row_factory(cursor, row, cls=[None]):
        rf = cls[0]
        if rf is None:
            fields = [col[0] for col in cursor.description]
            cls[0] = cls_factory("Row", fields, **kw)
            print(cls[0])
            return cls[0](*row)
        return rf(*row)
    return row_factory

def dataclass_row_factory(cls=None):
    if cls is None:
        return make_row_factory(make_dataclass, fast_new=True)
    else:
        def row_factory(cursor, row):
            return cls(*row)
        return row_factory

lib/recordclass/tools/sqlite.py
intellimath/recordclass
# coding: utf-8
 
# The MIT License (MIT)

# Copyright (c) «2015-2023» «Shibzukhov Zaur, szport at gmail dot com»

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software - recordclass library - and associated documentation files 
# (the "Software"), to deal in the Software without restriction, including 
# without limitation the rights to use, copy, modify, merge, publish, distribute, 
# sublicense, and/or sell copies of the Software, and to permit persons to whom 
# the Software is furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import sys as _sys
_PY36 = _sys.version_info[:2] >= (3, 6)

from keyword import iskeyword
from recordclass import dataobject
from .datatype import Field

_intern = _sys.intern
if _PY36:
    from typing import _type_check
else:
    def _type_check(t, msg):
        if isinstance(t, (type, str)):
            return t
        else:
            raise TypeError('invalid type annotation', t)    

### sizes

if 'PyPy' in _sys.version:
    is_pypy = True
else:
    is_pypy = False

    _t = ()
    _t1 = (1,)
    _o = object()
    headgc_size = _sys.getsizeof(_t) - _t.__sizeof__()
    ref_size = _sys.getsizeof(_t1) - _sys.getsizeof(_t)
    pyobject_size = _o.__sizeof__()
    pyvarobject_size = _t.__sizeof__()
    pyssize = pyvarobject_size - pyobject_size
    del _t, _t1, _o
del _sys

#############

# def get_option(options, name, default=False):
#     if name in options:
#         val = options[name]
#         if not val:
#             del options[name]
#     else:
#         val = default
#         if val:
#             options[name] = val
#     return val

def process_fields(fields, defaults, rename, invalid_names):
    annotations = {}
    msg = "in iterable (f0, t0), (f1, t1), ... each t must be a type"
    if isinstance(fields, str):
        fields = fields.replace(',', ' ').split()
        fields = [fn.strip() for fn in fields]

    field_names = []
    if isinstance(fields, dict):
        for i, fn in enumerate(fields):
            tp = fields[fn]
            tp = _type_check(tp, msg)
            check_name(fn, i, rename, invalid_names)
            fn = _intern(fn)
            annotations[fn] = tp
            field_names.append(fn)
    else:
        for i, fn in enumerate(fields):
            if type(fn) is tuple:
                fn, tp = fn
                tp = _type_check(tp, msg)
                annotations[fn] = tp
            check_name(fn, i, rename, invalid_names)
            fn = _intern(fn)
            field_names.append(fn)
    fields = field_names
        
    seen = set()
    for fn in fields:
        if fn in seen:
            raise ValueError('duplicate name ' + fn)
        seen.add(fn)

    if defaults is None:
        defaults = {}
    n_defaults = len(defaults)
    n_fields = len(fields)
    if n_defaults > n_fields:
        raise TypeError('Got more default values than fields')

    if isinstance(defaults, (tuple,list)) and n_defaults > 0:
        defaults = {fields[i]:defaults[i] for i in range(-n_defaults,0)}
    return fields, annotations, defaults

def check_name(name, rename=False, i=0, invalid_names=()):
    if not isinstance(name, str):
        raise TypeError('Type names and field names must be strings')

    if name.startswith('__') and name.endswith('__'):
        return name

    if rename:
        if not name.isidentifier() or iskeyword(name) or (name in invalid_names):
            name = "_%s" % (i+1)
    else:
        if name in invalid_names:
            raise ValueError('Name %s is invalid' % name)
        if not name.isidentifier():
            raise ValueError('Name must be valid identifiers: %r' % name)
        if iskeyword(name):
            raise ValueError('Name cannot be a keyword: %r' % name)
    
    return name

def number_of_dataitems(cls):
    fields = cls.__fields__
    if type(fields) is int:
        return fields
    else:
        return len(fields)

def collect_info_from_bases(bases, fields, fields_dict, options):
    from ._dataobject import _is_readonly_member
    _fields = []
    use_dict = options.get('use_dict', False)
    use_weakref = options.get('use_weakref', False)
    copy_default = options.get('copy_default', False)
    gc = options.get('gc', False)
    iterable = options.get('iterable', False)
    # readonly = True
    # hashable = None
    for base in bases:
        if base is dataobject:
            continue
        elif issubclass(base, dataobject):
            use_dict = use_dict or base.__options__.get('use_dict', False)
            if use_dict:
                options['use_dict'] = True
            use_weakref = use_weakref or base.__options__.get('use_weakref', False)
            if use_weakref:
                options['use_weakref'] = True
            copy_default = copy_default or base.__options__.get('copy_default', False)
            if copy_default:
                options['copy_default'] = True
            gc = gc or base.__options__.get('gc', False)
            if gc:
                options['gc'] = True
            iterable = iterable or base.__options__.get('iterable', False)
            if iterable:
                options['iterable'] = True
        else:
            continue

        fs = getattr(base, '__fields__', ())
        base_defaults = getattr(base, '__defaults__', {})
        n_defaults = len(base_defaults)
        base_annotations = getattr(base, '__annotations__', {})
        base_options = getattr(base, '__options__', {})
        base_fields_dict = base_options['fields_dict']
        n = number_of_dataitems(base)
        if type(fs) is tuple and len(fs) == n:
            for i, fn in enumerate(fs):
                if fn in fields:
                    raise TypeError('field %s is already defined in the %s' % (fn, base))
                else:
                    fields_dict[fn] = f = Field()
                    if _is_readonly_member(base.__dict__[fn]):
                        f['readonly'] = True
                    if fn in base_defaults:
                        f['default'] = base_defaults[fn]
                    if fn in base_annotations:
                        f['type'] = base_annotations[fn]
                    _fields.append(fn)
        else:
            raise TypeError("invalid fields in base class %r" % base)

    # for f in fields_dict.values():
    #     if not f.get('readonly', False):
    #         readonly = False
    #         break
        
    return _fields + fields

def _have_pyinit(bases):
    for base in bases:
        if base is object or base is dataobject:
            continue
        if '__init__' in base.__dict__:
            return True
        elif _have_pyinit(base.__bases__):
            return True
        
    return False

def _have_pynew(bases):
    for base in bases:
        if base is object or base is dataobject:
            continue
        if '__new__' in base.__dict__:
            return True
        elif _have_pynew(base.__bases__):
            return True
        
    return False

lib/recordclass/utils.py
intellimath/recordclass
# coding: utf-8

# The MIT License (MIT)
#
# Copyright (c) <2015-2023> <Shibzukhov Zaur, szport at gmail dot com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

from setuptools import setup, find_packages
from setuptools.command.build_ext import build_ext
from setuptools.extension import Extension
import sys

_PY310 = sys.version_info[:2] >= (3, 10)

# extra_compile_args = ["-O3", "-Wfatal-errors"]
# extra_compile_args = ["-Wfatal-errors"]
extra_compile_args = []
extra_link_args = []

use_cython = 0

if use_cython:
    ext_modules = [
        Extension(
            "recordclass._dataobject",
            ["lib/recordclass/_dataobject.c"],
            # extra_compile_args = extra_compile_args,
            # extra_link_args = extra_link_args,
        ),
        Extension(
            "recordclass._litetuple",
            ["lib/recordclass/_litetuple.c"],
            # extra_compile_args = extra_compile_args,
            # extra_link_args = extra_link_args,
        ),
        Extension(
            "recordclass._litelist",
            ["lib/recordclass/_litelist.c"],
            # extra_compile_args = extra_compile_args,
            # extra_link_args = extra_link_args,
        ),
        Extension(
            "recordclass._linkedlist",
            ["lib/recordclass/_linkedlist.pyx"],
            # extra_compile_args = extra_compile_args,
            # extra_link_args = extra_link_args,
        ),
    ]
else:
    ext_modules = [
        Extension(
            "recordclass._dataobject",
            ["lib/recordclass/_dataobject.c"],
            # extra_compile_args = extra_compile_args,
            # extra_link_args = extra_link_args,
        ),
        Extension(
            "recordclass._litetuple",
            ["lib/recordclass/_litetuple.c"],
            # extra_compile_args = extra_compile_args,
            # extra_link_args = extra_link_args,
        ),
        Extension(
            "recordclass._litelist",
            ["lib/recordclass/_litelist.c"],
            # extra_compile_args = extra_compile_args,
            # extra_link_args = extra_link_args,
        ),
        Extension(
            "recordclass._linkedlist",
            ["lib/recordclass/_linkedlist.c"],
            # extra_compile_args = extra_compile_args,
            # extra_link_args = extra_link_args,
        ),
    ]

description = """Mutable variant of namedtuple -- recordclass, which support assignments, compact dataclasses and other memory saving variants."""

with open('README.md', encoding='utf-8') as f:
    long_description = f.read()

packages = [ 'recordclass',
             'recordclass.test',
             'recordclass.test.typing',
             'recordclass.typing',
             'recordclass.tools',
           ] + (['recordclass.test.match'] if _PY310 else [])

setup(
    name = 'recordclass',
    version = '0.21',
    description = description,
    author = 'Zaur Shibzukhov',
    author_email = 'szport@gmail.com',
    maintainer = 'Zaur Shibzukhov',
    maintainer_email = 'szport@gmail.com',
    license = "MIT License",
    cmdclass = {'build_ext': build_ext},
    ext_modules = ext_modules,
    package_dir = {'': 'lib'},
    packages = packages,
    url = 'https://github.com/intellimath/recordclass',
    download_url = 'https://pypi.org/project/recordclass/#files',
    long_description=long_description,
    long_description_content_type='text/markdown',
#     description_content_type='text/plain',
    platforms='Linux, Mac OS X, Windows',
    keywords=['namedtuple', 'recordclass', 'dataclass', 'dataobject'],
    classifiers=[
        'Development Status :: 4 - Beta',
        'Intended Audience :: Developers',
        'Intended Audience :: Information Technology',
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.6',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'Programming Language :: Python :: 3.9',
        'Programming Language :: Python :: 3.10',
        'Programming Language :: Python :: 3.11',
        'Programming Language :: Python :: 3.12',
        'Operating System :: OS Independent',
        'Topic :: Software Development :: Libraries :: Python Modules',
    ],
)

setup.py
intellimath/recordclass