graphrag-common 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- graphrag_common/__init__.py +4 -0
- graphrag_common/config/__init__.py +8 -0
- graphrag_common/config/load_config.py +205 -0
- graphrag_common/factory/__init__.py +8 -0
- graphrag_common/factory/factory.py +113 -0
- graphrag_common/hasher/__init__.py +18 -0
- graphrag_common/hasher/hasher.py +59 -0
- graphrag_common/py.typed +0 -0
- graphrag_common-3.0.0.dist-info/METADATA +143 -0
- graphrag_common-3.0.0.dist-info/RECORD +12 -0
- graphrag_common-3.0.0.dist-info/WHEEL +4 -0
- graphrag_common-3.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Copyright (c) 2024 Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License
|
|
3
|
+
|
|
4
|
+
"""Load configuration."""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from string import Template
|
|
11
|
+
from typing import Any, TypeVar
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
from dotenv import load_dotenv
|
|
15
|
+
|
|
16
|
+
T = TypeVar("T", covariant=True)
|
|
17
|
+
|
|
18
|
+
_default_config_files = ["settings.yaml", "settings.yml", "settings.json"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ConfigParsingError(ValueError):
|
|
22
|
+
"""Configuration Parsing Error."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, msg: str) -> None:
|
|
25
|
+
"""Initialize the ConfigParsingError."""
|
|
26
|
+
super().__init__(msg)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_config_file_path(config_dir_or_file: Path) -> Path:
|
|
30
|
+
"""Resolve the config path from the given directory or file."""
|
|
31
|
+
config_dir_or_file = Path(config_dir_or_file)
|
|
32
|
+
|
|
33
|
+
if config_dir_or_file.is_file():
|
|
34
|
+
return config_dir_or_file
|
|
35
|
+
|
|
36
|
+
if not config_dir_or_file.is_dir():
|
|
37
|
+
msg = f"Invalid config path: {config_dir_or_file} is not a directory"
|
|
38
|
+
raise FileNotFoundError(msg)
|
|
39
|
+
|
|
40
|
+
for file in _default_config_files:
|
|
41
|
+
if (config_dir_or_file / file).is_file():
|
|
42
|
+
return config_dir_or_file / file
|
|
43
|
+
|
|
44
|
+
msg = f"No 'settings.[yaml|yml|json]' config file found in directory: {config_dir_or_file}"
|
|
45
|
+
raise FileNotFoundError(msg)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _load_dotenv(env_file_path: Path, required: bool) -> None:
|
|
49
|
+
"""Load the .env file if it exists."""
|
|
50
|
+
if not env_file_path.is_file():
|
|
51
|
+
if not required:
|
|
52
|
+
return
|
|
53
|
+
msg = f"dot_env_path not found: {env_file_path}"
|
|
54
|
+
raise FileNotFoundError(msg)
|
|
55
|
+
load_dotenv(env_file_path)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _parse_json(data: str) -> dict[str, Any]:
|
|
59
|
+
"""Parse JSON data."""
|
|
60
|
+
return json.loads(data)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _parse_yaml(data: str) -> dict[str, Any]:
|
|
64
|
+
"""Parse YAML data."""
|
|
65
|
+
return yaml.safe_load(data)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _get_parser_for_file(file_path: str | Path) -> Callable[[str], dict[str, Any]]:
|
|
69
|
+
"""Get the parser for the given file path."""
|
|
70
|
+
file_path = Path(file_path).resolve()
|
|
71
|
+
match file_path.suffix.lower():
|
|
72
|
+
case ".json":
|
|
73
|
+
return _parse_json
|
|
74
|
+
case ".yaml" | ".yml":
|
|
75
|
+
return _parse_yaml
|
|
76
|
+
case _:
|
|
77
|
+
msg = (
|
|
78
|
+
f"Failed to parse, {file_path}. Unsupported file extension, "
|
|
79
|
+
+ f"{file_path.suffix}. Pass in a custom config_parser argument or "
|
|
80
|
+
+ "use one of the supported file extensions, .json, .yaml, .yml, .toml."
|
|
81
|
+
)
|
|
82
|
+
raise ConfigParsingError(msg)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _parse_env_variables(text: str) -> str:
|
|
86
|
+
"""Parse environment variables in the configuration text."""
|
|
87
|
+
try:
|
|
88
|
+
return Template(text).substitute(os.environ)
|
|
89
|
+
except KeyError as error:
|
|
90
|
+
msg = f"Environment variable not found: {error}"
|
|
91
|
+
raise ConfigParsingError(msg) from error
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _recursive_merge_dicts(dest: dict[str, Any], src: dict[str, Any]) -> None:
|
|
95
|
+
"""Recursively merge two dictionaries in place."""
|
|
96
|
+
for key, value in src.items():
|
|
97
|
+
if isinstance(value, dict):
|
|
98
|
+
if isinstance(dest.get(key), dict):
|
|
99
|
+
_recursive_merge_dicts(dest[key], value)
|
|
100
|
+
else:
|
|
101
|
+
dest[key] = value
|
|
102
|
+
else:
|
|
103
|
+
dest[key] = value
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def load_config(
|
|
107
|
+
config_initializer: Callable[..., T],
|
|
108
|
+
config_path: str | Path | None = None,
|
|
109
|
+
overrides: dict[str, Any] | None = None,
|
|
110
|
+
set_cwd: bool = True,
|
|
111
|
+
parse_env_vars: bool = True,
|
|
112
|
+
load_dot_env_file: bool = True,
|
|
113
|
+
dot_env_path: str | Path | None = None,
|
|
114
|
+
config_parser: Callable[[str], dict[str, Any]] | None = None,
|
|
115
|
+
file_encoding: str = "utf-8",
|
|
116
|
+
) -> T:
|
|
117
|
+
"""Load configuration from a file.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
config_initializer : Callable[..., T]
|
|
122
|
+
Configuration constructor/initializer.
|
|
123
|
+
Should accept **kwargs to initialize the configuration,
|
|
124
|
+
e.g., Config(**kwargs).
|
|
125
|
+
config_path : str | Path | None, optional (default=None)
|
|
126
|
+
Path to the configuration directory containing settings.[yaml|yml|json].
|
|
127
|
+
Or path to a configuration file itself.
|
|
128
|
+
If None, search the current working directory for
|
|
129
|
+
settings.[yaml|yml|json].
|
|
130
|
+
overrides : dict[str, Any] | None, optional (default=None)
|
|
131
|
+
Configuration overrides.
|
|
132
|
+
Useful for overriding configuration settings programmatically,
|
|
133
|
+
perhaps from CLI flags.
|
|
134
|
+
set_cwd : bool, optional (default=True)
|
|
135
|
+
Whether to set the current working directory to the directory
|
|
136
|
+
containing the configuration file. Helpful for resolving relative paths
|
|
137
|
+
in the configuration file.
|
|
138
|
+
parse_env_vars : bool, optional (default=True)
|
|
139
|
+
Whether to parse environment variables in the configuration text.
|
|
140
|
+
load_dot_env_file : bool, optional (default=True)
|
|
141
|
+
Whether to load the .env file prior to parsing environment variables.
|
|
142
|
+
dot_env_path : str | Path | None, optional (default=None)
|
|
143
|
+
Optional .env file to load prior to parsing env variables.
|
|
144
|
+
If None and load_dot_env_file is True, looks for a .env file in the
|
|
145
|
+
same directory as the config file.
|
|
146
|
+
config_parser : Callable[[str], dict[str, Any]] | None, optional (default=None)
|
|
147
|
+
function to parse the configuration text, (str) -> dict[str, Any].
|
|
148
|
+
If None, the parser is inferred from the file extension.
|
|
149
|
+
Supported extensions: .json, .yaml, .yml.
|
|
150
|
+
file_encoding : str, optional (default="utf-8")
|
|
151
|
+
File encoding to use when reading the configuration file.
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
T
|
|
156
|
+
The initialized configuration object.
|
|
157
|
+
|
|
158
|
+
Raises
|
|
159
|
+
------
|
|
160
|
+
FileNotFoundError
|
|
161
|
+
- If the config file is not found.
|
|
162
|
+
- If the .env file is not found when parse_env_vars is True and dot_env_path is provided.
|
|
163
|
+
|
|
164
|
+
ConfigParsingError
|
|
165
|
+
- If an environment variable is not found when parsing env variables.
|
|
166
|
+
- If there was a problem merging the overrides with the configuration.
|
|
167
|
+
- If parser=None and load_config was unable to determine how to parse
|
|
168
|
+
the file based on the file extension.
|
|
169
|
+
- If the parser fails to parse the configuration text.
|
|
170
|
+
"""
|
|
171
|
+
config_path = Path(config_path).resolve() if config_path else Path.cwd()
|
|
172
|
+
config_path = _get_config_file_path(config_path)
|
|
173
|
+
|
|
174
|
+
file_contents = config_path.read_text(encoding=file_encoding)
|
|
175
|
+
|
|
176
|
+
if parse_env_vars:
|
|
177
|
+
if load_dot_env_file:
|
|
178
|
+
required = dot_env_path is not None
|
|
179
|
+
dot_env_path = (
|
|
180
|
+
Path(dot_env_path) if dot_env_path else config_path.parent / ".env"
|
|
181
|
+
)
|
|
182
|
+
_load_dotenv(dot_env_path, required=required)
|
|
183
|
+
file_contents = _parse_env_variables(file_contents)
|
|
184
|
+
|
|
185
|
+
if config_parser is None:
|
|
186
|
+
config_parser = _get_parser_for_file(config_path)
|
|
187
|
+
|
|
188
|
+
config_data: dict[str, Any] = {}
|
|
189
|
+
try:
|
|
190
|
+
config_data = config_parser(file_contents)
|
|
191
|
+
except Exception as error:
|
|
192
|
+
msg = f"Failed to parse config_path: {config_path}. Error: {error}"
|
|
193
|
+
raise ConfigParsingError(msg) from error
|
|
194
|
+
|
|
195
|
+
if overrides is not None:
|
|
196
|
+
try:
|
|
197
|
+
_recursive_merge_dicts(config_data, overrides)
|
|
198
|
+
except Exception as error:
|
|
199
|
+
msg = f"Failed to merge overrides with config_path: {config_path}. Error: {error}"
|
|
200
|
+
raise ConfigParsingError(msg) from error
|
|
201
|
+
|
|
202
|
+
if set_cwd:
|
|
203
|
+
os.chdir(config_path.parent)
|
|
204
|
+
|
|
205
|
+
return config_initializer(**config_data)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Copyright (c) 2025 Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License
|
|
3
|
+
|
|
4
|
+
"""Factory ABC."""
|
|
5
|
+
|
|
6
|
+
from abc import ABC
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any, ClassVar, Generic, Literal, TypeVar
|
|
10
|
+
|
|
11
|
+
from graphrag_common.hasher import hash_data
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T", covariant=True)
|
|
14
|
+
|
|
15
|
+
ServiceScope = Literal["singleton", "transient"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class _ServiceDescriptor(Generic[T]):
|
|
20
|
+
"""Descriptor for a service."""
|
|
21
|
+
|
|
22
|
+
scope: ServiceScope
|
|
23
|
+
initializer: Callable[..., T]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Factory(ABC, Generic[T]):
|
|
27
|
+
"""Abstract base class for factories."""
|
|
28
|
+
|
|
29
|
+
_instance: ClassVar["Factory | None"] = None
|
|
30
|
+
|
|
31
|
+
def __new__(cls, *args: Any, **kwargs: Any) -> "Factory[T]":
|
|
32
|
+
"""Create a new instance of Factory if it does not exist."""
|
|
33
|
+
if cls._instance is None:
|
|
34
|
+
cls._instance = super().__new__(cls, *args, **kwargs)
|
|
35
|
+
return cls._instance
|
|
36
|
+
|
|
37
|
+
def __init__(self):
|
|
38
|
+
if not hasattr(self, "_initialized"):
|
|
39
|
+
self._service_initializers: dict[str, _ServiceDescriptor[T]] = {}
|
|
40
|
+
self._initialized_services: dict[str, T] = {}
|
|
41
|
+
self._initialized = True
|
|
42
|
+
|
|
43
|
+
def __contains__(self, strategy: str) -> bool:
|
|
44
|
+
"""Check if a strategy is registered."""
|
|
45
|
+
return strategy in self._service_initializers
|
|
46
|
+
|
|
47
|
+
def keys(self) -> list[str]:
|
|
48
|
+
"""Get a list of registered strategy names."""
|
|
49
|
+
return list(self._service_initializers.keys())
|
|
50
|
+
|
|
51
|
+
def register(
|
|
52
|
+
self,
|
|
53
|
+
strategy: str,
|
|
54
|
+
initializer: Callable[..., T],
|
|
55
|
+
scope: ServiceScope = "transient",
|
|
56
|
+
) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Register a new service.
|
|
59
|
+
|
|
60
|
+
Args
|
|
61
|
+
----
|
|
62
|
+
strategy: str
|
|
63
|
+
The name of the strategy.
|
|
64
|
+
initializer: Callable[..., T]
|
|
65
|
+
A callable that creates an instance of T.
|
|
66
|
+
scope: ServiceScope (default: "transient")
|
|
67
|
+
The scope of the service ("singleton" or "transient").
|
|
68
|
+
Singleton services are cached based on their init args
|
|
69
|
+
so that the same instance is returned for the same init args.
|
|
70
|
+
"""
|
|
71
|
+
self._service_initializers[strategy] = _ServiceDescriptor(scope, initializer)
|
|
72
|
+
|
|
73
|
+
def create(self, strategy: str, init_args: dict[str, Any] | None = None) -> T:
|
|
74
|
+
"""
|
|
75
|
+
Create a service instance based on the strategy.
|
|
76
|
+
|
|
77
|
+
Args
|
|
78
|
+
----
|
|
79
|
+
strategy: str
|
|
80
|
+
The name of the strategy.
|
|
81
|
+
init_args: dict[str, Any] | None
|
|
82
|
+
A dictionary of keyword arguments to pass to the service initializer.
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
An instance of T.
|
|
87
|
+
|
|
88
|
+
Raises
|
|
89
|
+
------
|
|
90
|
+
ValueError: If the strategy is not registered.
|
|
91
|
+
"""
|
|
92
|
+
if strategy not in self._service_initializers:
|
|
93
|
+
msg = f"Strategy '{strategy}' is not registered. Registered strategies are: {', '.join(list(self._service_initializers.keys()))}"
|
|
94
|
+
raise ValueError(msg)
|
|
95
|
+
|
|
96
|
+
# Delete entries with value None
|
|
97
|
+
# That way services can have default values
|
|
98
|
+
init_args = {k: v for k, v in (init_args or {}).items() if v is not None}
|
|
99
|
+
|
|
100
|
+
service_descriptor = self._service_initializers[strategy]
|
|
101
|
+
if service_descriptor.scope == "singleton":
|
|
102
|
+
cache_key = hash_data({
|
|
103
|
+
"strategy": strategy,
|
|
104
|
+
"init_args": init_args,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
if cache_key not in self._initialized_services:
|
|
108
|
+
self._initialized_services[cache_key] = service_descriptor.initializer(
|
|
109
|
+
**init_args
|
|
110
|
+
)
|
|
111
|
+
return self._initialized_services[cache_key]
|
|
112
|
+
|
|
113
|
+
return service_descriptor.initializer(**(init_args or {}))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Copyright (c) 2024 Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License
|
|
3
|
+
|
|
4
|
+
"""The GraphRAG hasher module."""
|
|
5
|
+
|
|
6
|
+
from graphrag_common.hasher.hasher import (
|
|
7
|
+
Hasher,
|
|
8
|
+
hash_data,
|
|
9
|
+
make_yaml_serializable,
|
|
10
|
+
sha256_hasher,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Hasher",
|
|
15
|
+
"hash_data",
|
|
16
|
+
"make_yaml_serializable",
|
|
17
|
+
"sha256_hasher",
|
|
18
|
+
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Copyright (c) 2024 Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License
|
|
3
|
+
|
|
4
|
+
"""The GraphRAG hasher module."""
|
|
5
|
+
|
|
6
|
+
import hashlib
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
|
|
12
|
+
Hasher = Callable[[str], str]
|
|
13
|
+
"""Type alias for a hasher function (data: str) -> str."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def sha256_hasher(data: str) -> str:
|
|
17
|
+
"""Generate a SHA-256 hash for the input data."""
|
|
18
|
+
return hashlib.sha256(data.encode("utf-8")).hexdigest()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def make_yaml_serializable(data: Any) -> Any:
|
|
22
|
+
"""Convert data to a YAML-serializable format."""
|
|
23
|
+
if isinstance(data, (list, tuple)):
|
|
24
|
+
return tuple(make_yaml_serializable(item) for item in data)
|
|
25
|
+
|
|
26
|
+
if isinstance(data, set):
|
|
27
|
+
return tuple(sorted(make_yaml_serializable(item) for item in data))
|
|
28
|
+
|
|
29
|
+
if isinstance(data, dict):
|
|
30
|
+
return tuple(
|
|
31
|
+
sorted((key, make_yaml_serializable(value)) for key, value in data.items())
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return str(data)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def hash_data(data: Any, *, hasher: Hasher | None = None) -> str:
|
|
38
|
+
"""Hash the input data dictionary using the specified hasher function.
|
|
39
|
+
|
|
40
|
+
Args
|
|
41
|
+
----
|
|
42
|
+
data: dict[str, Any]
|
|
43
|
+
The input data to be hashed.
|
|
44
|
+
The input data is serialized using yaml
|
|
45
|
+
to support complex data structures such as classes and functions.
|
|
46
|
+
hasher: Hasher | None (default: sha256_hasher)
|
|
47
|
+
The hasher function to use. (data: str) -> str
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
str
|
|
52
|
+
The resulting hash of the input data.
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
hasher = hasher or sha256_hasher
|
|
56
|
+
try:
|
|
57
|
+
return hasher(yaml.dump(data, sort_keys=True))
|
|
58
|
+
except TypeError:
|
|
59
|
+
return hasher(yaml.dump(make_yaml_serializable(data), sort_keys=True))
|
graphrag_common/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: graphrag-common
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Common utilities and types for GraphRAG
|
|
5
|
+
Project-URL: Source, https://github.com/microsoft/graphrag
|
|
6
|
+
Author: Mónica Carvajal
|
|
7
|
+
Author-email: Alonso Guevara Fernández <alonsog@microsoft.com>, Andrés Morales Esquivel <andresmor@microsoft.com>, Chris Trevino <chtrevin@microsoft.com>, David Tittsworth <datittsw@microsoft.com>, Dayenne de Souza <ddesouza@microsoft.com>, Derek Worthen <deworthe@microsoft.com>, Gaudy Blanco Meneses <gaudyb@microsoft.com>, Ha Trinh <trinhha@microsoft.com>, Jonathan Larson <jolarso@microsoft.com>, Josh Bradley <joshbradley@microsoft.com>, Kate Lytvynets <kalytv@microsoft.com>, Kenny Zhang <zhangken@microsoft.com>, Nathan Evans <naevans@microsoft.com>, Rodrigo Racanicci <rracanicci@microsoft.com>, Sarah Smith <smithsarah@microsoft.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Python: <3.14,>=3.11
|
|
15
|
+
Requires-Dist: python-dotenv~=1.0
|
|
16
|
+
Requires-Dist: pyyaml~=6.0
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# GraphRAG Common
|
|
20
|
+
|
|
21
|
+
This package provides utility modules for GraphRAG, including a flexible factory system for dependency injection and service registration, and a comprehensive configuration loading system with Pydantic model support, environment variable substitution, and automatic file discovery.
|
|
22
|
+
|
|
23
|
+
## Factory module
|
|
24
|
+
|
|
25
|
+
The Factory class provides a flexible dependency injection pattern that can register and create instances of classes implementing a common interface using string-based strategies. It supports both transient scope (creates new instances on each request) and singleton scope (returns the same instance after first creation).
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from abc import ABC, abstractmethod
|
|
29
|
+
|
|
30
|
+
from graphrag_common.factory import Factory
|
|
31
|
+
|
|
32
|
+
class SampleABC(ABC):
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def get_value(self) -> str:
|
|
36
|
+
msg = "Subclasses must implement the get_value method."
|
|
37
|
+
raise NotImplementedError(msg)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ConcreteClass(SampleABC):
|
|
41
|
+
def __init__(self, value: str):
|
|
42
|
+
self._value = value
|
|
43
|
+
|
|
44
|
+
def get_value(self) -> str:
|
|
45
|
+
return self._value
|
|
46
|
+
|
|
47
|
+
class SampleFactory(Factory[SampleABC]):
|
|
48
|
+
"""A Factory for SampleABC classes."""
|
|
49
|
+
|
|
50
|
+
factory = SampleFactory()
|
|
51
|
+
|
|
52
|
+
# Registering transient services
|
|
53
|
+
# A new one is created for every request
|
|
54
|
+
factory.register("some_strategy", ConcreteTestClass)
|
|
55
|
+
|
|
56
|
+
trans1 = factory.create("some_strategy", {"value": "test1"})
|
|
57
|
+
trans2 = factory.create("some_strategy", {"value": "test2"})
|
|
58
|
+
|
|
59
|
+
assert trans1 is not trans2
|
|
60
|
+
assert trans1.get_value() == "test1"
|
|
61
|
+
assert trans2.get_value() == "test2"
|
|
62
|
+
|
|
63
|
+
# Registering singleton services
|
|
64
|
+
# After first creation, the same one is returned every time
|
|
65
|
+
factory.register("some_other_strategy", ConcreteTestClass, scope="singleton")
|
|
66
|
+
|
|
67
|
+
single1 = factory.create("some_other_strategy", {"value": "singleton"})
|
|
68
|
+
single2 = factory.create("some_other_strategy", {"value": "ignored"})
|
|
69
|
+
|
|
70
|
+
assert single1 is single2
|
|
71
|
+
assert single1.get_value() == "singleton"
|
|
72
|
+
assert single2.get_value() == "singleton"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Config module
|
|
76
|
+
|
|
77
|
+
The load_config function provides a comprehensive configuration loading system that automatically discovers and parses YAML/JSON config files into Pydantic models with support for environment variable substitution and .env file loading. It offers flexible features like config overrides, custom parsers for different file formats, and automatically sets the working directory to the config file location for relative path resolution.
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from pydantic import BaseModel, Field
|
|
81
|
+
from graphrag_common.config import load_config
|
|
82
|
+
|
|
83
|
+
from pathlib import Path
|
|
84
|
+
|
|
85
|
+
class Logging(BaseModel):
|
|
86
|
+
"""Test nested model."""
|
|
87
|
+
|
|
88
|
+
directory: str = Field(default="output/logs")
|
|
89
|
+
filename: str = Field(default="logs.txt")
|
|
90
|
+
|
|
91
|
+
class Config(BaseModel):
|
|
92
|
+
"""Test configuration model."""
|
|
93
|
+
|
|
94
|
+
name: str = Field(description="Name field.")
|
|
95
|
+
logging: Logging = Field(description="Nested model field.")
|
|
96
|
+
|
|
97
|
+
# Basic - by default:
|
|
98
|
+
# - searches for Path.cwd() / settings.[yaml|yml|json]
|
|
99
|
+
# - sets the CWD to the directory containing the config file.
|
|
100
|
+
# so if no custom config path is provided than CWD remains unchanged.
|
|
101
|
+
# - loads config_directory/.env file
|
|
102
|
+
# - parses ${env} in the config file
|
|
103
|
+
config = load_config(Config)
|
|
104
|
+
|
|
105
|
+
# Custom file location
|
|
106
|
+
config = load_config(Config, "path_to_config_filename_or_directory_containing_settings.[yaml|yml|json]")
|
|
107
|
+
|
|
108
|
+
# Using a custom file extension with
|
|
109
|
+
# custom config parser (str) -> dict[str, Any]
|
|
110
|
+
config = load_config(
|
|
111
|
+
config_initializer=Config,
|
|
112
|
+
config_path="config.toml",
|
|
113
|
+
config_parser=lambda contents: toml.loads(contents) # Needs toml pypi package
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# With overrides - provided values override whats in the config file
|
|
117
|
+
# Only overrides what is specified - recursively merges settings.
|
|
118
|
+
config = load_config(
|
|
119
|
+
config_initializer=Config,
|
|
120
|
+
overrides={
|
|
121
|
+
"name": "some name",
|
|
122
|
+
"logging": {
|
|
123
|
+
"filename": "my_logs.txt"
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# By default, sets CWD to directory containing config file
|
|
129
|
+
# So custom config paths will change the CWD.
|
|
130
|
+
config = load_config(
|
|
131
|
+
config_initializer=Config,
|
|
132
|
+
config_path="some/path/to/config.yaml",
|
|
133
|
+
set_cwd=True # default
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# now cwd == some/path/to
|
|
137
|
+
assert Path.cwd() == "some/path/to"
|
|
138
|
+
|
|
139
|
+
# And now throughout the codebase resolving relative paths in config
|
|
140
|
+
# will resolve relative to the config directory
|
|
141
|
+
Path(config.logging.directory) == "some/path/to/output/logs"
|
|
142
|
+
|
|
143
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
graphrag_common/__init__.py,sha256=s68tLiBfUEDmUvx-HugWKjamxVULHs70LCS4DoMlU7I,109
|
|
2
|
+
graphrag_common/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
graphrag_common/config/__init__.py,sha256=SjctkqbxSprZSkpDa9s-1l39dyNOPqfPyGXNj33RSEo,241
|
|
4
|
+
graphrag_common/config/load_config.py,sha256=9pGAnRP8ZfzD5yDOBMqumBPnQ3acnDgaK3HtgnJTJmY,7361
|
|
5
|
+
graphrag_common/factory/__init__.py,sha256=UVp3KH-wSfJSXJpIyTxPSml6tPZWsyfNrUIJFlMJqPo,219
|
|
6
|
+
graphrag_common/factory/factory.py,sha256=rHXGkYZztKD_rid-kxYk8-2qio0O3htWRLlM1myNWMg,3749
|
|
7
|
+
graphrag_common/hasher/__init__.py,sha256=6CxkLbWyTFfQvwTkkzqtxUGUuUPEEquJ6_YXhWKj3xE,330
|
|
8
|
+
graphrag_common/hasher/hasher.py,sha256=JaPTwJnWru4Nhx9UXNdFwV8y7RSb-XLR6OOYIRJiovc,1709
|
|
9
|
+
graphrag_common-3.0.0.dist-info/METADATA,sha256=2MLK4ExHqIVBAp6bmf7sjZ2xv6MyVAwatT6GTXoZ_QI,5563
|
|
10
|
+
graphrag_common-3.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
11
|
+
graphrag_common-3.0.0.dist-info/licenses/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141
|
|
12
|
+
graphrag_common-3.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Microsoft Corporation.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE
|