diff --git a/classes.py b/classes.py index bf0345a..d7242df 100644 --- a/classes.py +++ b/classes.py @@ -12,7 +12,6 @@ import time import socket import ssl import hashlib -from copy import copy, deepcopy import inspect import ipaddress import queue @@ -480,6 +479,8 @@ class PyLinkNetworkCore(utils.DeprecatedAttributesObject, utils.CamelCaseToSnake except AttributeError: return False +structures._BLACKLISTED_COPY_TYPES.append(PyLinkNetworkCore) + class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore): def __init__(self, *args, **kwargs): @@ -1567,7 +1568,7 @@ class Server(): IrcServer = Server -class Channel(utils.DeprecatedAttributesObject, utils.CamelCaseToSnakeCase): +class Channel(utils.DeprecatedAttributesObject, utils.CamelCaseToSnakeCase, structures.CopyWrapper): """PyLink IRC channel class.""" def __init__(self, irc, name=None): @@ -1599,22 +1600,6 @@ class Channel(utils.DeprecatedAttributesObject, utils.CamelCaseToSnakeCase): self.users.discard(target) removeuser = remove_user - def __deepcopy__(self, memo): - """Returns a deep copy of the channel object.""" - # XXX: we can't pickle IRCNetwork, so just return a reference of it. - channel_copy = copy(self) - # For everything else, create a copy. - for attr, val in self.__dict__.items(): - if not isinstance(val, PyLinkNetworkCore): - setattr(channel_copy, attr, deepcopy(val)) - - memo[id(self)] = channel_copy - - return channel_copy - - def deepcopy(self): - return deepcopy(self) - def is_voice(self, uid): """Returns whether the given user is voice in the channel.""" return uid in self.prefixmodes['voice'] diff --git a/structures.py b/structures.py index 46aba5f..f5314cf 100644 --- a/structures.py +++ b/structures.py @@ -10,10 +10,13 @@ import json import pickle import os import threading +from copy import copy, deepcopy from .log import log from . import conf +_BLACKLISTED_COPY_TYPES = [] + class KeyedDefaultdict(collections.defaultdict): """ Subclass of defaultdict allowing the key to be passed to the default factory. @@ -26,7 +29,34 @@ class KeyedDefaultdict(collections.defaultdict): value = self[key] = self.default_factory(key) return value -class CaseInsensitiveFixedSet(collections.abc.Set): +class CopyWrapper(): + """ + Base container class implementing copy methods. + """ + + def copy(self): + """Returns a shallow copy of this object instance.""" + return copy(self) + + def __deepcopy__(self, memo): + """Returns a deep copy of the channel object.""" + newobj = copy(self) + log.debug('CopyWrapper: _BLACKLISTED_COPY_TYPES = %s', _BLACKLISTED_COPY_TYPES) + for attr, val in self.__dict__.items(): + # We can't pickle IRCNetwork, so just return a reference of it. + if not isinstance(val, tuple(_BLACKLISTED_COPY_TYPES)): + log.debug('CopyWrapper: copying attr %r', attr) + setattr(newobj, attr, deepcopy(val)) + + memo[id(self)] = newobj + + return newobj + + def deepcopy(self): + """Returns a deep copy of this object instance.""" + return deepcopy(self) + +class CaseInsensitiveFixedSet(collections.abc.Set, CopyWrapper): """ Implements a fixed set storing items case-insensitively. """ @@ -55,8 +85,8 @@ class CaseInsensitiveFixedSet(collections.abc.Set): def __contains__(self, key): return self._data.__contains__(self._keymangle(key)) - def copy(self, *args, **kwargs): - return self._data.copy(*args, **kwargs) + def __copy__(self): + return self.__class__(data=self._data.copy()) class CaseInsensitiveDict(collections.abc.MutableMapping, CaseInsensitiveFixedSet): """ @@ -92,6 +122,9 @@ class IRCCaseInsensitiveDict(CaseInsensitiveDict): """Converts the given key to lowercase.""" return self._irc.to_lower(key) + def __copy__(self): + return self.__class__(self._irc, data=self._data.copy()) + class CaseInsensitiveSet(collections.abc.MutableSet, CaseInsensitiveFixedSet): """ A mutable set storing items case insensitively. @@ -115,6 +148,9 @@ class IRCCaseInsensitiveSet(CaseInsensitiveSet): """Converts the given key to lowercase.""" return self._irc.to_lower(key) + def __copy__(self): + return self.__class__(self._irc, data=self._data.copy()) + class DataStore: """ Generic database class. Plugins should use a subclass of this such as JSONDataStore or