Move singleton to common directory (#10935)

### What problem does this PR solve?

As title

### Type of change

- [x] Refactoring

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2025-11-02 12:24:08 +08:00
committed by GitHub
parent fe4852cb71
commit 6447b737ab
14 changed files with 117 additions and 35 deletions

View File

@ -36,18 +36,7 @@ from api.utils.json_encode import json_dumps, json_loads
from api.utils.configs import deserialize_b64, serialize_b64 from api.utils.configs import deserialize_b64, serialize_b64
from common.time_utils import current_timestamp, timestamp_to_date, date_string_to_timestamp from common.time_utils import current_timestamp, timestamp_to_date, date_string_to_timestamp
from common.decorator import singleton
def singleton(cls, *args, **kw):
instances = {}
def _singleton():
key = str(cls) + str(os.getpid())
if key not in instances:
instances[key] = cls(*args, **kw)
return instances[key]
return _singleton
CONTINUOUS_FIELD_TYPE = {IntegerField, FloatField, DateTimeField} CONTINUOUS_FIELD_TYPE = {IntegerField, FloatField, DateTimeField}

27
common/decorator.py Normal file
View File

@ -0,0 +1,27 @@
#
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import os
def singleton(cls, *args, **kw):
instances = {}
def _singleton():
key = str(cls) + str(os.getpid())
if key not in instances:
instances[key] = cls(*args, **kw)
return instances[key]
return _singleton

View File

@ -15,23 +15,10 @@
# #
import os import os
import tiktoken import tiktoken
from api.utils.file_utils import get_project_base_directory from api.utils.file_utils import get_project_base_directory
def singleton(cls, *args, **kw):
instances = {}
def _singleton():
key = str(cls) + str(os.getpid())
if key not in instances:
instances[key] = cls(*args, **kw)
return instances[key]
return _singleton
tiktoken_cache_dir = get_project_base_directory() tiktoken_cache_dir = get_project_base_directory()
os.environ["TIKTOKEN_CACHE_DIR"] = tiktoken_cache_dir os.environ["TIKTOKEN_CACHE_DIR"] = tiktoken_cache_dir
# encoder = tiktoken.encoding_for_model("gpt-3.5-turbo") # encoder = tiktoken.encoding_for_model("gpt-3.5-turbo")

View File

@ -19,7 +19,7 @@ import os
import time import time
from io import BytesIO from io import BytesIO
from rag import settings from rag import settings
from rag.utils import singleton from common.decorator import singleton
from azure.storage.blob import ContainerClient from azure.storage.blob import ContainerClient

View File

@ -18,7 +18,7 @@ import logging
import os import os
import time import time
from rag import settings from rag import settings
from rag.utils import singleton from common.decorator import singleton
from azure.identity import ClientSecretCredential, AzureAuthorityHosts from azure.identity import ClientSecretCredential, AzureAuthorityHosts
from azure.storage.filedatalake import FileSystemClient from azure.storage.filedatalake import FileSystemClient

View File

@ -26,7 +26,7 @@ from elasticsearch_dsl import UpdateByQuery, Q, Search, Index
from elastic_transport import ConnectionTimeout from elastic_transport import ConnectionTimeout
from rag import settings from rag import settings
from rag.settings import TAG_FLD, PAGERANK_FLD from rag.settings import TAG_FLD, PAGERANK_FLD
from rag.utils import singleton from common.decorator import singleton
from api.utils.file_utils import get_project_base_directory from api.utils.file_utils import get_project_base_directory
from api.utils.common import convert_bytes from api.utils.common import convert_bytes
from rag.utils.doc_store_conn import DocStoreConnection, MatchExpr, OrderByExpr, MatchTextExpr, MatchDenseExpr, \ from rag.utils.doc_store_conn import DocStoreConnection, MatchExpr, OrderByExpr, MatchTextExpr, MatchDenseExpr, \

View File

@ -27,7 +27,7 @@ from infinity.connection_pool import ConnectionPool
from infinity.errors import ErrorCode from infinity.errors import ErrorCode
from rag import settings from rag import settings
from rag.settings import PAGERANK_FLD, TAG_FLD from rag.settings import PAGERANK_FLD, TAG_FLD
from rag.utils import singleton from common.decorator import singleton
import pandas as pd import pandas as pd
from api.utils.file_utils import get_project_base_directory from api.utils.file_utils import get_project_base_directory
from rag.nlp import is_english from rag.nlp import is_english

View File

@ -21,7 +21,7 @@ from minio.commonconfig import CopySource
from minio.error import S3Error from minio.error import S3Error
from io import BytesIO from io import BytesIO
from rag import settings from rag import settings
from rag.utils import singleton from common.decorator import singleton
@singleton @singleton

View File

@ -4,7 +4,7 @@ import pymysql
from urllib.parse import quote_plus from urllib.parse import quote_plus
from api.utils.configs import get_base_config from api.utils.configs import get_base_config
from rag.utils import singleton from common.decorator import singleton
CREATE_TABLE_SQL = """ CREATE_TABLE_SQL = """

View File

@ -26,7 +26,7 @@ from opensearchpy import UpdateByQuery, Q, Search, Index
from opensearchpy import ConnectionTimeout from opensearchpy import ConnectionTimeout
from rag import settings from rag import settings
from rag.settings import TAG_FLD, PAGERANK_FLD from rag.settings import TAG_FLD, PAGERANK_FLD
from rag.utils import singleton from common.decorator import singleton
from api.utils.file_utils import get_project_base_directory from api.utils.file_utils import get_project_base_directory
from rag.utils.doc_store_conn import DocStoreConnection, MatchExpr, OrderByExpr, MatchTextExpr, MatchDenseExpr, \ from rag.utils.doc_store_conn import DocStoreConnection, MatchExpr, OrderByExpr, MatchTextExpr, MatchDenseExpr, \
FusionExpr FusionExpr

View File

@ -19,7 +19,7 @@ from botocore.exceptions import ClientError
from botocore.config import Config from botocore.config import Config
import time import time
from io import BytesIO from io import BytesIO
from rag.utils import singleton from common.decorator import singleton
from rag import settings from rag import settings

View File

@ -20,7 +20,7 @@ import uuid
import valkey as redis import valkey as redis
from rag import settings from rag import settings
from rag.utils import singleton from common.decorator import singleton
from valkey.lock import Lock from valkey.lock import Lock
import trio import trio

View File

@ -20,7 +20,7 @@ from botocore.exceptions import ClientError
from botocore.config import Config from botocore.config import Config
import time import time
from io import BytesIO from io import BytesIO
from rag.utils import singleton from common.decorator import singleton
from rag import settings from rag import settings
@singleton @singleton

View File

@ -0,0 +1,79 @@
#
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from common.decorator import singleton
# Test class for demonstration
@singleton
class TestClass:
def __init__(self):
self.counter = 0
def increment(self):
self.counter += 1
return self.counter
# Test cases
class TestSingleton:
def test_state_persistence(self):
"""Test that instance state persists across multiple calls"""
instance1 = TestClass()
instance1.increment()
instance1.increment()
instance2 = TestClass()
assert instance2.counter == 2 # State should persist
def test_multiple_calls_consistency(self):
"""Test consistency across multiple calls"""
instances = [TestClass() for _ in range(5)]
# All references should point to the same object
first_instance = instances[0]
for instance in instances:
assert instance is first_instance
def test_instance_methods_work(self):
"""Test that instance methods work correctly"""
instance = TestClass()
# Test method calls
result1 = instance.increment()
result2 = instance.increment()
assert result1 == 3
assert result2 == 4
assert instance.counter == 4
# Test decorator itself
def test_singleton_decorator_returns_callable():
"""Test that the decorator returns a callable"""
class PlainClass:
pass
decorated_class = singleton(PlainClass)
# Should return a function
assert callable(decorated_class)
# Calling should return an instance of PlainClass
instance = decorated_class()
assert isinstance(instance, PlainClass)