chesscalc 2.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.
- chesscalc/__init__.py +71 -0
- chesscalc/apsw/__init__.py +5 -0
- chesscalc/apsw/database.py +46 -0
- chesscalc/apsw/database_du.py +35 -0
- chesscalc/basecore/__init__.py +5 -0
- chesscalc/basecore/database.py +66 -0
- chesscalc/berkeleydb/__init__.py +5 -0
- chesscalc/berkeleydb/database.py +73 -0
- chesscalc/berkeleydb/database_du.py +63 -0
- chesscalc/calculate.py +79 -0
- chesscalc/core/__init__.py +5 -0
- chesscalc/core/apply_identities.py +219 -0
- chesscalc/core/calculate.py +838 -0
- chesscalc/core/configuration.py +24 -0
- chesscalc/core/constants.py +98 -0
- chesscalc/core/delete_games.py +725 -0
- chesscalc/core/eventrecord.py +305 -0
- chesscalc/core/export.py +288 -0
- chesscalc/core/filespec.py +391 -0
- chesscalc/core/gamerecord.py +741 -0
- chesscalc/core/identify_event.py +124 -0
- chesscalc/core/identify_item.py +406 -0
- chesscalc/core/identify_mode.py +108 -0
- chesscalc/core/identify_person.py +769 -0
- chesscalc/core/identify_playertype.py +107 -0
- chesscalc/core/identify_termination.py +108 -0
- chesscalc/core/identify_timecontrol.py +101 -0
- chesscalc/core/identity.py +285 -0
- chesscalc/core/mirror_identities.py +220 -0
- chesscalc/core/moderecord.py +265 -0
- chesscalc/core/name_lookup.py +236 -0
- chesscalc/core/person.py +81 -0
- chesscalc/core/pgnheaders.py +138 -0
- chesscalc/core/playerrecord.py +444 -0
- chesscalc/core/playertyperecord.py +270 -0
- chesscalc/core/population.py +179 -0
- chesscalc/core/selectorrecord.py +107 -0
- chesscalc/core/tab_from_selection.py +176 -0
- chesscalc/core/terminationrecord.py +274 -0
- chesscalc/core/timecontrolrecord.py +269 -0
- chesscalc/core/update_rule.py +155 -0
- chesscalc/core/utilities.py +37 -0
- chesscalc/db/__init__.py +5 -0
- chesscalc/db/database.py +73 -0
- chesscalc/db/database_du.py +63 -0
- chesscalc/db_tkinter/__init__.py +5 -0
- chesscalc/db_tkinter/database.py +61 -0
- chesscalc/db_tkinter/database_du.py +45 -0
- chesscalc/dpt/__init__.py +5 -0
- chesscalc/dpt/database.py +81 -0
- chesscalc/dpt/database_du.py +31 -0
- chesscalc/dpt/dptnofistat.py +65 -0
- chesscalc/gui/__init__.py +5 -0
- chesscalc/gui/calculator.py +2728 -0
- chesscalc/gui/events.py +386 -0
- chesscalc/gui/eventsgrid.py +32 -0
- chesscalc/gui/eventspec.py +79 -0
- chesscalc/gui/eventsrow.py +77 -0
- chesscalc/gui/games.py +119 -0
- chesscalc/gui/gamesgrid.py +32 -0
- chesscalc/gui/gamesrow.py +126 -0
- chesscalc/gui/gridlocator.py +65 -0
- chesscalc/gui/help_.py +30 -0
- chesscalc/gui/modes.py +269 -0
- chesscalc/gui/modesgrid.py +32 -0
- chesscalc/gui/modesrow.py +68 -0
- chesscalc/gui/performancedu.py +998 -0
- chesscalc/gui/persons.py +403 -0
- chesscalc/gui/personsgrid.py +32 -0
- chesscalc/gui/personsrow.py +94 -0
- chesscalc/gui/players.py +330 -0
- chesscalc/gui/playersgrid.py +32 -0
- chesscalc/gui/playersrow.py +88 -0
- chesscalc/gui/playertypes.py +269 -0
- chesscalc/gui/playertypesgrid.py +32 -0
- chesscalc/gui/playertypesrow.py +67 -0
- chesscalc/gui/report.py +56 -0
- chesscalc/gui/reportapply.py +457 -0
- chesscalc/gui/reportmirror.py +252 -0
- chesscalc/gui/reportremovepgn.py +23 -0
- chesscalc/gui/rule.py +1489 -0
- chesscalc/gui/ruleedit.py +55 -0
- chesscalc/gui/ruleinsert.py +38 -0
- chesscalc/gui/ruleshow.py +75 -0
- chesscalc/gui/selectors.py +70 -0
- chesscalc/gui/selectorsgrid.py +32 -0
- chesscalc/gui/selectorsrow.py +85 -0
- chesscalc/gui/terminations.py +271 -0
- chesscalc/gui/terminationsgrid.py +32 -0
- chesscalc/gui/terminationsrow.py +68 -0
- chesscalc/gui/timecontrols.py +265 -0
- chesscalc/gui/timecontrolsgrid.py +32 -0
- chesscalc/gui/timecontrolsrow.py +68 -0
- chesscalc/help_/__init__.py +30 -0
- chesscalc/help_/aboutcalculator.txt +16 -0
- chesscalc/help_/calculator.txt +110 -0
- chesscalc/legacy/__init__.py +5 -0
- chesscalc/legacy/core/__init__.py +5 -0
- chesscalc/legacy/core/performances.py +1000 -0
- chesscalc/legacy/gui/__init__.py +5 -0
- chesscalc/legacy/gui/performance.py +279 -0
- chesscalc/legacy/gui/population.py +533 -0
- chesscalc/legacy/gui/prediction.py +419 -0
- chesscalc/lmdb/__init__.py +5 -0
- chesscalc/lmdb/database.py +56 -0
- chesscalc/lmdb/database_du.py +64 -0
- chesscalc/shared/alldu.py +815 -0
- chesscalc/shared/dbdu.py +44 -0
- chesscalc/shared/litedu.py +83 -0
- chesscalc/shared/rundu.py +93 -0
- chesscalc/shared/task.py +41 -0
- chesscalc/sqlite/__init__.py +5 -0
- chesscalc/sqlite/database.py +50 -0
- chesscalc/sqlite/database_du.py +35 -0
- chesscalc/unqlite/__init__.py +5 -0
- chesscalc/unqlite/database.py +46 -0
- chesscalc/unqlite/database_du.py +35 -0
- chesscalc/vedis/__init__.py +5 -0
- chesscalc/vedis/database.py +46 -0
- chesscalc/vedis/database_du.py +35 -0
- chesscalc-2.0.0.dist-info/LICENCE +15 -0
- chesscalc-2.0.0.dist-info/METADATA +109 -0
- chesscalc-2.0.0.dist-info/RECORD +125 -0
- chesscalc-2.0.0.dist-info/WHEEL +5 -0
- chesscalc-2.0.0.dist-info/top_level.txt +1 -0
chesscalc/__init__.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# __init__.py
|
|
2
|
+
# Copyright 2008 Roger Marsh
|
|
3
|
+
# Licence: See LICENCE (BSD licence)
|
|
4
|
+
|
|
5
|
+
"""Calculate player's performances on a set of games.
|
|
6
|
+
|
|
7
|
+
Performances are calculated by iteration where each player starts with 0 as
|
|
8
|
+
their performance. The scale used is +50 for a win, -50 for a loss, and 0 for
|
|
9
|
+
a draw, relative to the opponents performance. English Chess Federation (ECF)
|
|
10
|
+
grades have the same scale.
|
|
11
|
+
|
|
12
|
+
The performances are not grades because the results of calculations in previous
|
|
13
|
+
runs are not used in this calculation.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
import os
|
|
17
|
+
import datetime
|
|
18
|
+
import traceback
|
|
19
|
+
|
|
20
|
+
import solentware_base.core.constants as sb_c_constants
|
|
21
|
+
|
|
22
|
+
APPLICATION_NAME = "ChessPerfCalc"
|
|
23
|
+
ERROR_LOG = "ErrorLog"
|
|
24
|
+
REPORT_DIRECTORY = "_reports"
|
|
25
|
+
|
|
26
|
+
# Map database module names to application module
|
|
27
|
+
APPLICATION_DATABASE_MODULE = {
|
|
28
|
+
sb_c_constants.BERKELEYDB_MODULE: __name__ + ".berkeleydb.database",
|
|
29
|
+
sb_c_constants.BSDDB3_MODULE: __name__ + ".db.database",
|
|
30
|
+
sb_c_constants.SQLITE3_MODULE: __name__ + ".sqlite.database",
|
|
31
|
+
sb_c_constants.APSW_MODULE: __name__ + ".apsw.database",
|
|
32
|
+
sb_c_constants.DPT_MODULE: __name__ + ".dpt.database",
|
|
33
|
+
sb_c_constants.UNQLITE_MODULE: __name__ + ".unqlite.database",
|
|
34
|
+
sb_c_constants.VEDIS_MODULE: __name__ + ".vedis.database",
|
|
35
|
+
sb_c_constants.DB_TCL_MODULE: __name__ + ".db_tkinter.database",
|
|
36
|
+
sb_c_constants.LMDB_MODULE: __name__ + ".lmdb.database",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
del sb_c_constants
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def write_error_to_log(directory):
|
|
43
|
+
"""Write the exception to the error log with a time stamp.
|
|
44
|
+
|
|
45
|
+
Consider using the ExceptionHandler.report_exception(...) method if
|
|
46
|
+
the write_error_to_log() call is in an instance of a subclass of the
|
|
47
|
+
solentware_bind.gui.exceptionhandler.ExceptionHandler class.
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
with open(
|
|
51
|
+
os.path.join(directory, ERROR_LOG),
|
|
52
|
+
"a",
|
|
53
|
+
encoding="utf-8",
|
|
54
|
+
) as file:
|
|
55
|
+
file.write(
|
|
56
|
+
"".join(
|
|
57
|
+
(
|
|
58
|
+
"\n\n\n",
|
|
59
|
+
" ".join(
|
|
60
|
+
(
|
|
61
|
+
APPLICATION_NAME,
|
|
62
|
+
"exception report at",
|
|
63
|
+
datetime.datetime.now().isoformat(),
|
|
64
|
+
)
|
|
65
|
+
),
|
|
66
|
+
"\n\n",
|
|
67
|
+
traceback.format_exc(),
|
|
68
|
+
"\n\n",
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# database.py
|
|
2
|
+
# Copyright 2023 Roger Marsh
|
|
3
|
+
# Licence: See LICENCE (BSD licence)
|
|
4
|
+
|
|
5
|
+
"""Chess Performance Calculation database using Sqlite3 via apsw."""
|
|
6
|
+
|
|
7
|
+
from solentware_base import apsw_database
|
|
8
|
+
|
|
9
|
+
from ..core.filespec import FileSpec
|
|
10
|
+
from ..basecore import database
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Database(database.Database, apsw_database.Database):
|
|
14
|
+
"""Methods and data structures to create, open, and close database."""
|
|
15
|
+
|
|
16
|
+
_deferred_update_process = "chesscalc.apsw.database_du"
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
sqlite3file,
|
|
21
|
+
use_specification_items=None,
|
|
22
|
+
dpt_records=None,
|
|
23
|
+
**kargs,
|
|
24
|
+
):
|
|
25
|
+
"""Define database specification then delegate."""
|
|
26
|
+
names = FileSpec(
|
|
27
|
+
use_specification_items=use_specification_items,
|
|
28
|
+
dpt_records=dpt_records,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
super().__init__(
|
|
32
|
+
names,
|
|
33
|
+
sqlite3file,
|
|
34
|
+
use_specification_items=use_specification_items,
|
|
35
|
+
**kargs,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def delete_database(self):
|
|
39
|
+
"""Close and delete the open chess results database."""
|
|
40
|
+
return super().delete_database(
|
|
41
|
+
(
|
|
42
|
+
self.database_file,
|
|
43
|
+
"-".join((self.database_file, "lock")),
|
|
44
|
+
"-".join((self.database_file, "journal")),
|
|
45
|
+
)
|
|
46
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# database_du.py
|
|
2
|
+
# Copyright 2023 Roger Marsh
|
|
3
|
+
# Licence: See LICENCE (BSD licence)
|
|
4
|
+
|
|
5
|
+
"""Chess performance database deferred update using Sqlite 3 via apsw."""
|
|
6
|
+
|
|
7
|
+
from solentware_base import apswdu_database
|
|
8
|
+
from solentware_base import apsw_database
|
|
9
|
+
|
|
10
|
+
from ..shared import litedu
|
|
11
|
+
from ..shared import alldu
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ApswDatabaseduError(Exception):
|
|
15
|
+
"""Exception class for apsw.database_du module."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Database(alldu.Alldu, litedu.Litedu, apswdu_database.Database):
|
|
19
|
+
"""Provide custom deferred update for chess performance database."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, sqlite3file, **kargs):
|
|
22
|
+
"""Delegate with ApswDatabaseduError as exception class."""
|
|
23
|
+
super().__init__(sqlite3file, ApswDatabaseduError, **kargs)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DatabaseSU(alldu.Alldu, litedu.Litedu, apsw_database.Database):
|
|
27
|
+
"""Provide custom deferred update for chess performance database."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, sqlite3file, **kargs):
|
|
30
|
+
"""Delegate with ApswDatabaseduError as exception class."""
|
|
31
|
+
super().__init__(sqlite3file, ApswDatabaseduError, **kargs)
|
|
32
|
+
|
|
33
|
+
# Class structure implies this is not an override at present.
|
|
34
|
+
def deferred_update_housekeeping(self):
|
|
35
|
+
"""Override to do nothing."""
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# database.py
|
|
2
|
+
# Copyright 2023 Roger Marsh
|
|
3
|
+
# Licence: See LICENCE (BSD licence)
|
|
4
|
+
|
|
5
|
+
"""Database methods common to all database engine interfaces."""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
|
|
10
|
+
from .. import APPLICATION_NAME, ERROR_LOG, REPORT_DIRECTORY
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Database:
|
|
14
|
+
"""Provide methods common to all database engine interfaces."""
|
|
15
|
+
|
|
16
|
+
# Non *_du database actions are done in separate threads by default,
|
|
17
|
+
# not the main thread. Override with 'can_use_thread = False' in
|
|
18
|
+
# subclasses if necessary.
|
|
19
|
+
# For SQLite3 the apsw interface is happy with the default, but the
|
|
20
|
+
# sqlite3 interface gives a sqlite3.programmingerror exception.
|
|
21
|
+
# For Berkeley DB the bsddb3 and berkeleydb interfaces are happy with
|
|
22
|
+
# the default, but the tcl via tkinter interface gives a RuntimeError
|
|
23
|
+
# exception.
|
|
24
|
+
can_use_thread = True
|
|
25
|
+
|
|
26
|
+
def use_deferred_update_process(self):
|
|
27
|
+
"""Return path to deferred update module."""
|
|
28
|
+
return self._deferred_update_process
|
|
29
|
+
|
|
30
|
+
def open_database(self, files=None):
|
|
31
|
+
"""Return '' to fit behaviour of dpt version of this method."""
|
|
32
|
+
super().open_database(files=files)
|
|
33
|
+
return ""
|
|
34
|
+
|
|
35
|
+
def delete_database(self, names):
|
|
36
|
+
"""Delete database and return message about items not deleted."""
|
|
37
|
+
listnames = set(n for n in os.listdir(self.home_directory))
|
|
38
|
+
homenames = set(n for n in names if os.path.basename(n) in listnames)
|
|
39
|
+
for name in (ERROR_LOG, REPORT_DIRECTORY):
|
|
40
|
+
if name in listnames:
|
|
41
|
+
homenames.add(name)
|
|
42
|
+
if len(listnames - set(os.path.basename(h) for h in homenames)):
|
|
43
|
+
message = "".join(
|
|
44
|
+
(
|
|
45
|
+
"There is at least one file or folder in\n\n",
|
|
46
|
+
self.home_directory,
|
|
47
|
+
"\n\nwhich may not be part of the database. These items ",
|
|
48
|
+
"have not been deleted by ",
|
|
49
|
+
APPLICATION_NAME,
|
|
50
|
+
".",
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
else:
|
|
54
|
+
message = None
|
|
55
|
+
self.close_database()
|
|
56
|
+
for hnm in homenames:
|
|
57
|
+
hnm = os.path.join(self.home_directory, hnm)
|
|
58
|
+
if os.path.isdir(hnm):
|
|
59
|
+
shutil.rmtree(hnm, ignore_errors=True)
|
|
60
|
+
else:
|
|
61
|
+
os.remove(hnm)
|
|
62
|
+
try:
|
|
63
|
+
os.rmdir(self.home_directory)
|
|
64
|
+
except (FileNotFoundError, OSError):
|
|
65
|
+
pass
|
|
66
|
+
return message
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# database.py
|
|
2
|
+
# Copyright 2023 Roger Marsh
|
|
3
|
+
# Licence: See LICENCE (BSD licence)
|
|
4
|
+
|
|
5
|
+
"""Chess Performance Calculation database using Berkeley DB via berkeleydb."""
|
|
6
|
+
|
|
7
|
+
from berkeleydb.db import (
|
|
8
|
+
# DB_BTREE,
|
|
9
|
+
# DB_HASH,
|
|
10
|
+
# DB_RECNO,
|
|
11
|
+
# DB_DUPSORT,
|
|
12
|
+
DB_CREATE,
|
|
13
|
+
DB_RECOVER,
|
|
14
|
+
DB_INIT_MPOOL,
|
|
15
|
+
DB_INIT_LOCK,
|
|
16
|
+
DB_INIT_LOG,
|
|
17
|
+
DB_INIT_TXN,
|
|
18
|
+
DB_PRIVATE,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from solentware_base import berkeleydb_database
|
|
22
|
+
|
|
23
|
+
from ..core.filespec import FileSpec
|
|
24
|
+
from ..basecore import database
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Database(database.Database, berkeleydb_database.Database):
|
|
28
|
+
"""Methods and data structures to create, open, and close database."""
|
|
29
|
+
|
|
30
|
+
_deferred_update_process = "chesscalc.berkeleydb.database_du"
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
DBfile,
|
|
35
|
+
use_specification_items=None,
|
|
36
|
+
dpt_records=None,
|
|
37
|
+
**kargs,
|
|
38
|
+
):
|
|
39
|
+
"""Define database specification and environment then delegate."""
|
|
40
|
+
dbnames = FileSpec(
|
|
41
|
+
use_specification_items=use_specification_items,
|
|
42
|
+
dpt_records=dpt_records,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
environment = {
|
|
46
|
+
"flags": (
|
|
47
|
+
DB_CREATE
|
|
48
|
+
| DB_RECOVER
|
|
49
|
+
| DB_INIT_MPOOL
|
|
50
|
+
| DB_INIT_LOCK
|
|
51
|
+
| DB_INIT_LOG
|
|
52
|
+
| DB_INIT_TXN
|
|
53
|
+
| DB_PRIVATE
|
|
54
|
+
),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
super().__init__(
|
|
58
|
+
dbnames,
|
|
59
|
+
DBfile,
|
|
60
|
+
environment,
|
|
61
|
+
use_specification_items=use_specification_items,
|
|
62
|
+
**kargs,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def delete_database(self):
|
|
66
|
+
"""Close and delete the open chess results database."""
|
|
67
|
+
return super().delete_database(
|
|
68
|
+
(
|
|
69
|
+
self.database_file,
|
|
70
|
+
"-".join((self.database_file, "lock")),
|
|
71
|
+
self.dbenv.get_lg_dir().decode(),
|
|
72
|
+
)
|
|
73
|
+
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# database_du.py
|
|
2
|
+
# Copyright 2023 Roger Marsh
|
|
3
|
+
# Licence: See LICENCE (BSD licence)
|
|
4
|
+
|
|
5
|
+
"""Chess performance deferred update using Berkeley DB via berkeleydb."""
|
|
6
|
+
|
|
7
|
+
import berkeleydb.db
|
|
8
|
+
|
|
9
|
+
from solentware_base import berkeleydbdu_database
|
|
10
|
+
from solentware_base import berkeleydb_database
|
|
11
|
+
|
|
12
|
+
from ..shared import dbdu
|
|
13
|
+
from ..shared import alldu
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BerkeleydbDatabaseduError(Exception):
|
|
17
|
+
"""Exception class for berkeleydb.database_du module."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Database(alldu.Alldu, dbdu.Dbdu, berkeleydbdu_database.Database):
|
|
21
|
+
"""Provide custom deferred update for chess performance database."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, DBfile, **kargs):
|
|
24
|
+
"""Delegate with BerkeleydbDatabaseduError as exception class."""
|
|
25
|
+
super().__init__(
|
|
26
|
+
DBfile,
|
|
27
|
+
BerkeleydbDatabaseduError,
|
|
28
|
+
(
|
|
29
|
+
berkeleydb.db.DB_CREATE
|
|
30
|
+
| berkeleydb.db.DB_RECOVER
|
|
31
|
+
| berkeleydb.db.DB_INIT_MPOOL
|
|
32
|
+
| berkeleydb.db.DB_INIT_LOCK
|
|
33
|
+
| berkeleydb.db.DB_INIT_LOG
|
|
34
|
+
| berkeleydb.db.DB_INIT_TXN
|
|
35
|
+
| berkeleydb.db.DB_PRIVATE
|
|
36
|
+
),
|
|
37
|
+
**kargs
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DatabaseSU(alldu.Alldu, dbdu.Dbdu, berkeleydb_database.Database):
|
|
42
|
+
"""Provide custom deferred update for chess performance database."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, DBfile, **kargs):
|
|
45
|
+
"""Delegate with BerkeleydbDatabaseduError as exception class."""
|
|
46
|
+
super().__init__(
|
|
47
|
+
DBfile,
|
|
48
|
+
BerkeleydbDatabaseduError,
|
|
49
|
+
(
|
|
50
|
+
berkeleydb.db.DB_CREATE
|
|
51
|
+
| berkeleydb.db.DB_RECOVER
|
|
52
|
+
| berkeleydb.db.DB_INIT_MPOOL
|
|
53
|
+
| berkeleydb.db.DB_INIT_LOCK
|
|
54
|
+
| berkeleydb.db.DB_INIT_LOG
|
|
55
|
+
| berkeleydb.db.DB_INIT_TXN
|
|
56
|
+
| berkeleydb.db.DB_PRIVATE
|
|
57
|
+
),
|
|
58
|
+
**kargs
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Class structure implies this is not an override at present.
|
|
62
|
+
def deferred_update_housekeeping(self):
|
|
63
|
+
"""Override to do nothing."""
|
chesscalc/calculate.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# calculate.py
|
|
2
|
+
# Copyright 2012 Roger Marsh
|
|
3
|
+
# Licence: See LICENCE (BSD licence)
|
|
4
|
+
|
|
5
|
+
"""Calculate performances from a file of chess game results."""
|
|
6
|
+
|
|
7
|
+
if __name__ == "__main__":
|
|
8
|
+
import multiprocessing
|
|
9
|
+
|
|
10
|
+
from . import APPLICATION_NAME
|
|
11
|
+
|
|
12
|
+
multiprocessing.set_start_method("spawn")
|
|
13
|
+
del multiprocessing
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from solentware_misc.gui.startstop import (
|
|
17
|
+
start_application_exception,
|
|
18
|
+
stop_application,
|
|
19
|
+
application_exception,
|
|
20
|
+
)
|
|
21
|
+
except Exception as error:
|
|
22
|
+
import tkinter.messagebox
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
tkinter.messagebox.showerror(
|
|
26
|
+
title="Start Exception",
|
|
27
|
+
message=".\n\nThe reported exception is:\n\n".join(
|
|
28
|
+
(
|
|
29
|
+
" ".join(
|
|
30
|
+
(
|
|
31
|
+
"Unable to import",
|
|
32
|
+
"solentware_misc.gui.startstop module",
|
|
33
|
+
)
|
|
34
|
+
),
|
|
35
|
+
str(error),
|
|
36
|
+
)
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
except tkinter.TclError:
|
|
40
|
+
pass
|
|
41
|
+
raise SystemExit(
|
|
42
|
+
"Unable to import start application utilities"
|
|
43
|
+
) from error
|
|
44
|
+
try:
|
|
45
|
+
from .gui.calculator import Calculator
|
|
46
|
+
except Exception as error:
|
|
47
|
+
start_application_exception(
|
|
48
|
+
error, appname=APPLICATION_NAME, action="import"
|
|
49
|
+
)
|
|
50
|
+
raise SystemExit(
|
|
51
|
+
" import ".join(("Unable to", APPLICATION_NAME))
|
|
52
|
+
) from error
|
|
53
|
+
try:
|
|
54
|
+
app = Calculator()
|
|
55
|
+
except Exception as error:
|
|
56
|
+
start_application_exception(
|
|
57
|
+
error, appname=APPLICATION_NAME, action="initialise"
|
|
58
|
+
)
|
|
59
|
+
raise SystemExit(
|
|
60
|
+
" initialise ".join(("Unable to", APPLICATION_NAME))
|
|
61
|
+
) from error
|
|
62
|
+
try:
|
|
63
|
+
app.widget.mainloop()
|
|
64
|
+
except SystemExit:
|
|
65
|
+
stop_application(app, app.widget)
|
|
66
|
+
raise
|
|
67
|
+
except Exception as error:
|
|
68
|
+
application_exception(
|
|
69
|
+
error,
|
|
70
|
+
app,
|
|
71
|
+
app.widget,
|
|
72
|
+
title="Chess Performace Calculator",
|
|
73
|
+
appname=APPLICATION_NAME,
|
|
74
|
+
)
|
|
75
|
+
raise SystemExit(
|
|
76
|
+
" reporting exception in ".join(
|
|
77
|
+
("Exception while", APPLICATION_NAME)
|
|
78
|
+
)
|
|
79
|
+
) from error
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# apply_identities.py
|
|
2
|
+
# Copyright 2024 Roger Marsh
|
|
3
|
+
# Licence: See LICENCE (BSD licence)
|
|
4
|
+
|
|
5
|
+
"""This module provides functions to verify and apply player identities.
|
|
6
|
+
|
|
7
|
+
Identities exported from a performance calculation database are verified
|
|
8
|
+
for consistency and applied to a database if consistent.
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from . import playerrecord
|
|
13
|
+
from . import filespec
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _ApplyIdentitiesReport(list):
|
|
17
|
+
"""Report the outcome of verifying and applying player identities."""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
"""Define the initial report which also means no problems found."""
|
|
21
|
+
self.messages_exist = False
|
|
22
|
+
|
|
23
|
+
def append(self, identity):
|
|
24
|
+
"""Delegate and note if identity has a message."""
|
|
25
|
+
super().append(identity)
|
|
26
|
+
if identity.message is not None:
|
|
27
|
+
self.messages_exist = True
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _NameStatus:
|
|
31
|
+
"""Classify names by representation on database.
|
|
32
|
+
|
|
33
|
+
A name may: not exist, be new, be identified as primary, or an alias
|
|
34
|
+
of a primary.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, names):
|
|
38
|
+
"""Initialise buckets for names."""
|
|
39
|
+
self.names = names
|
|
40
|
+
self.not_on_database = set()
|
|
41
|
+
self.not_identified = {}
|
|
42
|
+
self.identified_primary = {}
|
|
43
|
+
self.identified_alias = {}
|
|
44
|
+
self.chosen_name = None
|
|
45
|
+
self.message = None
|
|
46
|
+
|
|
47
|
+
def classify_names(self, database):
|
|
48
|
+
"""Put each name in the appropriate bucket."""
|
|
49
|
+
not_on_database = self.not_on_database
|
|
50
|
+
not_identified = self.not_identified
|
|
51
|
+
identified_primary = self.identified_primary
|
|
52
|
+
identified_alias = self.identified_alias
|
|
53
|
+
value = playerrecord.PersonValue()
|
|
54
|
+
person_record = playerrecord.PlayerDBrecord(
|
|
55
|
+
valueclass=playerrecord.PersonDBvalue
|
|
56
|
+
)
|
|
57
|
+
encode_record_selector = database.encode_record_selector
|
|
58
|
+
for name in self.names:
|
|
59
|
+
value.load_alias_index_key(repr(name))
|
|
60
|
+
personlist = database.recordlist_key(
|
|
61
|
+
filespec.PLAYER_FILE_DEF,
|
|
62
|
+
filespec.PLAYER_ALIAS_FIELD_DEF,
|
|
63
|
+
key=encode_record_selector(value.alias_index_key()),
|
|
64
|
+
)
|
|
65
|
+
count = personlist.count_records()
|
|
66
|
+
if count > 1:
|
|
67
|
+
self.message = "Player by name record is not unique."
|
|
68
|
+
return
|
|
69
|
+
record = personlist.create_recordsetbase_cursor(
|
|
70
|
+
internalcursor=True
|
|
71
|
+
).first()
|
|
72
|
+
if record:
|
|
73
|
+
not_identified[name] = record
|
|
74
|
+
continue
|
|
75
|
+
personlist = database.recordlist_key(
|
|
76
|
+
filespec.PLAYER_FILE_DEF,
|
|
77
|
+
filespec.PERSON_ALIAS_FIELD_DEF,
|
|
78
|
+
key=encode_record_selector(value.alias_index_key()),
|
|
79
|
+
)
|
|
80
|
+
count = personlist.count_records()
|
|
81
|
+
if count > 1:
|
|
82
|
+
self.message = "Person by name record is not unique."
|
|
83
|
+
return
|
|
84
|
+
record = personlist.create_recordsetbase_cursor(
|
|
85
|
+
internalcursor=True
|
|
86
|
+
).first()
|
|
87
|
+
if record:
|
|
88
|
+
person_record.load_record(record)
|
|
89
|
+
alias = person_record.value.alias
|
|
90
|
+
if alias == person_record.value.identity:
|
|
91
|
+
if alias not in identified_primary:
|
|
92
|
+
identified_primary[alias] = {name: record}
|
|
93
|
+
else:
|
|
94
|
+
identified_primary[alias][name] = record
|
|
95
|
+
continue
|
|
96
|
+
if alias not in identified_alias:
|
|
97
|
+
identified_alias[alias] = {name: record}
|
|
98
|
+
else:
|
|
99
|
+
identified_alias[alias][name] = record
|
|
100
|
+
continue
|
|
101
|
+
not_on_database.add(name)
|
|
102
|
+
|
|
103
|
+
def identify_names(self, database):
|
|
104
|
+
"""Make each not identified name an alias of an identified name.
|
|
105
|
+
|
|
106
|
+
The name in identified_primary or derived from identified_alias
|
|
107
|
+
will be the identified name if available.
|
|
108
|
+
|
|
109
|
+
Otherwise one of the names in not_identified is picked and noted
|
|
110
|
+
in chosen_name.
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
value = playerrecord.PersonValue()
|
|
114
|
+
packer = playerrecord.PlayerDBrecord(
|
|
115
|
+
valueclass=playerrecord.PersonDBvalue
|
|
116
|
+
)
|
|
117
|
+
player_record = playerrecord.PlayerDBrecord()
|
|
118
|
+
person_record = playerrecord.PlayerDBrecord(
|
|
119
|
+
valueclass=playerrecord.PersonDBvalue
|
|
120
|
+
)
|
|
121
|
+
encode_record_selector = database.encode_record_selector
|
|
122
|
+
names = self.not_identified.copy()
|
|
123
|
+
if len(self.identified_primary):
|
|
124
|
+
chosen_alias, name = list(self.identified_primary.items())[0]
|
|
125
|
+
record = list(name.values())[0]
|
|
126
|
+
elif len(self.identified_alias):
|
|
127
|
+
chosen_alias, name = list(self.identified_alias.items())[0]
|
|
128
|
+
record = list(name.values())[0]
|
|
129
|
+
else:
|
|
130
|
+
chosen_name, record = names.popitem()
|
|
131
|
+
player_record.load_record(record)
|
|
132
|
+
chosen_alias = player_record.value.alias
|
|
133
|
+
self.chosen_name = chosen_name
|
|
134
|
+
person_record.load_record(record)
|
|
135
|
+
|
|
136
|
+
# None is safe because self.srkey == new_record.srkey.
|
|
137
|
+
# filespec.PLAYER_ALIAS_FIELD_DEF is correct value otherwise
|
|
138
|
+
# because of how argument is used in edit_record().
|
|
139
|
+
player_record.edit_record(
|
|
140
|
+
database, filespec.PLAYER_FILE_DEF, None, person_record
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
packer.load_record(record)
|
|
144
|
+
person = packer.value.alias_index_key()
|
|
145
|
+
persongames = database.recordlist_key(
|
|
146
|
+
filespec.GAME_FILE_DEF,
|
|
147
|
+
filespec.GAME_PLAYER_FIELD_DEF,
|
|
148
|
+
key=encode_record_selector(person),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
while len(names):
|
|
152
|
+
name, record = names.popitem()
|
|
153
|
+
# value should be PlayerDBvalue but while PersonDBvalue gives
|
|
154
|
+
# the same answer there is no need to change it.
|
|
155
|
+
value.load_alias_index_key(repr(name))
|
|
156
|
+
player_record.load_record(record)
|
|
157
|
+
person_record.load_record(record)
|
|
158
|
+
person_record.value.alias = chosen_alias
|
|
159
|
+
|
|
160
|
+
# None is safe because self.srkey == new_record.srkey.
|
|
161
|
+
# filespec.PLAYER_ALIAS_FIELD_DEF is correct value otherwise
|
|
162
|
+
# because of how argument is used in edit_record().
|
|
163
|
+
player_record.edit_record(
|
|
164
|
+
database, filespec.PLAYER_FILE_DEF, None, person_record
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
persongames |= database.recordlist_key(
|
|
168
|
+
filespec.GAME_FILE_DEF,
|
|
169
|
+
filespec.GAME_PLAYER_FIELD_DEF,
|
|
170
|
+
key=encode_record_selector(value.alias_index_key()),
|
|
171
|
+
)
|
|
172
|
+
database.file_records_under(
|
|
173
|
+
filespec.GAME_FILE_DEF,
|
|
174
|
+
filespec.GAME_PERSON_FIELD_DEF,
|
|
175
|
+
persongames,
|
|
176
|
+
encode_record_selector(person),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def verify_and_apply_identities(database, identities):
|
|
181
|
+
"""Return None if identities applied or a message for display if not."""
|
|
182
|
+
if not database:
|
|
183
|
+
return None
|
|
184
|
+
if not identities:
|
|
185
|
+
return None
|
|
186
|
+
commit_flag = True
|
|
187
|
+
report = _ApplyIdentitiesReport()
|
|
188
|
+
database.start_transaction()
|
|
189
|
+
try:
|
|
190
|
+
for identity in identities:
|
|
191
|
+
name = _NameStatus(identity)
|
|
192
|
+
report.append(name)
|
|
193
|
+
name.classify_names(database)
|
|
194
|
+
if name.message is not None:
|
|
195
|
+
commit_flag = False
|
|
196
|
+
continue
|
|
197
|
+
if (
|
|
198
|
+
len(set(name.identified_primary).union(name.identified_alias))
|
|
199
|
+
> 1
|
|
200
|
+
):
|
|
201
|
+
name.message = "More than one database person is referenced."
|
|
202
|
+
report.messages_exist = True
|
|
203
|
+
commit_flag = False
|
|
204
|
+
continue
|
|
205
|
+
if not commit_flag:
|
|
206
|
+
return report
|
|
207
|
+
for identity in report:
|
|
208
|
+
if not identity.not_identified:
|
|
209
|
+
continue
|
|
210
|
+
identity.identify_names(database)
|
|
211
|
+
# For testing report what has been found.
|
|
212
|
+
# if commit_flag:
|
|
213
|
+
# commit_flag = False
|
|
214
|
+
finally:
|
|
215
|
+
if commit_flag:
|
|
216
|
+
database.commit()
|
|
217
|
+
else:
|
|
218
|
+
database.backout()
|
|
219
|
+
return report
|