-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathCodeBaseTools.py
5210 lines (4677 loc) · 257 KB
/
CodeBaseTools.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
# Copyright (C) 2010-2018 by M-P Systems Services, Inc.,
# PMB 136, 1631 NE Broadway, Portland, OR 97232.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
# Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
""" This module is a wrapper and Pythonizer for the CodeBasePYWrapper.pyd shared library written
in 'C' using the Python 'C' API, which provides relatively easy and Pythonic access to Visual FoxPro DBF tables. The
underlying engine is the CodeBase library, which, as of September 28, 2018, is released by its owner Sequiter, Inc.,
into Open Source, and is available for free use under the GNU Lesser GPL V3.0 license.
Basic documenation for each function is provided in the docstrings below.
NOTE FOR THIS VERSION 2.00 -- October, 2014:
This version is a wrapper for a compiled core python module named CodeBasePYWrapper.pyd which is a revised and
improved version of the original CodeBaseWrapper.dll. The core module is now built with the Python C-API which
allows much faster transfer of data from disk to memory. The original dll was accessed using the cTypes modules
and the Win32 extensions. cTypes is not required for this enhanced version.
Python Version information:
This module has been developed and tested with Python 2.7.6 and 2.7.10.
Tested also with Python 3.6. Earlier 3x versions haven't been tested and may not work.
***********************************************
String handling for Python 2/3 compatibility
***********************************************
Effective with the April, 2018 changes for Python 2 and 3 compatibility, all text values passed to the
Codebase Tools engine will be coerced to str types. That means plain strings for 2.x and Unicode
strings for 3.x. The engine will test for these types and throw an error if the type is wrong.
This applies to path names (no "pathlike" objects), alias names, field names, tag names, and logical expressions
for selecting and filtering.
Field return values will be converted per the table below, if conversion is requested. Unconverted field
contents will be returned as type bytes in all versions, if stored in the tables as text. Field setting values may
be supplied as any valid type (including text representations of values like numbers and dates) and will be converted
by the engine.
**************************************
FoxPro and Visual FoxPro Compatibility
**************************************
All field types used in FoxPro 2.6 DOS and Windows are supported natively by this tool. Also the indexes
generated are compatible with .CDX compound indexes with one or more index tags.
Visual FoxPro 5.0 through 9.0 SP2 tables are supported with the following exceptions:
1. Tables contained in a database container can be read, written to and reindexed, but only the short field names
will be usable. Field names that are 10 characters long or less can be accessed by their normal names.
2. Tables contained in a database container cannot have their structure changed, nor have indexes removed, added,
or changed, as those changes will not be recorded in the database container.
3. VFP supports multiple Windows Code Pages, including 1252, Western Europe. This module only supports
tables marked for Code Page 1252, however, NO translations between code pages are performed by this module.
4. VFP may or may not support Unicode strings, including multibyte languages like Cantonese, in its C and M type
fields. See the docs below on string and Unicode handling in this module.
5. Most, but not all field types are supported, and some type codes are different as given in the following table:
VFP Type Code | Type Name | CodeBase Type Code | Python 2 Type | Python 3 Type***
B Double B float float
C Character C str str (Unicode)
C Character(binary) Z bytearray bytes
(opt) unicode (opt)str (Unicode)
D Date D datetime.date datetime.date
F Float F (same as Numeric) float float
G General G (see **note below) bytes bytes
I Integer I int int
I Integer(auto incr) [not supported
- see *note below]
L Logical L bool bool
M Memo M str str (Unicode)
M Memo(binary) X bytes bytes
N Number N float float
Q Varbinary [not supported]
T Datetime T datetime.datetime datetime.datetime
V Varchar [not supported]
V Varchar(binary) [not supported]
W Blob [not supported]
Y Currency Y Decimal Decimal
*Note -- The last Codebase version added support for an auto incrementing integer field, but these fields are NOT
recognized by Visual FoxPro, and VFP auto incrementing integer fields are not recognized by Codebase and can
cause Codebase to crash. This module therefore does not support auto incrementing integer fields directly, but a
utility function getNewKey() is provided as a source for consecutive integer values.
**Note -- CodeBase and this module support G (General) type fields for reading and writing, but unlike in Visual
Foxpro, their contents cannot be filled with some kind of APPEND GENERAL sort of function. VFP tables which have
had a G field populated by the APPEND GENERAL command from an OLE document can be copied to other tables by this
module, however, as these fields are read as simple strings of generic binary data (possibly containing null bytes),
exactly as X Memo(binary) fields, there is no interpretation done with them.
***Note -- All "strings" in Python 3.x are Unicode. Sequences of bytes that are not intended to represent text of any
kind are stored in "bytes" variables, not "strings". Accordingly, for Py 3.x the default Python type target for a
standard char or memo field is a string, and that of char or memo binary fields is bytes. But you may want to store
Unicode data in binary fields to ensure no CodePage conversions are attempted by FoxPro. As a result options are
provided to write to and read from binary fields in several different Unicode codings.
***********************************************************************
String handling and internationalization... Depends on Python version
***********************************************************************
Field Names:
ASCII is default, but extended ASCII with Code Page 1252 is supported for displaying table structure,
but fields having names with characters above ASCII 127 cannot be accessed for reading and writing.
Tag Names:
ASCII is default, but extended ASCII tag names work properly
Alias Names:
Must be standard ASCII strings. If specified with cp1252 high order bit characters, the Alias Name will be
coerced to 7-bit ASCII.
Table Names:
Names supported by the operating system will work (but see note above for Alias Names, as auto-assigned
Alias Names are also forced to 7-bit ASCII strings.)
Data Fields:
------------
Python 2.7 (earlier versions may work but are not officially supported)
----------
Char and Memo:
Default data storage is 8-bit strings as found in Python str objects. Embedded nulls will be treated as end of
string markers and will not be stored. Unicode strings will be coerced to Code Page 1252 before saving.
Applies to scatter, gather, insert, replace, and curval type functions.
If bytearray types (with or without embedded nulls) are supplied for these fields, they will be copied into
the target field as pure binary data. In the case of char fields, any unused space to the right of the data
will be padded with blanks. Memo fields will contain only the data content. If you write binary data from
bytearray objects into these standard text fields, be sure that you handle the content accordingly when
reading it back. Also beware of FoxPro Code Page translations to which these fields are subject by default.
The following special coding will be allowed optionally in these functions for string and unicode object
data sources.:
' ', 'X', or None = The default storage and retrieval
'W' = Windows Double Byte Unicode encoding for the specified Code Page (default Code Page in the U.S.A. is
usually cp1252) on the local machine. Source data may be string or bytearray, and conversion will be
performed. Unicode will be coerced to the default Code Page.
'8' = UTF-8 encoding. Source may be string, bytearray, or Unicode.
Char(binary) and Memo(binary):
Default data storage is a non-text sequence of 8-bit bytes with no textual meaning. The raw contents of strings
will be stored, but with strings terminated when a NULL is encountered. Bytearrays will be copied character
for character, regardless of the presence of null bytes.
The following special coding will be allowed optionally in these functions with string or unicode type
data source objects:
' ', 'X', or None = The default storage and retrieval
'D' = Windows Double Byte Unicode encoding for the specified Code Page (default Code Page in the U.S.A. is
usually cp1252) on the local machine.
'U' = UTF-8 encoding.
'C' = Custom Code Page code. See Python codecs documentation for allowable values. Examples include:
"cp1252" - Western Europe, "cp1251" - Cyrillic (Russian, etc.), "gb2312" - Simplified Chinese.
Python 3.x (3.4 and higher. Earlier versions not supported.)
------------------------------------------------------------
Char and Memo:
All 3.x Python strings are Unicode. This module will attempt to code Python strings for storage in the
field type specified. If the Unicode string value cannot be converted into a form suitable for the field type,
an error will be triggered. Applies to scatter, gather, insert, replace, and curval type functions.
By default conversion of the strings to and from 8-bit Code Page 1252 will be attempted. This will handle
most special characters for Western European languages.
Version 3.x also has a bytes and bytearray type (differences are very subtle, but the both contain
sequences of bytes that don't necessarily have avy textual meaning. Both of these binary data types may be
stored in both plain and binary char/memo fields, but you will need to be careful with their data
reteieval. In all such cases, the contents of the bytes and bytearray objects will be stored byte for
byte into the target field.
The following special coding will be allowed optionally in these functions:
' ', 'X', or None = The default storage and retrieval (cp1252, 8-bit coding or plain ASCII)
'W' = Windows Double Byte Unicode encoding for the specified Code Page (default Code Page in the U.S.A. is
usually cp1252) on the local machine.
'8' = UTF-8 encoding.
Char(binary) and Memo(binary):
Default data storage is a non-text sequence of 8-bit bytes with no textual meaning.
The following special coding will be allowed optionally in these functions:
' ', 'X', or None = The default storage and retrieval. For Bytes and ByteArray data, this will be a simple
copy of the binary data. Char fields will be filled out with nulls. String (Unicode) data will be stored
as Code Page 1252 and padded out with blanks. (For pure binary data, memo (binary) should be preferred.)
'D' = Windows Double Byte Unicode encoding for the specified Code Page (default Code Page in the U.S.A. is
usually cp1252) on the local machine.
'U' = UTF-8 encoding.
'C' = Custom Code Page code. See Python codecs documentation for allowable values. Examples include:
"cp1252" - Western Europe, "cp1251" - Cyrillic (Russian, etc.), "gb2312" - Simplified Chinese. There
are many others.
All Python versions
-------------------
Note that with all the non-default codings, you are expected to know what codings are contained the fields
you are reading and writing. Data which cannot be transformed by the Python codecs manager will trigger
a Python error and will need to be trapped by a try... except... block.
Note that if you specify a custom coding and are sharing the data with Visual FoxPro, you are responsible for
ensuring the codings match on both sides. VFP will dynamically change field data in Char and Memo
fields (but not their binary versiona) if the table and local machine Code Pages do not match. This could
result in badly corrupted data, as this module does no automatic Code Page conversions.
Also, be careful with functions that read or write data from and to multiple fields. In such cases, the custom
coding spcifiers shown above will be applied to every field of the appropriate type. If that is not what you
want, process fields individually using replace() or curvalstrex().
"""
from __future__ import print_function, absolute_import
import decimal
import os
import csv
from time import time, localtime, strftime, sleep
from locale import atof, atoi
from datetime import date, datetime
import copy
import sys
import string
import shutil
import random
from xml.dom import minidom
import CodeBasePYWrapper as cbp
import MPSSBaseTools as mTools
import collections
if sys.version_info[0] <= 2:
_ver3x = False
xLongType = long
else:
_ver3x = True
xLongType = int
__author__ = "Jim Heuer"
__version__ = "2.01"
__copyright__ = "M-P System Services, Inc., Portland, OR, 2008-2016"
__versiondate__ = "February 19, 2015"
__license__ = """Provided to all users on an as-is basis. No warranties, express or implied are
made for this code. Users are asked to supply information on required changes, perceived bugs,
and other comments about the code to the author. This license and author information must be included
in any derived versions of this code modified by others. The author reserves the right to modify
this code at any time and may or may not post updated versions to a website for retrieval by others.
Use of this code is completely free. HOWEVER, this code will NOT WORK without a copy
of Sequiter Software's CodeBase software. It also requires a compiled version of the CodeBasePYWrapper.c
program, built into a DLL (copied to a PYD) with Microsoft Visual Studio C, using the header files supplied in the
CodeBase install package.
This software is working successfully in the author's systems with CodeBase 6.5 updated with all the latest
CodeBase updates and enhancements available to registered users. Other versions may or may not work with this
software, and the author takes no responsibility and makes no guarantees for compatibility with this or any other
versions of CodeBase.
No support is provided to users of this code by the author. However, the author will respond to questions on a
time available basis. The author may be contacted at jsheuer at mpss-pdx.com
This software and its companion CodeBasePYWrapper.c program are provided free of charge. The terms of use
for this software are the GNU Lesser GPL, Version 3.0, a copy of which should have been provided with this file.
"""
# 10/16/2014 JSH. Start conversion from Version 1.0 of this code
# 10/30/2014 JSH. Final testing completed.
# 12/02/2014 JSH. Added cbToolsX() which returns a unique stand-alone version of the _cbTools() object and
# supports the use of multiple sessions to keep table opens and closes isolated from those
# of other modules using another instance.
# 01/06/2015 JSH. Added extra table open try to getNewKey().
# 02/19/2015 JSH. Major changes to use() method. No longer allows same table to be opened multiple times
# with the same alias.
gbIsRandomized = False
"""
Set True when the randomizer functions are seeded, as that only needs to happen once.
"""
gnNextKeyTableNameLength = 0
gcLastErrorMessage = ""
class VFPFIELD(object):
def __init__(self):
self.cName = ""
self.cType = ""
self.nWidth = 0
self.nDecimals = 0
self.bNulls = False
class VFPINDEXTAG(object):
def __init__(self):
self.cTagName = ""
self.cTagExpr = ""
self.cTagFilt = ""
self.nDirection = 0
self.nUnique = 0
class _cbTools(object):
recDict = {}
recArray = list()
_tablenames = list()
def __init__(self, bLargeMode=False):
""" Initializer routine. """
self.cErrorMessage = ""
self.nErrorNumber = 0
self.bUseDecimals = False # Set this to True to return Number field values as decimal.Decimal() type values.
self.tally = 0 # Stores the count of records most recently processed by several methods.
self.xCalcTypes = {"SUM": 1, "AVG": 2, "MAX": 3, "MIN": 4}
# if not _ver3x:
# self.workIdent = string.maketrans('', '')
# else:
# self.workIdent = str.maketrans('', '')
self.workIdent = None
self.cNonPrintables = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
self.xNonPrintables = dict()
for c in self.cNonPrintables:
self.xNonPrintables[ord(c)] = None
self.cbt = cbp
self.nDataSession = self.cbt.initdatasession(bLargeMode)
self.oCSV = csv
self.xLastFieldList = [] # Used by scattertorecord() to avoid having to repeat afields()
self.cLastRecordAlias = "" # ditto
self.xLastRecordTemplate = None
self.name = "CodeBaseTools"
self.cPreferredEncoding = "cp1252" # Western European languages. Like "Latin-1".
def __getitem__(self,xfile):
return self.TableObj(xfile)
def __del__(self):
if self.nDataSession >= 0:
if self.cbt is not None:
try:
self.cbt.closedatasession(self.nDataSession)
except:
pass # Nothing to be done about it.
# print "Closed Datasession", self.nDataSession
self.nDataSession = -1
self.cbt = None
def cb_shutdown(self):
"""
Manually does what the __del__() does above.
"""
if self.nDataSession >= 0:
self.cbt.closedatasession(self.nDataSession)
self.nDataSession = -1
return True
def TableObj(self, *args, **kwargs):
"""
Create database object from cbTools connection object. Ignores deleted record.
:tablename: can include full path
optional :reset: will cause file to be re-opened if already open
"""
return TableObj( self, *args, **kwargs)
def setcustomencoding(self, lpcEncoding=""):
"""
If you want string data to be retrieved and saved in an encoding different from the three basic options
(ASCII, UTF-8, and Code Page 1252), you can specify one the many standard Python encodings (see the Python
documentation for the latest complete list.) For this custom configuration to be applied, the conversion
code of 'C' must be specified and the target field must be a binary version (either of char or memo).
:param lpcEncoding: Python encoding name like "cp1252" or "cp1251".
:return: True. Note that there is no error checking on the value of the lpcEncoding parameter! If
you specify a non-existent name, this module will generate a coding error when processing binary data
with the 'C' conversion option.
"""
self.cbt.setcustomencoding(lpcEncoding)
self.cPreferredEncoding = lpcEncoding
return True
def setdateformat(self, lpcFormat=""):
"""
Codebase, like the USA versions of Visual FoxPro assumes U.S. standard date
expressions of the form MM/DD/YY or MM/DD/YYYY, in contrast to other parts of
the world that use DD/MM/YY or DD/MM/YYYY or other combinations. This default
assumptions about date text strings can be changed. This is especially useful
when importing text data via the appendfrom() function. If non-numeric
characters are found in the text, and it is intended to be stored in a date or
datetime field, then an attempt will be made to parse the text based on the
standard date expression, either MM/DD/YYYY or an alternate set prior to the
append process.
Parameters:
- lpcFormat - This must be a character string with at minimum the characters
'YY' or 'YYYY', and 'MM', and 'DD' in some combination with punctuation or
it must be one of the Visual FoxPro standard SET DATE TO values such as:
AMERICAN = MM/DD/YY
ANSI = YY.MM.DD
BRITISH = DD/MM/YY
FRENCH = DD/MM/YY
GERMAN = DD.MM.YY
ITALIAN = DD-MM-YY
JAPAN = YY/MM/DD
TAIWAN = YY/MM/DD
USA = MM-DD-YY
MDY = MM/DD/YY
DMY = DD/MM/YY
YMD = YY/MM/DD
Pass NULL or the empty string to restore the MDY/AMERICAN default.
Returns 1 on OK, 0 on any kind of error, mainly unrecognized formats.
The total length of the lcFormat string must be no greater than 16 bytes.
"""
self.cErrorMessage = ""
self.nErrorNumber = 0
lnReturn = self.cbt.setdateformat(lpcFormat)
if lnReturn == 0:
self.cErrorMessage = self.cbt.geterrormessage()
self.nErrorNumber = self.cbt.geterrornumber()
return lnReturn
def getdateformat(self):
"""
Obtains the current setting of date format from the CodeBase engine. Returns a text string indicting
the type of format. See setdateformat() for format codes. Will always return a string value.
"""
return self.cbt.getdateformat()
def newfield(self, cName, cType, nWidth, nDecimals, bNulls):
lxRet = VFPFIELD()
lxRet.cName = cName
lxRet.cType = cType
lxRet.nWidth = nWidth
lxRet.nDecimals = nDecimals
lxRet.bNulls = bNulls
return lxRet
def isexclusive(self):
"""
Returns the exclusive open status on the currently selected table. Returns True if exclusive, False if
NOT exclusive. Returns None on error.
"""
self.cErrorMessage = ""
self.nErrorNumber = 0
bRet = self.cbt.isexclusive()
if bRet is None:
self.cErrorMessage = self.cbt.geterrormessage()
self.nErrorNumber = self.cbt.geterrornumber()
return bRet
def islargemode(self):
"""
Returns the status of Large Mode operations. If this instance of the Tools is using large mode
then, this will return True. Otherwise False. If True, this tools instance may NOT be used to
open DBF tables that might also be opened concurrently by Visual FoxPro applications.
"""
return self.cbt.getlargemode()
def use(self, tableName, alias="", readOnly=False, exclusive=False, noBuffering=False):
"""
Basic method for starting work with a DBF table. This must be called before any work with
the table can be performed. After the table is opened with use(), it is the "Currently
Selected Table" and can be accessed by the other methods of this class that operate on the
currently selected table. If you call use() again for another table, that table becomes the
currently selected table. You can change the currently selected table later by calling the
select() method and passing to it the alias name assigned by the original use() method call.
When finished with the table call close() and pass the alias name assigned when the table
was opened. The method closedatabases() will also close the table (and close all other open
tables too). (Changed the mechanism by which the alias is handled 02/19/2015. JSH.)
Parameters:
:param: tableName: path name to the dbf table you want to open. Must have the .dbf extension
unless the table you wish to open has some other extension -- .DBF is not
enforced, in other words. Up to you as to how much info you pass in the
path name, but if the file can't be found the method reports an error.
:param: alias: an optional character string with an alphanumeric string starting with a
letter or underscore which will be used as the alias for subsequent table and field
manipulations. The presence of this value determines how tables are opened when
multiple tables might be opened with the same base file name but in different
directories. Two cases:
1) An alias is specified.
If a table with that alias is already open (either this same table or another one in
a different directory but with the same base name), that table is closed, and then
this table open happens and the alias you specified is assigned. Otherwise the
table is simply opened and this alias name is assigned.
2) NO alias is specified.
A default alias is determined, which is equal to the base table name (other than
path and extension) of the table with embedded spaces and punctuation removed, if any.
If this same table is already open with that alias, it is closed and re-opened with your
required properties.
If a different table is already open with that alias, it is left open, and a new
unique alias is created as 'TMP' followed by a string of 12 random characters. The
table specified in this function call is then opened with that new alias.
NOTE: If you do NOT specify an alias, the alias name is NOT guaranteed to be the table
base name. If you will need to manipulate the tables and you don't specify an alias
name, you MUST store the alias name value provided by the alias() method immediately
after the table is opened, and use that alias name for select() and similar functions
which take an alias name parameter.
The same table is allowed to be opened multiple times, each with its own alias. Remember
in that case that closing one of the instances will NOT close the other(s).
:param: readOnly: boolean value which defaults to False. If set to True, then the table is
opened in a mode which prevents any record updates or record appends.
Reads from tables opened readOnly are faster, and readOnly is preferred when
writing is not actually required.
:param: exclusive: boolean value which defaults to False. If set to True, then the table is
opened in exclusive mode. This means that while the table is open, no other
process may open or access it. Exclusive mode is required for safely
applying indexes in situations where other processes may attempt access. When
exclusive is True, the setting for readOnly is ignored and defaults to False.
:param: noBuffering: Boolean value defaults to False. Set to True to force the CodeBase engine
to read/write directly from/to disk without any in-memory buffering of data.
This may be useful for tables with lots of multi-user access. However you
may still need to call flush() if data corruption issues are encountered.
Returns:
True on success, False on any error
"""
lbReturn = True
if tableName == "" or tableName == "?":
import easygui # on demand. should only be used in interactive mode
tableName = easygui.fileopenbox("Select file to open", "Open DBF file",
" *.dbf") # the initial space IS required because of a bug in easygui
if (tableName == "") or (tableName is None):
self.cErrorMessage = "NO table name supplied"
self.nErrorNumber = -49348
return False
# if "LTLACCESSENABLE" in tableName:
# import traceback
# stack_str = ''.join(traceback.format_stack())
# MPSSCommon.MPSSBaseTools.MPMsgBox("OPENING LAE with Alias: >" + alias + "< stack:" + stack_str)
self.cErrorMessage = ""
self.nErrorNumber = 0
if not exclusive:
nReadOnly = (1 if readOnly else 0)
nNoBuffering = (1 if noBuffering else 0)
lbReturn = self.cbt.use(tableName, alias, nReadOnly, nNoBuffering)
else:
lbReturn = self.cbt.useexcl(tableName, alias)
if not lbReturn:
self.cErrorMessage = self.cbt.geterrormessage() + " OPEN FAILED"
self.nErrorNumber = self.cbt.geterrornumber()
return lbReturn
def maketempindex(self, lcExpr, tagFilter="", descending=0):
"""
Similar to indexon except that this creates a temporary, single use index that only lives as long
as the table is open. This index has only one order embedded in it. This method call applies the
index to the currently selected table and makes the new temp index the current order selection. You
can traverse the file in the temp index order but seek() is not currently supported. However, the
locate() method will exploit available temporary indexes if they allow the locate to be optimizable.
You can change to different orders in the regular index file (CDX) by using setorderto() while you keep
this temp index open. Open temp indexes are updated when records are changed, added or deleted in the
table.
The method returns a temp index code which you should store in case you need to set the ordering back to
this temp index with selecttempindex(), which uses the index code as its parameter.
The temp index is associated with one alias -- the currently selected one when the index was created. If
the same table is open with a different alias, it cannot be accessed by that different alias with this
index order.
Multiple temporary indexes can be opened for a table at the same time, and you can switch back and forth
among them as required. Closing the table with the closetable() method, closes all temporary indexes
associated with the specified alias and deletes the files they were stored in.
lcExpr MUST be a valid VFP index expression, and tagFilter, if non-blank, must be a valid VFP logical
filter expression. See indexon() for details. Set descending to 1 to have the index constructed in
reverse order (highest at the top). The 'unique' option is not available for temp indexes.
Returns a valid index code >= 0 or -1 on error.
"""
self.cErrorMessage = ""
self.nErrorNumber = 0
if not mTools.isstr(tagFilter):
raise TypeError("tagFilter parm MUST be a string or be omitted")
lnResult = self.cbt.tempindex(lcExpr, tagFilter, descending)
if lnResult == -1:
self.cErrorMessage = self.cbt.geterrormessage()
self.nErrorNumber = self.cbt.geterrornumber()
return lnResult
def selecttempindex(self, lnCode):
"""
Once a temporary index has been created with maketempindex, if you have needed to change the index order
for any reason, you can restore the temporary ordering of a previously created temp index. Store the return
value from maketempindex() and use it as the lnCode parm value in this method. Subsequent goto() and skip()
method calls will use the temp index.
"""
self.cErrorMessage = ""
self.nErrorNumber = 0
lbReturn = self.cbt.selecttempindex(lnCode)
if (not lbReturn):
self.cErrorMessage = self.cbt.geterrormessage()
self.nErrorNumber = self.cbt.geterrornumber()
return lbReturn
def closetempindex(self, lnCode):
"""
Temporary indexes are automatically destroyed when the table they relate to is closed. If you want
to close the temporary index before the table, pass the index code to this method. You may wish to do
this if you are going to be adding a large number of records to the table, and don't want the possibly
useless overhead of updating the temporary table(s) on every append. Returns True on OK, False on failure.
"""
self.cErrorMessage = ""
self.nErrorNumber = 0
lbReturn = self.cbt.closetempindex(lnCode)
if not lbReturn:
self.cErrorMessage = self.cbt.geterrormessage()
self.nErrorNumber = self.cbt.geterrornumber()
return lbReturn
def updatestructure(self, cSourceTable, cTargetTable):
"""
This function examines the field structure and index tags of the source table referenced by cSourceTable
and forces the table named in cTargetTable (not currently open) to have the same structure and index tags. The
current content of the target table is retained so long as all of its fields are retained. Data in fields
which are not contained in the cSourceTable table will be lost.
:param cSourceTable: Alias of pattern table, the source of the structure/tags, not currently open.
:param cTargetTable: DBF table to be modified with the structure and index tags of the pattern (source) table.
:return: True on success, False on failure or any error.
NOTE: Do NOT, repeat DO NOT, use this method to modify the structure or indexes of a VFP table contained
in a VFP database container. If you do, the table may become un-readable!
"""
bReturn = self.use(cSourceTable, readOnly=True)
cErr = ""
nErr = 0
cTempFile = ""
cTempAlias = ""
# mTools = MPSSCommon.MPSSBaseTools
cPath, cFile = os.path.split(cTargetTable)
cTempFile = os.path.join(cPath, "X" + mTools.strRand(8) + ".DBF")
if not bReturn:
cErr = self.cErrorMessage
nErr = self.nErrorNumber
else:
cSrcAlias = self.alias()
xStru = self.afields()
bReturn = self.createtable(cTempFile, xStru)
if bReturn:
cTempAlias = self.alias()
self.closetable(cTempAlias)
self.use(cTempFile, alias=cTempAlias, exclusive=True)
self.closetable(cSrcAlias)
nACount = self.appendfrom(cTempAlias, cSource=cTargetTable, cType="DBF")
if nACount == -1:
bReturn = False
cErr = self.cErrorMessage
nErr = self.nErrorNumber
if bReturn:
bReturn = self.use(cSourceTable, alias=cSrcAlias, readOnly=True)
if bReturn:
bReturn = self.copyindexto(cSrcAlias, cTempAlias)
if not bReturn:
cErr = self.cErrorMessage
nErr = self.nErrorNumber
self.closetable(cSrcAlias)
else:
cErr = self.cErrorMessage
nErr = self.nErrorNumber
cTargetMemo = mTools.FORCEEXT(cTargetTable, "FPT")
cTargetIndex = mTools.FORCEEXT(cTargetTable, "CDX")
cTempMemo = mTools.FORCEEXT(cTempFile, "FPT")
cTempIndex = mTools.FORCEEXT(cTempFile, "CDX")
if bReturn:
self.closetable(cTempAlias)
mTools.DELETEFILE(cTargetTable)
mTools.DELETEFILE(cTargetMemo)
mTools.DELETEFILE(cTargetIndex)
try:
if os.path.exists(cTempFile):
shutil.copy2(cTempFile, cTargetTable)
if os.path.exists(cTempMemo):
shutil.copy2(cTempMemo, cTargetMemo)
if os.path.exists(cTempIndex):
shutil.copy2(cTempIndex, cTargetIndex)
except:
bReturn = False
cErr = "Copy to replacement table failed"
nErr = -30598
mTools.DELETEFILE(cTempFile) # These don't throw an error if the file isn't there.
mTools.DELETEFILE(cTempMemo)
mTools.DELETEFILE(cTempIndex)
self.cErrorMessage = cErr
self.nErrorNumber = nErr
return bReturn
def indexon(self, lcTag, lcExpr, tagFilter="", descending=0, unique=0):
"""
Method that creates an index tag for the currently selected open DBF table.
If no DBF is currently selected, or some other problem occurs, it returns False
otherwise returns True. This method creates and modifies the VFP type CDX index
files which may contain multiple physical indexes, each identified by a Tag name.
It is expected that there will be one and only one CDX index for the table. In VFP
and CodeBase, this one-to-one limit is not enforced, but with one CDX file having the
same base file name as the table, you are assured that the index will be updated with
its key values whenever the underlying table field values change.
If no CDX file exists for the table, one is created and the requested tag added to it.
If the tag exists, it is replaced with your new tag without any warning.
Parameters:
- lcTag = a character string of up to 10 alphanumeric values starting with a letter
- lcExpr = a character string containing an index expression with one or more fields
defining the index 'key'.
- tagFilter = optional character string with an expression limiting the number of
records indexed
- unique = one of several values determining how duplicate key values will be handled when the table
is indexed or new records are added:
0 = No special handling, duplicate key values are recorded in the index normally.
15 = VFP Compatible "Candidate" index. Duplicate key values will result in an error condition if
encountered in the table. Both VFP and CodeBase recognize this index type.
20 = CodeBase compatible setting. Duplicate key values will result in an error condition if
encountered in the table by CodeBase BUT, when VFP accesses or indexes the table it will behave
like code 25.
25 = VFP type UNIQUE index. In this case when the table is indexed, only the first occurrence of a
duplicate is stored in the index. If the table is traversed or browsed in this index order,
it is possible that not all records will be visible or be displayed. Behavior is the same
for VFP and CodeBase.
Returns:
True on success, False on any error (see properties cErrorMessage and nErrorNumber for
more information)
NOTE: As of Oct. 26, 2013, see method setstrictaliasmode(), which may be required to support this method.
IMPORTANT NOTE!!! Do NOT attempt to use this method to add an index to a table contained in a VFP Data Dictionary
Container. Such tables can ONLY have indexes changed using VFP itself. However, those indexes CAN be
reindexed successfully by the reindex() method.
"""
lbReturn = True
self.cErrorMessage = ""
self.nErrorNumber = 0
if (unique != 0) and (unique != 20) and (unique != 25) and (unique != -340) and (unique != 15) and (unique != -360):
unique = 0 # Make sure it's a valid value.
lbReturn = self.cbt.indexon(lcTag, lcExpr, tagFilter, descending, unique)
if not lbReturn:
self.cErrorMessage = self.cbt.geterrormessage()
self.nErrorNumber = self.cbt.geterrornumber()
return lbReturn
def getErrorMessage(self):
cMsg = self.cbt.geterrormessage()
return cMsg
def select(self, lcAlias):
"""
Method that sets the currently selected table to the one with the alias passed as the
parameter. If the string value you pass is not associated with an open table, a False is
returned. If the string is associated with an open table, that table becomes the currently
selected table, and a True is returned. Note that alias names are NOT case sensitive in VFP
or in this module.
"""
self.cErrorMessage = ""
self.nErrorNumber = 0
lbReturn = self.cbt.select(lcAlias)
if not lbReturn:
self.cErrorMessage = self.cbt.geterrormessage() + " SELECT FAILED"
self.nErrorNumber = self.cbt.geterrornumber()
return lbReturn
def dbf(self, alias=""):
"""
Returns the fully qualified path name of the currently selected table or the table
referenced by the optional parameter alias. Returns the empty string if there is no
currently open table or if the alias doesn't exist.
"""
lcReturn = ""
self.cErrorMessage = ""
self.nErrorNumber = 0
lcReturn = self.cbt.dbf(alias)
if lcReturn == "":
self.cErrorMessage = self.cbt.geterrormessage()
self.nErrorNumber = self.cbt.geterrornumber()
return lcReturn
def isreadonly(self, alias=""):
"""
Returns True if the specified open data table was opened in read only mode, otherwise false.
:param alias: Specify the target table alias or leave empty for the currently selected table.
:return: True if opened readonly. If in read-write or exclusive mode, returns False. Returns None
on error - typically table not opened.
"""
lbReturn = False
self.cErrorMessage = ""
self.nErrorNumber = 0
lbReturn = self.cbt.isreadonly(alias)
if lbReturn is None:
self.cErrorMessage = self.cbt.geterrormessage()
self.nErrorNumber = self.cbt.geterrornumber()
return lbReturn
def used(self, alias):
"""
Pass a text string with an alias name. If that table is open, the function will return True,
otherwise false.
"""
self.cErrorMessage = ""
self.nErrorNumber = 0
if alias != "":
lbReturn = self.cbt.used(alias)
if not lbReturn:
self.cErrorMessage = self.cbt.geterrormessage()
self.nErrorNumber = self.cbt.geterrornumber()
else:
self.cErrorMessage = "Alias parameter was blank"
self.nErrorNumber = -59595
lbReturn = False
return lbReturn
def alias(self):
"""
Method that returns the alias name of the currently selected table. If there is no
currently selected table, returns an empty string "". Call this immediately after a table
use() or createtable() if you need to capture the alias, especially if you may have opened
or created a table with a temporary or externally defined name, and don't want to make
assumptions about what default name might have been assigned to the table and have not set
the alias name yourself.
"""
lcStr = self.cbt.alias()
return lcStr
def goto(self, gomode, gonum=1):
"""
Primary method for navigating through the currently selected table. Functionality is
similar to the GOTO command in Visual FoxPro and xBase languages. Unlike SQL-based
databases, VFP tables can be accessed navigationally by moving around amongst the records in
the table using variations of this method. Returns True when navigation is successful,
otherwise False. If False, inspect the cErrorMessage and nErrorNumber properties for more
information. The effect of this method is to move the current record pointer. The current
record will be the source of field information when the scatter() method is executed and the
target of updates when the gathermemvar() method is executed. If an order based on an index
Tag has been set by setorderto(), all gomode values except RECORD honor the index order.
Parameters:
- gomode = character string containing one of the following values:
RECORD - In this case you must supply the second parameter gonum with an integer
value specifying the record number to move to. If the record number
specified doesn't exist, the method returns False
SKIP - Moves the current record point forward or back a specified number of records
(in index order if one has been set). If gonum is not supplied, then moves
forward by one record. If gonum > 1, moves forward by gonum records if
possible. If EOF() is reached before gonum records, returns False. If gonum
< 0, moves backwards by gonum records if possible. If BOF() is reached
before gonum records, returns False.
NEXT - Identical to SKIP with gonum = 1.
PREV - Identical to SKIP with gonum = -1.
TOP - Moves to the top record by the current ordering (or record 1 if there is no
index Tag ordering in effect). If there are no records in the table, returns
False.
BOTTOM - Move to the the last record by the current ordering (or the highest record
number if there is no index Tag ordering in effect). If there are no records
in the table, returns False.
Returns:
True on Success, False on Failure for any reason. Inspect the cErrorMessage or nErrorNumber
for reasons.
"""
self.cErrorMessage = ""
self.nErrorNumber = 0
lbReturn = self.cbt.goto(gomode, gonum)
if not lbReturn:
self.cErrorMessage = self.cbt.geterrormessage()
self.nErrorNumber = self.cbt.geterrornumber()
return lbReturn
def calcfor(self, stat="SUM", fldexpr="", forexpr=""):
"""
Calculates a statistic from a numeric expression in fldexpr based on the records in the currently selected table
that satisfy the logical expression contained in forexpr. Returns a float value result of the calculation.
Returns None on any kind of error (for example an invalid expression string). See locate() for examples of
valid contents for the forexpr parameter. This obeys the setting of DELETED.
Values allowed in stat are:
"SUM" - totals the values in the fldexpr
"AVG" - produces a simple average
"MAX" - what it says
"MIN" - what it says.
Other considerations:
- Must NEVER be used in the midst of a locate/continue/locateclear sequence. If it is,
it will return None error.
- Does not alter the current record pointer position, so can be called on the current table in the middle
of a scan().
- Since this executes entirely in the 'C' engine, it will be much faster than scanning through a table
with Python code calling lower level CBTools functions.
- If the fldexpr contains only the name of an Integer type field, the result will always be 0.0 UNLESS
you append a "+0" to the expression (due to an oddity in the way CodeBase handles Integer fields). So,
if S_NUM is an Integer, and you need the sum of these values, pass "S_NUM+0" as the value of fldexpr. This
does not apply to Number type fields. It has not been tested with VFP Double, Float, or Currency type fields.
"""
lnStat = self.xCalcTypes.get(stat.upper(), 0)
if not lnStat:
self.cErrorMessage = "Bad Statistic Type"
self.nErrorNumber = -34934
return None
self.cErrorMessage = ""
self.nErrorNumber = 0
lnResult = self.cbt.calcstats(lnStat, fldexpr, forexpr)
if lnResult is None:
# Error condition
self.cErrorMessage = self.cbt.geterrormessage()
self.nErrorNumber = self.cbt.geterrornumber()
return lnResult
def deleted(self):
"""
Determines if the current record of the currently selected table has been marked for deletion.
Returns True if the record has been marked for deletion. Returns False if it hasn't. Returns
None if there has been an error.
Inspect cErrorMessage and nErrorNumber if None is returned for more information.
"""
self.cErrorMessage = ""
self.nErrorNumber = 0
bResult = self.cbt.deleted()
if bResult is None:
self.cErrorMessage = self.cbt.geterrormessage()
self.nErrorNumber = self.cbt.geterrornumber()
return bResult
def recno(self):
"""
Returns the record number of the current record of the currently
selected table. Returns the record number on success, -1 on any kind of
failure.
When a table is opened the record pointer is automatically moved to
record 1, if it exists, and there is no selected index tag. If you need
to work on a record, then move to some other records, and later go back
to the original record, it is much faster to save the recno() value and
return to that record using the goto("RECORD", nTheRecord) method.
It is possible for the recno() value returned to be one greater than
the number of records as returned by the reccount() method. In that
case, the record pointer is at an EndOfFile condition, and eof() will
return true.
"""
self.cErrorMessage = ""
self.nErrorNumber = 0
lnReturn = self.cbt.recno()
if lnReturn < 0:
self.cErrorMessage = self.cbt.geterrormessage()
self.nErrorNumber = self.cbt.geterrornumber()
return lnReturn
def countfor(self, expr):
"""
Counts the number of records in the currently selected table that satisfy the logical expression contained
in expr. Returns a value from 0 to the number of records in the table on success. Returns -1 on any kind
of error (for example an invalid expression string). See locate() for examples of valid contents for the
expr parameter. This obeys the setting of DELETED.
Other considerations:
- Must NEVER be used in the midst of a locate/continue/locateclear sequence. If it is,
it will return -1 error.
- Does not alter the current record pointer position, so can be called on the current table in the middle
of a scan().
"""
self.cErrorMessage = ""
self.nErrorNumber = 0
if not expr.strip():
raise ValueError("Expression may NOT be empty or blank")
lnReturn = self.cbt.count(expr)
if lnReturn == -1:
self.cErrorMessage = self.cbt.geterrormessage()
self.nErrorNumber = self.cbt.geterrornumber()
return lnReturn
def locate(self, expr):
"""
Comparable to the VFP LOCATE command. This operates ONLY on the currently selected table and
performs a search of the table for the first record which satisfies the logical expression provided
in the parameter expr. Parm expr must contain a valid VFP/DBASE type expression and may include
.AND., .OR., and other logical expression elements. Field names in the currently selected table
may be included as well as VFP/DBASE functions supported by CodeBase. If any of the logical expressions
contained in expr match index expressions in index tags on the current table, CodeBase will optimize
the search to take advantage of the tags.
Returns True on success (found a record), False on failure for any reason. Inspect nErrorNumber if there
is a possibility of an error condition. It will have the value 0, if it is a simple no-find situation.
Examples of valid expr values:
UPPER(LAST_NAME) = "JONES" .AND. UPPER(FIRST_NAME) = "PETER" .AND. SSN = "123456789"
STR(RecordKey) + TTOC(dateField, 1) + COMPANY = " 123456720120629053329UNITED COPPER"
In the first example if there is an index on the expression UPPER(LAST_NAME) the search will be partially
optimizable and will be substantially faster than a brute force search through the table. In the second
example, for it to be optimizable, an index tag would need to exist with the expression exactly the
same as the expression to the left of the = sign.