Singleton Factory API
sweet_tea.singleton_factory.SingletonFactory
Bases: BaseFactory
Factory for registering and retrieving singleton instances.
This implements the service locator pattern where pre-configured instances can be registered once and retrieved multiple times. This is useful for:
- Singleton services that should only exist once
- Pre-configured database connections
- Dependency injection containers
- Caching expensive-to-create objects
Thread-safe: the cache is synchronized, and exactly one thread constructs the instance for a given key. The factory's lock is not held while a constructor runs — a constructor may itself resolve or create, and holding this lock across that inverted the lock order against the registry (SWE-22) — so construction is admitted by a per-key lock instead. Nor is it held while a configuration is judged for drift: that applies any declared configuration model, and a pydantic validator is arbitrary code on the same footing as a constructor (SWE-29).
Because a constructor may resolve or create, it may also — by mistake or by design
— ask for a singleton whose construction it is already inside. That is a circular
dependency, and this factory reports it as a :class:SweetTeaError naming the
cycle rather than blocking on a lock that can never be released (SWE-36).
Source code in sweet_tea/singleton_factory.py
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 | |
__admit_construction(cache_key, construction_lock)
classmethod
Admit this thread to construct one key, or say why it never can.
Construction is admitted by a per-key lock, and a plain lock is the right tool
for the thing it guards — but only for threads that have something to wait for.
A thread that already holds the lock has not: a constructor reaching back into
:meth:create for the key it is being constructed under is asking for an
instance that cannot exist until it returns. Blocking there was a hang with no
traceback and no timeout, which is the one failure mode a container must never
hand a consumer, since there is nothing left to debug (SWE-36). Circular
dependencies are the consumer's mistake; reporting them is this factory's job.
Two shapes are detected, by the two things that can see them:
- Within one thread — the self-referential constructor, and the longer
alpha -> beta -> alphachain — against this thread's own construction stack, before any lock is touched. No shared state is read, so the check costs a list scan whose length is the depth of the dependency graph. - Across threads — two threads entering one cycle from opposite ends, each holding the lock the other needs. No thread's own stack can see this, so a thread about to block registers what it is waiting for and walks the wait-for graph. Whichever of the two registers second finds the chain closed and raises; the other is freed when that one unwinds, and then meets the cycle on its own stack.
The alternative for the cross-thread case was acquiring with a timeout and calling expiry a deadlock. That was rejected: any timeout short enough to diagnose a deadlock promptly is one a legitimately slow constructor — a connection pool, a model load, a cold cache on a loaded CI box — can exceed, and turning a correct program into a failing one to diagnose an incorrect one is the wrong trade. The wait-for graph needs no number chosen, reports the instant the cycle closes rather than seconds later, and names the threads and keys involved. What it cannot see is a cycle that leaves this factory — a constructor blocking on a lock of the consumer's own, held by a thread waiting on a singleton — which no bookkeeping here could see either; that remains a documented limitation rather than a guess enforced by a timer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache_key
|
tuple[str, str, str]
|
Identity of the entry being constructed. |
required |
construction_lock
|
Lock
|
The key's construction lock, already created under the factory's lock by the caller. |
required |
Yields:
| Type | Description |
|---|---|
None
|
None, with the construction lock held and this thread recorded as the key's |
None
|
constructor. |
Raises:
| Type | Description |
|---|---|
SweetTeaError
|
When this thread is already constructing the key, or when waiting for it would close a cycle of threads that can never finish. |
Source code in sweet_tea/singleton_factory.py
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 | |
__applied(entry, class_def, configuration)
classmethod
Apply a class's declared __configuration__, when it declares one.
Done through the same helper construction uses, so that one application serves both: construction builds from the model, and the comparison basis is of the model rather than of the dict the caller happened to type (see SWE-29).
Applied once per call to :meth:create, which is what it takes to keep a
validator from running twice for every singleton built (see SWE-42).
Nothing is caught. Which failures are survivable is the caller's question, not this one's, and the two callers answer it differently: on the constructing path a schema that refuses the configuration is a create that must fail, while on the cache-hit path no instance is being built and there is nothing to fail at.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entry
|
Entry
|
The resolved registry entry, used for error messages. |
required |
class_def
|
Any
|
The class whose declaration to read. Passed in rather than taken
from the entry because the two callers may not read it the same way —
see :meth: |
required |
configuration
|
dict[str, Any] | BaseModel | None
|
The configuration as given. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any] | BaseModel | None
|
The validated model when a schema is declared, and the configuration |
dict[str, Any] | BaseModel | None
|
unchanged when none is. |
Raises:
| Type | Description |
|---|---|
SweetTeaError
|
When the declaration is not a BaseModel subclass, or when it rejects the configuration. |
Exception
|
Whatever a validator raises on its own account. |
Source code in sweet_tea/singleton_factory.py
__block_until_admitted(cache_key, construction_lock, identity)
classmethod
Wait for another thread's construction of a key, unless that would deadlock.
The wait is registered before it is judged, and both happen in one hold of the factory's lock. That ordering is what makes the detection complete rather than best-effort: two threads closing a cycle each register, and the one that registers second necessarily sees the other's registration, so the cycle cannot be missed by both. The lock is released before blocking, since blocking under it is the lock inversion SWE-22 exists to prevent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache_key
|
tuple[str, str, str]
|
The key this thread wants to construct. |
required |
construction_lock
|
Lock
|
Its construction lock, currently held by another thread. |
required |
identity
|
int
|
This thread's id, as :func: |
required |
Raises:
| Type | Description |
|---|---|
SweetTeaError
|
When the wait would close a cycle of threads waiting on one another's constructions. |
Source code in sweet_tea/singleton_factory.py
__commit_token(cache_key)
classmethod
What must still hold when a construction commits, for its instance to be kept.
Must be called with the factory's lock held, by both readers: the point of the token is that the state it samples cannot move between the sample and the comparison without a clear() or a pop() happening in between, and only the lock makes that true.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache_key
|
tuple[str, str, str]
|
Identity of the entry being constructed. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The number of clear() calls so far, with the number of times this key has |
int
|
been popped. |
Source code in sweet_tea/singleton_factory.py
__comparable(entry, configuration)
classmethod
Compute a comparison basis on the cache-hit path, where nothing may go wrong.
Nothing is being constructed here. The caller has its instance already and is owed it whatever this makes of the configuration, so every way of failing to reach a basis has to end in a return rather than a raise. Two of them end differently, and the distinction is the point of this method:
- The schema refuses the configuration. Decidable, and decided: the cached
instance's configuration validated, so one that does not validate cannot be
the same request. That is drift, and it is reported (
__REJECTED_BY_SCHEMA). - The schema cannot be applied at all — a validator raising
AttributeErroron a value of the wrong type, aKeyError, anything that is not pydantic saying no. Then there is no basis to compare and no verdict to give, so this falls silent, exactly as a value whose==is not boolean does. Previously such an exception escaped :meth:createon a call that only wanted the cached instance, which is the one thing a warning path must never do (SWE-42).
Telling the two apart by exception type is the honest reading of each: a
ValidationError, which :meth:BaseFactory._validate_configuration has
already wrapped as a :class:SweetTeaError, is the schema's own considered no.
Anything else is a validator that broke, and a broken validator is evidence
about the validator and none at all about the configuration.
Nothing here imports, and nothing here resolves a lazy entry: the cache-hit path runs where SWE-22 showed what an import under a lock costs. A still-lazy entry leaves drift undecidable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entry
|
Entry
|
The resolved registry entry the cached instance was built from. |
required |
configuration
|
dict[str, Any] | BaseModel | None
|
The configuration as given. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The keyword arguments construction would spread; |
Any
|
when a declared model refuses the configuration; None when no basis can be |
Any
|
computed, which leaves drift undecidable. |
Source code in sweet_tea/singleton_factory.py
__deadlock_chain(wanted, identity)
classmethod
Follow the wait-for graph from a key, looking for a way back to this thread.
Must be called with the factory's lock held and this thread's wait already registered, so that what the walk reads is the whole graph at one instant.
Each hop asks the same two questions: who is constructing the key being waited for, and is that thread itself waiting? An owner that is running, or a key with no owner at all, ends the walk — the chain terminates in work that can finish, which is contention and not deadlock. An owner that turns out to be this thread closes the circle: this thread cannot release what it is constructing while it waits, so nothing in the chain can ever proceed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wanted
|
tuple[str, str, str]
|
The key this thread is waiting to construct. |
required |
identity
|
int
|
This thread's id, as :func: |
required |
Returns:
| Type | Description |
|---|---|
list[str] | None
|
The chain of keys and the threads holding them, as text ready to join, when |
list[str] | None
|
waiting would deadlock; None when the wait is ordinary contention. |
Source code in sweet_tea/singleton_factory.py
__describe(cache_key)
classmethod
Name a cache key the way a reader of a cycle message needs to see it.
The key alone reads best and is almost always enough, but two entries can share
a key and differ by library or label — which is exactly the pair a cycle message
would otherwise render as alpha -> alpha — so those are spelled out when
they are set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache_key
|
tuple[str, str, str]
|
Identity of a registry entry, as (key, library, label). |
required |
Returns:
| Type | Description |
|---|---|
str
|
A short description for a diagnostic message. |
Source code in sweet_tea/singleton_factory.py
__drifted(cache_key, entry, configuration)
classmethod
Decide whether this call asked for something other than what is cached.
The answer is no whenever the question cannot be answered. Drift is a warning,
not a diagnosis: where the comparison is undecidable, the instance is returned
exactly as it would have been and the caller hears nothing, which is quieter
than a warning fired on every call and far quieter than an exception out of
:meth:create.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache_key
|
tuple[str, str, str]
|
Identity of the cached entry. |
required |
entry
|
Entry
|
The resolved entry, carrying the class whose declared configuration model the comparison is made through. |
required |
configuration
|
dict[str, Any] | BaseModel | None
|
What this call passed. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True when this call's configuration would have built a different instance. |
Source code in sweet_tea/singleton_factory.py
__keys_under_construction()
classmethod
Get the calling thread's own stack of keys it is part-way through constructing.
Thread-local and therefore lock-free: the list is created on first use by the thread that will be its only reader and only writer.
Returns:
| Type | Description |
|---|---|
list[tuple[str, str, str]]
|
The live list, outermost key first, to be appended to and popped by the |
list[tuple[str, str, str]]
|
calling thread alone. |
Source code in sweet_tea/singleton_factory.py
__reduced(configuration)
classmethod
Reduce a schema-applied configuration to the keyword arguments it spreads.
Drift is judged on what the class would actually be built with rather than on
what the caller typed, because that is the question the warning answers: would
this call have produced a different instance? Several spellings of one
configuration reach the constructor identically — None and {}, a dict
and the model a declared __configuration__ validates it into, a field
omitted and the same field spelled out as the schema's own default — and all of
them have to compare equal (see SWE-29).
This mirrors the tail of :meth:BaseFactory._construct step for step; if that
reduction ever changes, this must change with it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
configuration
|
dict[str, Any] | BaseModel | None
|
The configuration with any declared schema already applied,
as :meth: |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any] | None
|
The keyword arguments construction would spread, or None when the reduction |
dict[str, Any] | None
|
cannot be made at all, which leaves drift undecidable. |
Source code in sweet_tea/singleton_factory.py
__same_request(requested, existing)
classmethod
Decide whether two reduced configurations ask for the same thing.
Plain == answers that wrongly in two places, and both rulings here are
deliberate (see SWE-29):
- NaN is the same request as NaN. That
float("nan") != float("nan")is a statement about arithmetic, not about intent: a caller who passes NaN twice asked for the same configuration both times.==already agreed whenever the two calls happened to share one NaN object, since container comparison short-circuits on identity, so reflexivity was never really the rule — only an accident of which object the caller was holding. - 1, 1.0 and True are different requests.
==conflates them, but they arrive at the constructor as different types and behave differently once there, and0passed whereFalsewas meant is a plausible mistake worth reporting. Numbers are therefore compared type-sensitively. Where a class declares__configuration__the schema has already coerced both spellings to the field's own type before this is reached, so an equivalence somebody declared is honoured and one nobody declared is not.
Dicts, lists and tuples are walked so that both rulings hold at any depth.
Everything else is left to the value's own ==. Identity is taken as equality
first, which is what lets an object with no useful == compare equal to
itself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
requested
|
Any
|
What this call would build with. |
required |
existing
|
Any
|
The recorded basis of the cached instance. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True when the two are the same request. |
Raises:
| Type | Description |
|---|---|
Exception
|
Whatever a value's own |
Source code in sweet_tea/singleton_factory.py
774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 | |
__snapshot(kwargs)
classmethod
Copy a comparison basis away from the caller's own objects.
The recorded basis has to keep answering what the first caller asked for, however long ago. A shallow copy cannot: it keeps the caller's own containers, so a later mutation of one rewrites history. That cuts both ways — a caller who passes an equal configuration gets blamed for a mutation someone else made, and a caller who mutates a nested value and asks again is told nothing, because both sides are reading the one object (see SWE-29).
Values are copied one at a time rather than the mapping as a whole, because
copy.deepcopy raises on plenty of ordinary objects — an open file, a lock, a
socket, some C extension types. A value that refuses to copy is kept by
reference instead, which is no worse for that value than the old behaviour and
leaves every other key in the configuration properly snapshotted; the
alternative, discarding the whole basis, would cost the warning entirely for any
configuration carrying a lock. Copying per value also drops any aliasing between
two values of one configuration, which equality does not look at.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kwargs
|
dict[str, Any] | None
|
The keyword arguments to record, as :meth: |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any] | None
|
A snapshot safe to keep, or None when there were no keyword arguments to |
dict[str, Any] | None
|
record — which records drift against this instance as undecidable. |
Source code in sweet_tea/singleton_factory.py
__warn_on_configuration_drift(cache_key, entry, configuration)
classmethod
Report a configuration that arrived too late to take effect.
A cached singleton is never rebuilt — other holders rely on the identity — so a configuration passed to a later call is discarded. That is the right behaviour and the wrong silence: a caller who believes they configured something gets an instance configured by whoever called first (see SWE-14).
Nothing is said when the caller passed no configuration, since that is the
ordinary way to fetch an existing singleton. An empty dict says the same thing
and is treated the same way: construction maps it and None alike to no
keyword arguments, and someone writing configuration=options.get("cfg", {})
is fetching rather than configuring. Warning there was the false positive most
likely to get the whole category filtered out (see SWE-29).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache_key
|
tuple[str, str, str]
|
Identity of the cached entry. |
required |
entry
|
Entry
|
The resolved entry, which carries the class whose declared configuration model the comparison is made through. |
required |
configuration
|
dict[str, Any] | BaseModel | None
|
What this call passed. |
required |
Source code in sweet_tea/singleton_factory.py
__warn_on_disowned_instance(cache_key)
classmethod
Report an instance that was constructed but not cached.
A dropped instance is worth a warning rather than a log line, because the
caller is left holding something the factory has disowned: it is not the
singleton for its key, nothing else will ever be handed it, and the next
create for that key constructs a second object the holder of this one has
no way to learn about. Two objects that were both, briefly, "the" singleton is
precisely the situation this class exists to prevent, and the reset is what
caused it — so the report belongs with whoever's call lost, not only in a log
nobody reads after the fact.
Returning the instance anyway is deliberate. The construction finished, the
object is valid, and raising instead would turn a caller's correct call into a
failure because another thread reset the factory while it ran. A caller who
would rather fail has warnings.simplefilter("error", SweetTeaWarning),
which is the same switch the rest of this library hands them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache_key
|
tuple[str, str, str]
|
Identity of the entry whose instance was dropped. |
required |
Source code in sweet_tea/singleton_factory.py
clear()
classmethod
Remove all registered instances.
This is primarily useful for testing or resetting the factory state.
The construction locks are deliberately left in place. Dropping them could not strand a construction — a lock a thread holds stays alive through that thread's own reference — but that was never the danger. The danger was the second lock: with the mapping emptied, the next caller for a key under construction created a fresh lock for it, took that one unopposed, and ran the constructor a second time in parallel with the first. Two instances of a singleton, from a reset API, is the one outcome this class must not have (SWE-39). The locks are keyed by resolved cache key and so bounded by the registry, which costs little to keep.
A construction already running is not cancelled either — nothing can cancel it
— but it no longer installs its result over this reset. Clearing bumps the
epoch every in-flight construction sampled before it began, so a construction
that finishes after this call returns its instance to its caller with a warning
and caches nothing (SWE-45). Without that, list_singletons() went []
and then back to ['slow'] with no create in between, holding an
instance built against the registry as it was before the reset.
Source code in sweet_tea/singleton_factory.py
create(key, library='', label='', configuration=None)
classmethod
Get an existing singleton instance or create a new one if it doesn't exist.
This method provides lazy initialization - instances are created only when first requested. Subsequent calls with the same key will return the same cached instance.
A :meth:clear or a :meth:pop of this key that lands while the constructor
is running is honoured over this call: the instance is returned to the caller
but not cached, with a warning, since an instance built against state that has
since been reset must not install itself over the reset (SWE-45).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Name to reference the class from the registry. |
required |
library
|
str
|
Optional library filter for the class. |
''
|
label
|
str
|
Optional label filter for the class. |
''
|
configuration
|
dict[str, Any] | BaseModel | None
|
Configuration parameters as keyword arguments, given as a dict or a pydantic model whose fields are spread into the constructor. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
The existing singleton instance, or a newly created instance — registered, |
Any
|
unless the factory was reset while it was being constructed. |
Raises:
| Type | Description |
|---|---|
SweetTeaError
|
If the key is not found in the registry, filters don't match, or the key is already being constructed by the calling thread or by a thread this call would deadlock against (see SWE-36). |
Source code in sweet_tea/singleton_factory.py
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | |
list_keys()
classmethod
Get a list of all class keys that can be created.
Returns:
| Type | Description |
|---|---|
list[str]
|
List of available class keys from the registry in alphabetical order. |
Source code in sweet_tea/singleton_factory.py
list_singletons()
classmethod
Get a list of all cached singleton instance keys.
Returns:
| Type | Description |
|---|---|
list[str]
|
List of cached singleton keys in alphabetical order. Entries that share a |
list[str]
|
key across libraries or labels appear once per cached instance. |
Source code in sweet_tea/singleton_factory.py
pop(key, library='', label='')
classmethod
Remove and return a cached instance.
Resolves through the same path as :meth:create, so any spelling of the key
removes the instance that spelling would have returned.
A construction of this key that began before this call does not cache its
result afterwards, for the reason :meth:clear gives: an instance built before
a removal must not undo it (SWE-45). Only this key is affected — a construction
of some other singleton is none of this call's business.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The key of the instance to remove, in any supported spelling. |
required |
library
|
str
|
Optional library filter, matching the one passed to create. |
''
|
label
|
str
|
Optional label filter, matching the one passed to create. |
''
|
Returns:
| Type | Description |
|---|---|
Any
|
The removed instance. |
Raises:
| Type | Description |
|---|---|
SweetTeaError
|
If no instance is cached for the given key. |
Source code in sweet_tea/singleton_factory.py
Methods
create()
sweet_tea.singleton_factory.SingletonFactory.create(key, library='', label='', configuration=None)
classmethod
Get an existing singleton instance or create a new one if it doesn't exist.
This method provides lazy initialization - instances are created only when first requested. Subsequent calls with the same key will return the same cached instance.
A :meth:clear or a :meth:pop of this key that lands while the constructor
is running is honoured over this call: the instance is returned to the caller
but not cached, with a warning, since an instance built against state that has
since been reset must not install itself over the reset (SWE-45).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Name to reference the class from the registry. |
required |
library
|
str
|
Optional library filter for the class. |
''
|
label
|
str
|
Optional label filter for the class. |
''
|
configuration
|
dict[str, Any] | BaseModel | None
|
Configuration parameters as keyword arguments, given as a dict or a pydantic model whose fields are spread into the constructor. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
The existing singleton instance, or a newly created instance — registered, |
Any
|
unless the factory was reset while it was being constructed. |
Raises:
| Type | Description |
|---|---|
SweetTeaError
|
If the key is not found in the registry, filters don't match, or the key is already being constructed by the calling thread or by a thread this call would deadlock against (see SWE-36). |
Source code in sweet_tea/singleton_factory.py
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | |
clear()
sweet_tea.singleton_factory.SingletonFactory.clear()
classmethod
Remove all registered instances.
This is primarily useful for testing or resetting the factory state.
The construction locks are deliberately left in place. Dropping them could not strand a construction — a lock a thread holds stays alive through that thread's own reference — but that was never the danger. The danger was the second lock: with the mapping emptied, the next caller for a key under construction created a fresh lock for it, took that one unopposed, and ran the constructor a second time in parallel with the first. Two instances of a singleton, from a reset API, is the one outcome this class must not have (SWE-39). The locks are keyed by resolved cache key and so bounded by the registry, which costs little to keep.
A construction already running is not cancelled either — nothing can cancel it
— but it no longer installs its result over this reset. Clearing bumps the
epoch every in-flight construction sampled before it began, so a construction
that finishes after this call returns its instance to its caller with a warning
and caches nothing (SWE-45). Without that, list_singletons() went []
and then back to ['slow'] with no create in between, holding an
instance built against the registry as it was before the reset.
Source code in sweet_tea/singleton_factory.py
pop()
sweet_tea.singleton_factory.SingletonFactory.pop(key, library='', label='')
classmethod
Remove and return a cached instance.
Resolves through the same path as :meth:create, so any spelling of the key
removes the instance that spelling would have returned.
A construction of this key that began before this call does not cache its
result afterwards, for the reason :meth:clear gives: an instance built before
a removal must not undo it (SWE-45). Only this key is affected — a construction
of some other singleton is none of this call's business.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The key of the instance to remove, in any supported spelling. |
required |
library
|
str
|
Optional library filter, matching the one passed to create. |
''
|
label
|
str
|
Optional label filter, matching the one passed to create. |
''
|
Returns:
| Type | Description |
|---|---|
Any
|
The removed instance. |
Raises:
| Type | Description |
|---|---|
SweetTeaError
|
If no instance is cached for the given key. |
Source code in sweet_tea/singleton_factory.py
list_keys()
sweet_tea.singleton_factory.SingletonFactory.list_keys()
classmethod
Get a list of all class keys that can be created.
Returns:
| Type | Description |
|---|---|
list[str]
|
List of available class keys from the registry in alphabetical order. |
Source code in sweet_tea/singleton_factory.py
list_singletons()
sweet_tea.singleton_factory.SingletonFactory.list_singletons()
classmethod
Get a list of all cached singleton instance keys.
Returns:
| Type | Description |
|---|---|
list[str]
|
List of cached singleton keys in alphabetical order. Entries that share a |
list[str]
|
key across libraries or labels appear once per cached instance. |