basic-memory 0.23.1.dev27__py3-none-any.whl → 0.23.2__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.
- basic_memory/__init__.py +1 -1
- basic_memory/indexing/accepted_note_mutation_runner.py +99 -7
- basic_memory/mcp/tools/move_note.py +12 -1
- basic_memory/utils.py +46 -0
- {basic_memory-0.23.1.dev27.dist-info → basic_memory-0.23.2.dist-info}/METADATA +1 -1
- {basic_memory-0.23.1.dev27.dist-info → basic_memory-0.23.2.dist-info}/RECORD +9 -9
- {basic_memory-0.23.1.dev27.dist-info → basic_memory-0.23.2.dist-info}/WHEEL +0 -0
- {basic_memory-0.23.1.dev27.dist-info → basic_memory-0.23.2.dist-info}/entry_points.txt +0 -0
- {basic_memory-0.23.1.dev27.dist-info → basic_memory-0.23.2.dist-info}/licenses/LICENSE +0 -0
basic_memory/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
|
|
2
2
|
|
|
3
3
|
# Package version - updated by release automation
|
|
4
|
-
__version__ = "0.23.
|
|
4
|
+
__version__ = "0.23.2"
|
|
5
5
|
|
|
6
6
|
# API version for FastAPI - independent of package version
|
|
7
7
|
__api_version__ = "v2"
|
|
@@ -60,6 +60,7 @@ from basic_memory.runtime.storage import (
|
|
|
60
60
|
)
|
|
61
61
|
from basic_memory.schemas.base import Entity as EntitySchema
|
|
62
62
|
from basic_memory.schemas.request import EditEntityRequest
|
|
63
|
+
from basic_memory.utils import resolve_directory_casing
|
|
63
64
|
|
|
64
65
|
type AcceptedNoteMutationChange = RuntimeAcceptedNoteChange[RuntimeNoteContentResponsePayload]
|
|
65
66
|
type AcceptedNoteMutationUserProfileId = UUID
|
|
@@ -233,6 +234,11 @@ class AcceptedNoteMutationEntityRepository(Protocol):
|
|
|
233
234
|
load_relations: bool = False,
|
|
234
235
|
) -> Entity | None: ...
|
|
235
236
|
|
|
237
|
+
async def get_distinct_directories(
|
|
238
|
+
self,
|
|
239
|
+
session: AsyncSession,
|
|
240
|
+
) -> list[str]: ...
|
|
241
|
+
|
|
236
242
|
|
|
237
243
|
class AcceptedNoteMutationNoteContentRepository(Protocol):
|
|
238
244
|
"""note_content lookup capability for accepted-note mutations."""
|
|
@@ -379,6 +385,58 @@ async def resolve_accepted_note_source_checksum(
|
|
|
379
385
|
)
|
|
380
386
|
|
|
381
387
|
|
|
388
|
+
async def resolve_accepted_note_directory(
|
|
389
|
+
session: AsyncSession,
|
|
390
|
+
*,
|
|
391
|
+
project_id: ProjectId,
|
|
392
|
+
directory: str,
|
|
393
|
+
dependencies: AcceptedNoteMutationDependencies,
|
|
394
|
+
) -> str:
|
|
395
|
+
"""Resolve a requested note directory against existing folder casing (#1326).
|
|
396
|
+
|
|
397
|
+
Trigger: the requested directory is not an existing folder but matches
|
|
398
|
+
exactly one existing folder case-insensitively.
|
|
399
|
+
Why: LLM callers guess plausible casing ("schemas" beside an existing
|
|
400
|
+
"Schemas/"); on case-sensitive storage (cloud object storage) the guess
|
|
401
|
+
silently creates a case-duplicate sibling folder. Folders are derived
|
|
402
|
+
from indexed entity file paths in the DB, never by probing storage, so
|
|
403
|
+
local and cloud runtimes resolve identically.
|
|
404
|
+
Outcome: a unique case-insensitive match adopts the existing folder's
|
|
405
|
+
casing; exact matches, unknown folders, and ambiguous case-variant
|
|
406
|
+
siblings keep the requested casing unchanged.
|
|
407
|
+
"""
|
|
408
|
+
if not directory:
|
|
409
|
+
return directory
|
|
410
|
+
entity_repository = dependencies.lookup_repositories.entity_repository(project_id)
|
|
411
|
+
existing_directories = await entity_repository.get_distinct_directories(session)
|
|
412
|
+
return resolve_directory_casing(directory, existing_directories)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
async def resolve_accepted_note_schema_directory(
|
|
416
|
+
session: AsyncSession,
|
|
417
|
+
*,
|
|
418
|
+
project_id: ProjectId,
|
|
419
|
+
data: EntitySchema,
|
|
420
|
+
dependencies: AcceptedNoteMutationDependencies,
|
|
421
|
+
) -> EntitySchema:
|
|
422
|
+
"""Return the write schema with its directory resolved to existing casing.
|
|
423
|
+
|
|
424
|
+
The schema's ``file_path`` is computed from ``directory``, so adjusting the
|
|
425
|
+
directory redirects the conflict lookup, preparation, and permalink
|
|
426
|
+
resolution downstream. The caller's schema is never mutated: a resolved
|
|
427
|
+
directory yields a copy so route-owned request data stays as received.
|
|
428
|
+
"""
|
|
429
|
+
resolved_directory = await resolve_accepted_note_directory(
|
|
430
|
+
session,
|
|
431
|
+
project_id=project_id,
|
|
432
|
+
directory=data.directory,
|
|
433
|
+
dependencies=dependencies,
|
|
434
|
+
)
|
|
435
|
+
if resolved_directory == data.directory:
|
|
436
|
+
return data
|
|
437
|
+
return data.model_copy(update={"directory": resolved_directory})
|
|
438
|
+
|
|
439
|
+
|
|
382
440
|
async def run_accepted_note_create(
|
|
383
441
|
session: AsyncSession,
|
|
384
442
|
*,
|
|
@@ -501,10 +559,16 @@ async def _run_accepted_note_create(
|
|
|
501
559
|
dependencies=dependencies,
|
|
502
560
|
)
|
|
503
561
|
|
|
562
|
+
data = await resolve_accepted_note_schema_directory(
|
|
563
|
+
session,
|
|
564
|
+
project_id=project.id,
|
|
565
|
+
data=request.data,
|
|
566
|
+
dependencies=dependencies,
|
|
567
|
+
)
|
|
504
568
|
entity_repository = dependencies.lookup_repositories.entity_repository(project.id)
|
|
505
569
|
conflicting_entity = await entity_repository.get_by_file_path(
|
|
506
570
|
session,
|
|
507
|
-
|
|
571
|
+
data.file_path,
|
|
508
572
|
load_relations=False,
|
|
509
573
|
)
|
|
510
574
|
reject_accepted_note_file_path_conflict(
|
|
@@ -515,7 +579,7 @@ async def _run_accepted_note_create(
|
|
|
515
579
|
preparer = dependencies.preparer_factory.create_note_preparer(project)
|
|
516
580
|
prepared_write = await prepare_create_or_reject(
|
|
517
581
|
preparer,
|
|
518
|
-
|
|
582
|
+
data,
|
|
519
583
|
check_storage_exists=dependencies.verify_storage_absent_on_create,
|
|
520
584
|
session=session,
|
|
521
585
|
)
|
|
@@ -579,10 +643,26 @@ async def _run_accepted_note_update(
|
|
|
579
643
|
existing_file_path = entity.file_path if entity is not None else None
|
|
580
644
|
vacated_source: tuple[RuntimeFilePath, RuntimeFileChecksum | None] | None = None
|
|
581
645
|
|
|
646
|
+
# Trigger: the addressed entity already lives in the exact requested directory.
|
|
647
|
+
# Why: casing resolution scans every distinct entity file_path in the project,
|
|
648
|
+
# and content-only PUTs (e.g. repeated collaboration-relay saves of the
|
|
649
|
+
# same note) are the hot path where the directory cannot change; an exact
|
|
650
|
+
# match would resolve to itself anyway because exact match always wins.
|
|
651
|
+
# Outcome: only case-variant or relocating PUTs pay for the directory scan.
|
|
652
|
+
current_directory = existing_file_path.rpartition("/")[0] if existing_file_path else None
|
|
653
|
+
if current_directory == request.data.directory:
|
|
654
|
+
data = request.data
|
|
655
|
+
else:
|
|
656
|
+
data = await resolve_accepted_note_schema_directory(
|
|
657
|
+
session,
|
|
658
|
+
project_id=project.id,
|
|
659
|
+
data=request.data,
|
|
660
|
+
dependencies=dependencies,
|
|
661
|
+
)
|
|
582
662
|
await reject_conflicting_accepted_note_file_path(
|
|
583
663
|
session,
|
|
584
664
|
project_id=project.id,
|
|
585
|
-
file_path=
|
|
665
|
+
file_path=data.file_path,
|
|
586
666
|
allowed_entity_external_id=request.entity_external_id,
|
|
587
667
|
dependencies=dependencies,
|
|
588
668
|
)
|
|
@@ -600,7 +680,7 @@ async def _run_accepted_note_update(
|
|
|
600
680
|
reject_stale_base_checksum(current_db_checksum=None)
|
|
601
681
|
prepared_write = await prepare_create_or_reject(
|
|
602
682
|
preparer,
|
|
603
|
-
|
|
683
|
+
data,
|
|
604
684
|
check_storage_exists=dependencies.verify_storage_absent_on_create,
|
|
605
685
|
session=session,
|
|
606
686
|
)
|
|
@@ -630,7 +710,7 @@ async def _run_accepted_note_update(
|
|
|
630
710
|
try:
|
|
631
711
|
await preparer.verify_move_destination_absent(
|
|
632
712
|
source_file_path=entity.file_path,
|
|
633
|
-
destination_file_path=
|
|
713
|
+
destination_file_path=data.file_path,
|
|
634
714
|
)
|
|
635
715
|
except EntityAlreadyExistsError as error:
|
|
636
716
|
reject_accepted_note_mutation(AcceptedNoteMutationRejectKind.conflict, str(error))
|
|
@@ -644,7 +724,7 @@ async def _run_accepted_note_update(
|
|
|
644
724
|
# A PUT replacement may also rename the note. Capture the exact source bytes before
|
|
645
725
|
# persistence mutates the entity and note_content to the destination version so delayed
|
|
646
726
|
# cleanup cannot let a later project index recreate the old path as a ghost.
|
|
647
|
-
if
|
|
727
|
+
if data.file_path != entity.file_path:
|
|
648
728
|
vacated_source = (
|
|
649
729
|
entity.file_path,
|
|
650
730
|
await resolve_accepted_note_source_checksum(
|
|
@@ -693,7 +773,7 @@ async def _run_accepted_note_update(
|
|
|
693
773
|
preparer,
|
|
694
774
|
session,
|
|
695
775
|
entity=entity,
|
|
696
|
-
data=
|
|
776
|
+
data=data,
|
|
697
777
|
current_note_content=current_note_content,
|
|
698
778
|
user_profile_value=user_profile_value,
|
|
699
779
|
)
|
|
@@ -825,6 +905,18 @@ async def _run_accepted_note_move(
|
|
|
825
905
|
dependencies=dependencies,
|
|
826
906
|
)
|
|
827
907
|
existing_file_path = entity.file_path
|
|
908
|
+
# The destination filename keeps its requested casing; only the parent
|
|
909
|
+
# directory resolves against existing folders (issue #1326). The path is
|
|
910
|
+
# posix-normalized above, so rpartition splits directory from filename.
|
|
911
|
+
destination_directory, _, destination_filename = accepted_file_path.rpartition("/")
|
|
912
|
+
resolved_directory = await resolve_accepted_note_directory(
|
|
913
|
+
session,
|
|
914
|
+
project_id=project.id,
|
|
915
|
+
directory=destination_directory,
|
|
916
|
+
dependencies=dependencies,
|
|
917
|
+
)
|
|
918
|
+
if resolved_directory != destination_directory:
|
|
919
|
+
accepted_file_path = f"{resolved_directory}/{destination_filename}"
|
|
828
920
|
# Same-path moves fail fast everywhere by decision (2026-07-14): cloud's
|
|
829
921
|
# pre-unification route returned a 200 no-op, local rejected — the unified
|
|
830
922
|
# runner keeps the rejection so a mistaken identity move surfaces instead
|
|
@@ -947,9 +947,20 @@ move_note("{identifier}", destination_folder="notes")
|
|
|
947
947
|
# the up-front detection: any divergence the caller did not ask for
|
|
948
948
|
# surfaces as a failure rather than a fake success.
|
|
949
949
|
# Outcome: report failure with the actual landing path instead of "✅".
|
|
950
|
+
# A case-only difference in the PARENT directories is the server resolving
|
|
951
|
+
# the destination folder to an existing folder's casing (#1326), not a
|
|
952
|
+
# boundary degradation — the success message below reports the actual
|
|
953
|
+
# landing path either way. The server always preserves the requested
|
|
954
|
+
# filename verbatim, so a basename divergence (even case-only) still
|
|
955
|
+
# reports the honest failure.
|
|
950
956
|
normalized_requested = PureWindowsPath(destination_path).as_posix().strip("/")
|
|
951
957
|
normalized_actual = PureWindowsPath(result.file_path).as_posix().strip("/")
|
|
952
|
-
|
|
958
|
+
requested_parent, _, requested_name = normalized_requested.rpartition("/")
|
|
959
|
+
actual_parent, _, actual_name = normalized_actual.rpartition("/")
|
|
960
|
+
diverged = normalized_actual != normalized_requested and not (
|
|
961
|
+
actual_name == requested_name and actual_parent.lower() == requested_parent.lower()
|
|
962
|
+
)
|
|
963
|
+
if diverged:
|
|
953
964
|
logger.warning(
|
|
954
965
|
f"Move outcome diverged from intent: requested={destination_path} "
|
|
955
966
|
f"actual={result.file_path}"
|
basic_memory/utils.py
CHANGED
|
@@ -776,6 +776,52 @@ def detect_potential_file_conflicts(file_path: str, existing_paths: List[str]) -
|
|
|
776
776
|
return conflicts
|
|
777
777
|
|
|
778
778
|
|
|
779
|
+
def resolve_directory_casing(directory: str, existing_directories: List[str]) -> str:
|
|
780
|
+
"""Resolve a requested directory to the casing of existing folders (#1326).
|
|
781
|
+
|
|
782
|
+
LLM callers guess plausible-but-wrong casing ("schemas" beside an existing
|
|
783
|
+
"Schemas/"), and case-sensitive storage backends then grow case-duplicate
|
|
784
|
+
sibling folders. Each path segment resolves against the folders that exist
|
|
785
|
+
under the already-resolved parent:
|
|
786
|
+
|
|
787
|
+
- Exact match wins: the requested segment casing is kept unchanged.
|
|
788
|
+
- Unique case-insensitive match: the existing folder's casing is adopted.
|
|
789
|
+
- No match: the segment is kept as given (a new folder).
|
|
790
|
+
- Multiple case-variant folders exist: ambiguous, the segment is kept as
|
|
791
|
+
given (today's exact behavior).
|
|
792
|
+
|
|
793
|
+
Args:
|
|
794
|
+
directory: Requested directory path relative to the project root
|
|
795
|
+
("" means the root itself).
|
|
796
|
+
existing_directories: Known project folders derived from indexed entity
|
|
797
|
+
file paths (see EntityRepository.get_distinct_directories).
|
|
798
|
+
|
|
799
|
+
Returns:
|
|
800
|
+
The directory path with each segment resolved to existing casing.
|
|
801
|
+
"""
|
|
802
|
+
if not directory:
|
|
803
|
+
return directory
|
|
804
|
+
|
|
805
|
+
existing = set(existing_directories)
|
|
806
|
+
resolved_prefix = ""
|
|
807
|
+
for segment in directory.split("/"):
|
|
808
|
+
candidate = f"{resolved_prefix}/{segment}" if resolved_prefix else segment
|
|
809
|
+
if candidate in existing:
|
|
810
|
+
resolved_prefix = candidate
|
|
811
|
+
continue
|
|
812
|
+
# Candidates are constrained to children of the already-resolved parent:
|
|
813
|
+
# once a parent segment stays ambiguous (kept as requested), a deeper
|
|
814
|
+
# existing folder under a *different* parent casing must not be spliced in.
|
|
815
|
+
matches = [
|
|
816
|
+
existing_directory
|
|
817
|
+
for existing_directory in existing
|
|
818
|
+
if existing_directory.lower() == candidate.lower()
|
|
819
|
+
and existing_directory.rpartition("/")[0] == resolved_prefix
|
|
820
|
+
]
|
|
821
|
+
resolved_prefix = matches[0] if len(matches) == 1 else candidate
|
|
822
|
+
return resolved_prefix
|
|
823
|
+
|
|
824
|
+
|
|
779
825
|
def valid_project_path_value(path: str):
|
|
780
826
|
"""Ensure project path is valid."""
|
|
781
827
|
# Allow empty strings as they resolve to the project root
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: basic-memory
|
|
3
|
-
Version: 0.23.
|
|
3
|
+
Version: 0.23.2
|
|
4
4
|
Summary: Local-first knowledge management combining Zettelkasten with knowledge graphs
|
|
5
5
|
Project-URL: Homepage, https://github.com/basicmachines-co/basic-memory
|
|
6
6
|
Project-URL: Repository, https://github.com/basicmachines-co/basic-memory
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
basic_memory/__init__.py,sha256=
|
|
1
|
+
basic_memory/__init__.py,sha256=dtmjVy3Q4_DYddSDwS9XOq3okErGMfYtVZ-_1s5p2zQ,256
|
|
2
2
|
basic_memory/config.py,sha256=gPw1piQZJANYCF-TkpEpc9-vD7-lHnSez8HQidGh5qk,12080
|
|
3
3
|
basic_memory/config_logging.py,sha256=keqWv5xPOP6fjh1LDQmKMDyduHTYAhr6Y1EKiiYVDnc,1857
|
|
4
4
|
basic_memory/config_migrations.py,sha256=OO5hwzs3x5EEix-jNMeETrJQwMx0l2iHUywXRLMjzFA,3582
|
|
@@ -10,7 +10,7 @@ basic_memory/migration_loop.py,sha256=KGhuCLgrgwW21GAGT9cOczbtRlM4mGi7aUBjG1shQ8
|
|
|
10
10
|
basic_memory/project_resolver.py,sha256=vHWHw1X9_1IqtX9Dy5yNXqNRas3wcrpQJVKxJuj4qWg,3801
|
|
11
11
|
basic_memory/redaction.py,sha256=tYolNeRQdOfcELD9sGtzQbB5dS3odP9PAnzipqmMl6M,4804
|
|
12
12
|
basic_memory/telemetry.py,sha256=pAkPgbAOmivZDkCAW0S2CKSNw4AqBfMckdsbb2EipcQ,1721
|
|
13
|
-
basic_memory/utils.py,sha256=
|
|
13
|
+
basic_memory/utils.py,sha256=bXlhv3qHOO_1Lb_2gUspjOZ3H1-qgreWl_n5eR6_Jxg,35426
|
|
14
14
|
basic_memory/workspace_context.py,sha256=v-u4jO8VFB1WeFZ1UpzZnbf9ph71vvz1OMibpAtrtQI,3648
|
|
15
15
|
basic_memory/alembic/alembic.ini,sha256=IEZsnF8CbbZnkwBr67LzKKNobHuzTaQNUvM8Psop5xc,3733
|
|
16
16
|
basic_memory/alembic/env.py,sha256=ZfUWoJeIJKTCsOSvz6mty0c570k2k4MF9cvi7T4sj1g,9581
|
|
@@ -160,7 +160,7 @@ basic_memory/index/watch_coordinator.py,sha256=Nif22CFD8romuHTdPTTW1LHyKvy2UyUbv
|
|
|
160
160
|
basic_memory/index/watch_service.py,sha256=R4uOFjLVTQjSYo1fE6kOUcnnq3o7JPIlYQFZf0PjXCI,13885
|
|
161
161
|
basic_memory/indexing/__init__.py,sha256=YdS2QwyyQ7fPxvUr2wkubztu2aolAyuQy9Y0FbH0BH8,236
|
|
162
162
|
basic_memory/indexing/accepted_note_enqueue_runner.py,sha256=v0BQTdKFOnhwrvls4QBqF2fIMejRkeFWyQpimOdoUt4,7540
|
|
163
|
-
basic_memory/indexing/accepted_note_mutation_runner.py,sha256=
|
|
163
|
+
basic_memory/indexing/accepted_note_mutation_runner.py,sha256=BTaMDcE2GpF4S5uhF2tHzxFbBOy1fOgUVjmB2_Wyj0M,47564
|
|
164
164
|
basic_memory/indexing/accepted_note_search.py,sha256=fNcuxOKJ_zVM6FVlZQ8-OGjG3E3NXGJwkTuvO8aF0PU,4748
|
|
165
165
|
basic_memory/indexing/accepted_note_write_runner.py,sha256=ZvQlX47C8oD-VTCBWSEDCzTE3x938IPetZQp48_H_PM,25759
|
|
166
166
|
basic_memory/indexing/batch_indexer.py,sha256=Yy9CcZcHzUsLG24aAsaUVrCjkDFemAQeagDmr8IFV_s,37976
|
|
@@ -245,7 +245,7 @@ basic_memory/mcp/tools/chatgpt_tools.py,sha256=BpBLzT4zGe7TMLoFWbbnTq9lwcVS_1_F1
|
|
|
245
245
|
basic_memory/mcp/tools/delete_note.py,sha256=RPFXo1QxxMV6PFw4Uf9eXdOBUpn_m8rJkVr4pFSVflI,20822
|
|
246
246
|
basic_memory/mcp/tools/edit_note.py,sha256=owbnPnncW__O0cBAR0sZcLo37b4tTFtFtNYatTVhiGA,44695
|
|
247
247
|
basic_memory/mcp/tools/list_directory.py,sha256=YQT4AmEfbmyHOWGYlU0s-htNBvZBzonA7gZQ-btiNkg,10367
|
|
248
|
-
basic_memory/mcp/tools/move_note.py,sha256=
|
|
248
|
+
basic_memory/mcp/tools/move_note.py,sha256=DzsB7sQU0Q-KlVGELN3kb9ivXfbqEiVNfFTNzxVFoqc,45139
|
|
249
249
|
basic_memory/mcp/tools/project_management.py,sha256=B2MCcX7PCjAobfJkDCI5KiJkT4AVE43yNXGpZoICSpU,36341
|
|
250
250
|
basic_memory/mcp/tools/read_content.py,sha256=eaGy4bV1fF7KAoyP9QgautDsaJUR2u8GOqx0CG0p5Ug,12970
|
|
251
251
|
basic_memory/mcp/tools/read_note.py,sha256=BS40PBOSHwf7LYdfZK1OiNhsJvOSccen3V81UR6AG_0,25636
|
|
@@ -391,8 +391,8 @@ basic_memory/services/search_service.py,sha256=8A1vXo5IIpKXL2gxEsCEP_tcKyRcnz4R5
|
|
|
391
391
|
basic_memory/services/service.py,sha256=V-d_8gOV07zGIQDpL-Ksqs3ZN9l3qf3HZOK1f_YNTag,336
|
|
392
392
|
basic_memory/templates/prompts/continue_conversation.hbs,sha256=trrDHSXA5S0JCbInMoUJL04xvCGRB_ku1RHNQHtl6ZI,3076
|
|
393
393
|
basic_memory/templates/prompts/search.hbs,sha256=H1cCIsHKp4VC1GrH2KeUB8pGe5vXFPqb2VPotypmeCA,3098
|
|
394
|
-
basic_memory-0.23.
|
|
395
|
-
basic_memory-0.23.
|
|
396
|
-
basic_memory-0.23.
|
|
397
|
-
basic_memory-0.23.
|
|
398
|
-
basic_memory-0.23.
|
|
394
|
+
basic_memory-0.23.2.dist-info/METADATA,sha256=wy0hbehVXk8xFwvXvdqv-oqzkmO8KyEbWzgqbZvP-Ps,26842
|
|
395
|
+
basic_memory-0.23.2.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
396
|
+
basic_memory-0.23.2.dist-info/entry_points.txt,sha256=wvE2mRF6-Pg4weIYcfQ-86NOLZD4WJg7F7TIsRVFLb8,90
|
|
397
|
+
basic_memory-0.23.2.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
398
|
+
basic_memory-0.23.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|