AmritSbisht commited on
Commit
d31253d
·
verified ·
1 Parent(s): 96e1c7f

Upload 3701 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env +1 -0
  2. .gitattributes +7 -0
  3. Scraper/course_scraper.py +162 -0
  4. app.py +185 -0
  5. data/detailed_courses.csv +0 -0
  6. myenv/Include/site/python3.11/greenlet/greenlet.h +164 -0
  7. myenv/Lib/site-packages/.DS_Store +0 -0
  8. myenv/Lib/site-packages/GitPython-3.1.44.dist-info/AUTHORS +59 -0
  9. myenv/Lib/site-packages/GitPython-3.1.44.dist-info/INSTALLER +1 -0
  10. myenv/Lib/site-packages/GitPython-3.1.44.dist-info/LICENSE +29 -0
  11. myenv/Lib/site-packages/GitPython-3.1.44.dist-info/METADATA +295 -0
  12. myenv/Lib/site-packages/GitPython-3.1.44.dist-info/RECORD +82 -0
  13. myenv/Lib/site-packages/GitPython-3.1.44.dist-info/WHEEL +5 -0
  14. myenv/Lib/site-packages/GitPython-3.1.44.dist-info/top_level.txt +1 -0
  15. myenv/Lib/site-packages/adodbapi/__init__.py +81 -0
  16. myenv/Lib/site-packages/adodbapi/__pycache__/__init__.cpython-311.pyc +0 -0
  17. myenv/Lib/site-packages/adodbapi/__pycache__/ado_consts.cpython-311.pyc +0 -0
  18. myenv/Lib/site-packages/adodbapi/__pycache__/adodbapi.cpython-311.pyc +0 -0
  19. myenv/Lib/site-packages/adodbapi/__pycache__/apibase.cpython-311.pyc +0 -0
  20. myenv/Lib/site-packages/adodbapi/__pycache__/is64bit.cpython-311.pyc +0 -0
  21. myenv/Lib/site-packages/adodbapi/__pycache__/process_connect_string.cpython-311.pyc +0 -0
  22. myenv/Lib/site-packages/adodbapi/__pycache__/schema_table.cpython-311.pyc +0 -0
  23. myenv/Lib/site-packages/adodbapi/__pycache__/setup.cpython-311.pyc +0 -0
  24. myenv/Lib/site-packages/adodbapi/ado_consts.py +281 -0
  25. myenv/Lib/site-packages/adodbapi/adodbapi.py +1157 -0
  26. myenv/Lib/site-packages/adodbapi/apibase.py +724 -0
  27. myenv/Lib/site-packages/adodbapi/examples/__pycache__/db_print.cpython-311.pyc +0 -0
  28. myenv/Lib/site-packages/adodbapi/examples/__pycache__/db_table_names.cpython-311.pyc +0 -0
  29. myenv/Lib/site-packages/adodbapi/examples/__pycache__/xls_read.cpython-311.pyc +0 -0
  30. myenv/Lib/site-packages/adodbapi/examples/__pycache__/xls_write.cpython-311.pyc +0 -0
  31. myenv/Lib/site-packages/adodbapi/examples/db_print.py +72 -0
  32. myenv/Lib/site-packages/adodbapi/examples/db_table_names.py +21 -0
  33. myenv/Lib/site-packages/adodbapi/examples/xls_read.py +41 -0
  34. myenv/Lib/site-packages/adodbapi/examples/xls_write.py +41 -0
  35. myenv/Lib/site-packages/adodbapi/is64bit.py +34 -0
  36. myenv/Lib/site-packages/adodbapi/license.txt +505 -0
  37. myenv/Lib/site-packages/adodbapi/process_connect_string.py +137 -0
  38. myenv/Lib/site-packages/adodbapi/readme.txt +87 -0
  39. myenv/Lib/site-packages/adodbapi/schema_table.py +16 -0
  40. myenv/Lib/site-packages/adodbapi/setup.py +68 -0
  41. myenv/Lib/site-packages/adodbapi/test/__pycache__/adodbapitest.cpython-311.pyc +0 -0
  42. myenv/Lib/site-packages/adodbapi/test/__pycache__/adodbapitestconfig.cpython-311.pyc +0 -0
  43. myenv/Lib/site-packages/adodbapi/test/__pycache__/dbapi20.cpython-311.pyc +0 -0
  44. myenv/Lib/site-packages/adodbapi/test/__pycache__/is64bit.cpython-311.pyc +0 -0
  45. myenv/Lib/site-packages/adodbapi/test/__pycache__/setuptestframework.cpython-311.pyc +0 -0
  46. myenv/Lib/site-packages/adodbapi/test/__pycache__/test_adodbapi_dbapi20.cpython-311.pyc +0 -0
  47. myenv/Lib/site-packages/adodbapi/test/__pycache__/tryconnection.cpython-311.pyc +0 -0
  48. myenv/Lib/site-packages/adodbapi/test/adodbapitest.py +1599 -0
  49. myenv/Lib/site-packages/adodbapi/test/adodbapitestconfig.py +184 -0
  50. myenv/Lib/site-packages/adodbapi/test/dbapi20.py +885 -0
.env ADDED
@@ -0,0 +1 @@
 
 
1
+ GOOGLE_API_KEY= "AIzaSyDV12vrr57IM98VmbEMdWm2fqDTMFFsNE4"
.gitattributes CHANGED
@@ -40,3 +40,10 @@ Project/myenv/Lib/site-packages/faiss_cpu.libs/openblas-a71b19fb42460f7cb972d9cc
40
  Project/myenv/Lib/site-packages/faiss/_swigfaiss.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
41
  Project/myenv/Lib/site-packages/grpc/_cython/cygrpc.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
42
  Project/myenv/share/jupyter/nbextensions/pydeck/index.js.map filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
40
  Project/myenv/Lib/site-packages/faiss/_swigfaiss.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
41
  Project/myenv/Lib/site-packages/grpc/_cython/cygrpc.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
42
  Project/myenv/share/jupyter/nbextensions/pydeck/index.js.map filter=lfs diff=lfs merge=lfs -text
43
+ myenv/Lib/site-packages/altair/vegalite/v5/schema/__pycache__/channels.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
44
+ myenv/Lib/site-packages/altair/vegalite/v5/schema/__pycache__/core.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
45
+ myenv/Lib/site-packages/faiss_cpu.libs/flang-d38962844214aa9b06fc3989f9adae5b.dll filter=lfs diff=lfs merge=lfs -text
46
+ myenv/Lib/site-packages/faiss_cpu.libs/openblas-a71b19fb42460f7cb972d9cc8bfba6b4.dll filter=lfs diff=lfs merge=lfs -text
47
+ myenv/Lib/site-packages/faiss/_swigfaiss.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
48
+ myenv/Lib/site-packages/grpc/_cython/cygrpc.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
49
+ myenv/share/jupyter/nbextensions/pydeck/index.js.map filter=lfs diff=lfs merge=lfs -text
Scraper/course_scraper.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from bs4 import BeautifulSoup
3
+ import pandas as pd
4
+ from sklearn.feature_extraction.text import TfidfVectorizer
5
+ from sklearn.metrics.pairwise import cosine_similarity
6
+ import time
7
+ from urllib.parse import urljoin
8
+
9
+ # Constants
10
+ BASE_URL = 'https://courses.analyticsvidhya.com/collections/'
11
+ COURSE_LISTING_URL = f'{BASE_URL}courses'
12
+ CSV_FILE = 'detailed_courses.csv'
13
+
14
+ # Function to fetch and parse HTML content
15
+ def fetch_html(url):
16
+ try:
17
+ response = requests.get(url)
18
+ response.raise_for_status()
19
+ return BeautifulSoup(response.content, 'html.parser')
20
+ except requests.exceptions.HTTPError as errh:
21
+ print(f"HTTP Error: {errh}")
22
+ except requests.exceptions.ConnectionError as errc:
23
+ print(f"Error Connecting: {errc}")
24
+ except requests.exceptions.Timeout as errt:
25
+ print(f"Timeout Error: {errt}")
26
+ except requests.exceptions.RequestException as err:
27
+ print(f"Error: {err}")
28
+
29
+ # Function to scrape course listing pages
30
+ def scrape_course_listings():
31
+ courses = [] # Initialize empty list to store all courses
32
+ page_num = 1
33
+ max_pages = 9
34
+
35
+ while page_num <= max_pages:
36
+ print(f"\nProcessing page {page_num}")
37
+ page_url = f"{COURSE_LISTING_URL}?page={page_num}"
38
+ soup = fetch_html(page_url)
39
+
40
+ if not soup:
41
+ print(f"Failed to fetch or parse page {page_num}")
42
+ break
43
+
44
+ course_container = soup.find('div', class_='collections__product-cards collections__product-cards___0b9ab')
45
+ if not course_container:
46
+ print(f"No course container found on page {page_num}")
47
+ break
48
+
49
+ course_cards = course_container.find_all('li', class_='products__list-item')
50
+
51
+ courses_on_this_page = 0 # Counter for courses added from current page
52
+
53
+ for card in course_cards:
54
+ price = card.find('span', class_='course-card__price')
55
+ if price and price.text.strip() == 'Free':
56
+ link = card.find('a')
57
+ title = card.find('h3')
58
+
59
+ if link and title:
60
+ full_link = urljoin(BASE_URL, link['href'])
61
+ courses.append({
62
+ 'Title': title.text.strip(),
63
+ 'Link': full_link,
64
+ 'Page': page_num # Adding page number for verification
65
+ })
66
+ courses_on_this_page += 1
67
+
68
+
69
+ page_num += 1
70
+ time.sleep(1)
71
+
72
+ return courses
73
+
74
+ #Function to scrape detailed course information
75
+ def scrape_course_details(courses):
76
+ for course in courses:
77
+ soup = fetch_html(course['Link'])
78
+ if not soup:
79
+ continue
80
+
81
+ try:
82
+ # Brief
83
+ h2_elements = soup.find_all('h2')
84
+ course['Brief'] = h2_elements[0].text if h2_elements else 'No brief available'
85
+
86
+ # Duration, Rating, Level
87
+ h4_elements = soup.find_all('h4', class_=None)
88
+ if len(h4_elements) >= 3:
89
+ course['Duration'] = h4_elements[0].text
90
+ course['Rating'] = h4_elements[1].text
91
+ course['Level'] = h4_elements[2].text
92
+ else:
93
+ course['Duration'] = 'No duration available'
94
+ course['Rating'] = 'No rating available'
95
+ course['Level'] = 'No level available'
96
+
97
+ # Trainer information
98
+ trainer = []
99
+ inst = soup.find_all('h4', class_=lambda x: x and x.startswith("section__subheading"))
100
+ trainer.extend(i.text for i in inst)
101
+
102
+ tf = soup.find_all('div', class_='section__body')
103
+ if tf and tf[0].get_text(strip=True).startswith("Unlock a lifetime-valid"):
104
+ tf = tf[1:]
105
+
106
+ trainer_dict = {}
107
+ for i in range(len(trainer)):
108
+ if i < len(tf):
109
+ trainer_dict[trainer[i]] = tf[i].get_text(strip=True)
110
+ course['Trainer'] = trainer_dict if trainer_dict else 'No trainer available'
111
+
112
+ # Description
113
+ description_elements = soup.find_all('div', class_='custom-theme')
114
+ course['Description'] = description_elements[0].text if description_elements else 'No description available'
115
+
116
+ # Curriculum
117
+ spans = soup.find_all('span', class_='course-curriculum__chapter-lesson')
118
+ curriculum = [span.get_text(strip=True) for span in spans]
119
+ course['Curriculum'] = curriculum if curriculum else 'No curriculum available'
120
+
121
+ # What should enroll & takeaway
122
+ wse_ta = soup.find_all('li', class_='checklist__list-item')
123
+ wa = [i.get_text(strip=True) for i in wse_ta]
124
+ course['What should enroll & takeaway'] = wa if wa else 'No what should enroll & takeaway available'
125
+
126
+ # FAQ
127
+ faq_list_items = soup.find_all('li', class_='faq__list-item')
128
+ faq_data = []
129
+ for item in faq_list_items:
130
+ question = item.find('strong')
131
+ answer = item.find('p')
132
+ if question and answer:
133
+ faq_data.append({
134
+ 'Question': question.text,
135
+ 'Answer': answer.text
136
+ })
137
+ course['FAQ'] = faq_data if faq_data else 'No FAQ available'
138
+
139
+ except Exception as e:
140
+ print(f"Error processing {course['Title']}: {str(e)}")
141
+ continue
142
+
143
+ time.sleep(1) # Respectful delay between requests
144
+
145
+ return courses
146
+
147
+ # Example usage:
148
+ courses = scrape_course_listings() # Get the initial courses
149
+ detailed_courses = scrape_course_details(courses) # Add details to each course
150
+
151
+
152
+ # Function to save data to CSV
153
+ def save_to_csv(courses):
154
+ df = pd.DataFrame(courses)
155
+ df.to_csv(CSV_FILE, index=False)
156
+ print(f"Data saved to {CSV_FILE}")
157
+
158
+
159
+ course_list = scrape_course_listings()
160
+ cr = scrape_course_details(course_list)
161
+ save_to_csv(cr)
162
+
app.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import os
4
+ from dotenv import load_dotenv
5
+ import google.generativeai as genai
6
+ from langchain_google_genai import GoogleGenerativeAIEmbeddings
7
+ from langchain_community.vectorstores import FAISS
8
+ import tempfile
9
+
10
+ # Load environment variables
11
+ load_dotenv()
12
+
13
+ class CourseSearchSystem:
14
+ def __init__(self):
15
+ """
16
+ Initialize the course search system with Google's Generative AI
17
+ """
18
+ genai.configure(api_key=os.getenv('GOOGLE_API_KEY'))
19
+ self.generation_model = genai.GenerativeModel('gemini-pro')
20
+ self.embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001")
21
+ self.vector_store = None
22
+ self.course_data = []
23
+
24
+ def process_course(self, row):
25
+ """
26
+ Process a single course row into a formatted string
27
+ """
28
+ return f"""
29
+ TITLE: {row['Title']}
30
+ BRIEF: {row['Brief']}
31
+ LEVEL: {row['Level']}
32
+ DURATION: {row['Duration']}
33
+ DESCRIPTION: {row['Description']}
34
+ URL: {row['Link']}
35
+ CURRICULUM: {row['Curriculum']}
36
+ TARGET AUDIENCE AND BENEFITS: {row['What should enroll & takeaway']}
37
+ """
38
+
39
+ def create_vector_store(self, df):
40
+ """
41
+ Create vector store from course data
42
+ """
43
+ texts = []
44
+ for _, row in df.iterrows():
45
+ doc = self.process_course(row)
46
+ texts.append(doc)
47
+ self.course_data.append({
48
+ 'title': row['Title'],
49
+ 'brief': row['Brief'],
50
+ 'level': row['Level'],
51
+ 'duration': row['Duration'],
52
+ 'url': row['Link'],
53
+ 'curriculum': row['Curriculum'],
54
+ 'target_audience': row['What should enroll & takeaway']
55
+ })
56
+
57
+ # Create vector store and store it directly in memory
58
+ self.vector_store = FAISS.from_texts(texts, self.embeddings)
59
+
60
+ def search_courses(self, query, k=3):
61
+ """
62
+ Search for relevant courses based on query
63
+ """
64
+ try:
65
+ if not self.vector_store:
66
+ return "Error: Search index not initialized.", []
67
+
68
+ # Perform similarity search using in-memory vector store
69
+ similar_docs = self.vector_store.similarity_search(query, k=k)
70
+
71
+ relevant_courses = []
72
+ relevant_chunks = []
73
+
74
+ for doc in similar_docs:
75
+ doc_content = doc.page_content
76
+ try:
77
+ idx = next(i for i, course in enumerate(self.course_data)
78
+ if course['title'] in doc_content)
79
+ relevant_courses.append(self.course_data[idx])
80
+ relevant_chunks.append(doc_content)
81
+ except StopIteration:
82
+ continue
83
+
84
+ if not relevant_courses:
85
+ return "No matching courses found for your query.", []
86
+
87
+ # Enhanced prompt for better analysis
88
+ context = f"""
89
+ Act as an experienced course advisor analyzing courses for a student interested in: "{query}"
90
+
91
+ Based on their interest, analyze these relevant courses:
92
+ {relevant_chunks}
93
+
94
+ Provide a detailed analysis that includes:
95
+ 1. Query Analysis: What specific learning needs or interests are indicated by this query
96
+ 2. Course Recommendations: For each relevant course:
97
+ - Explain why it matches the student's needs
98
+ - Highlight key features and benefits
99
+ - Specify who would benefit most from this course
100
+ 3. Best Match: Identify the most suitable course and explain why
101
+ 4. Learning Path: Suggest how the student might progress through these courses if relevant
102
+
103
+ Be specific in your analysis, mentioning course titles and concrete features.
104
+ Focus on how each course addresses the student's learning objectives.
105
+ """
106
+
107
+ try:
108
+ response = self.generation_model.generate_content(context)
109
+ return response.text, relevant_courses
110
+ except Exception as e:
111
+ return f"Error generating course analysis: {str(e)}", relevant_courses
112
+
113
+ except Exception as e:
114
+ return f"Error during course search: {str(e)}", []
115
+
116
+ def main():
117
+ """
118
+ Main function to run the Streamlit application
119
+ """
120
+ st.title("🎓 Intelligent Course Search Assistant")
121
+ st.write("Find the perfect course for your learning journey with AI-powered recommendations.")
122
+
123
+ @st.cache_resource
124
+ def initialize_search_system():
125
+ return CourseSearchSystem()
126
+
127
+ @st.cache_data
128
+ def load_and_process_data():
129
+ csv_path = r"data\detailed_courses.csv"
130
+ try:
131
+ df = pd.read_csv(csv_path)
132
+ return df
133
+ except FileNotFoundError:
134
+ st.error(f"Could not find the file: {csv_path}")
135
+ st.info("Please ensure the CSV file path is correct.")
136
+ return None
137
+
138
+ search_system = initialize_search_system()
139
+ df = load_and_process_data()
140
+
141
+ if df is not None:
142
+ if 'index_built' not in st.session_state:
143
+ with st.spinner("Building search index... This may take a moment."):
144
+ search_system.create_vector_store(df)
145
+ st.session_state.index_built = True
146
+
147
+ query = st.text_input("🔍 What would you like to learn?",
148
+ placeholder="Example: machine learning for beginners")
149
+
150
+ col1, col2 = st.columns([2, 1])
151
+ with col1:
152
+ search_button = st.button("Search Courses", use_container_width=True)
153
+
154
+ if query and search_button:
155
+ with st.spinner("Analyzing courses for you..."):
156
+ response, courses = search_system.search_courses(query)
157
+
158
+ if courses: # Only show analysis if courses were found
159
+ st.write("### 📊 Course Analysis")
160
+ st.write(response)
161
+
162
+ st.write("### 📚 Recommended Courses")
163
+ for course in courses:
164
+ with st.expander(f"📘 {course['title']}", expanded=True):
165
+ cols = st.columns([1, 1])
166
+ with cols[0]:
167
+ st.write(f"**Level:** {course['level']}")
168
+ st.write(f"**Duration:** {course['duration']}")
169
+
170
+ with cols[1]:
171
+ st.markdown(f"[**Enroll Now** 🚀]({course['url']})")
172
+
173
+ st.write("**Overview:**")
174
+ st.write(course['brief'])
175
+
176
+ with st.expander("📋 View Curriculum"):
177
+ st.write(course['curriculum'])
178
+
179
+ with st.expander("👥 Who Should Take This Course"):
180
+ st.write(course['target_audience'])
181
+ else:
182
+ st.warning("No courses found matching your query. Please try different search terms.")
183
+
184
+ if __name__ == "__main__":
185
+ main()
data/detailed_courses.csv ADDED
The diff for this file is too large to render. See raw diff
 
myenv/Include/site/python3.11/greenlet/greenlet.h ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
2
+
3
+ /* Greenlet object interface */
4
+
5
+ #ifndef Py_GREENLETOBJECT_H
6
+ #define Py_GREENLETOBJECT_H
7
+
8
+
9
+ #include <Python.h>
10
+
11
+ #ifdef __cplusplus
12
+ extern "C" {
13
+ #endif
14
+
15
+ /* This is deprecated and undocumented. It does not change. */
16
+ #define GREENLET_VERSION "1.0.0"
17
+
18
+ #ifndef GREENLET_MODULE
19
+ #define implementation_ptr_t void*
20
+ #endif
21
+
22
+ typedef struct _greenlet {
23
+ PyObject_HEAD
24
+ PyObject* weakreflist;
25
+ PyObject* dict;
26
+ implementation_ptr_t pimpl;
27
+ } PyGreenlet;
28
+
29
+ #define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type))
30
+
31
+
32
+ /* C API functions */
33
+
34
+ /* Total number of symbols that are exported */
35
+ #define PyGreenlet_API_pointers 12
36
+
37
+ #define PyGreenlet_Type_NUM 0
38
+ #define PyExc_GreenletError_NUM 1
39
+ #define PyExc_GreenletExit_NUM 2
40
+
41
+ #define PyGreenlet_New_NUM 3
42
+ #define PyGreenlet_GetCurrent_NUM 4
43
+ #define PyGreenlet_Throw_NUM 5
44
+ #define PyGreenlet_Switch_NUM 6
45
+ #define PyGreenlet_SetParent_NUM 7
46
+
47
+ #define PyGreenlet_MAIN_NUM 8
48
+ #define PyGreenlet_STARTED_NUM 9
49
+ #define PyGreenlet_ACTIVE_NUM 10
50
+ #define PyGreenlet_GET_PARENT_NUM 11
51
+
52
+ #ifndef GREENLET_MODULE
53
+ /* This section is used by modules that uses the greenlet C API */
54
+ static void** _PyGreenlet_API = NULL;
55
+
56
+ # define PyGreenlet_Type \
57
+ (*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM])
58
+
59
+ # define PyExc_GreenletError \
60
+ ((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM])
61
+
62
+ # define PyExc_GreenletExit \
63
+ ((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM])
64
+
65
+ /*
66
+ * PyGreenlet_New(PyObject *args)
67
+ *
68
+ * greenlet.greenlet(run, parent=None)
69
+ */
70
+ # define PyGreenlet_New \
71
+ (*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \
72
+ _PyGreenlet_API[PyGreenlet_New_NUM])
73
+
74
+ /*
75
+ * PyGreenlet_GetCurrent(void)
76
+ *
77
+ * greenlet.getcurrent()
78
+ */
79
+ # define PyGreenlet_GetCurrent \
80
+ (*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM])
81
+
82
+ /*
83
+ * PyGreenlet_Throw(
84
+ * PyGreenlet *greenlet,
85
+ * PyObject *typ,
86
+ * PyObject *val,
87
+ * PyObject *tb)
88
+ *
89
+ * g.throw(...)
90
+ */
91
+ # define PyGreenlet_Throw \
92
+ (*(PyObject * (*)(PyGreenlet * self, \
93
+ PyObject * typ, \
94
+ PyObject * val, \
95
+ PyObject * tb)) \
96
+ _PyGreenlet_API[PyGreenlet_Throw_NUM])
97
+
98
+ /*
99
+ * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args)
100
+ *
101
+ * g.switch(*args, **kwargs)
102
+ */
103
+ # define PyGreenlet_Switch \
104
+ (*(PyObject * \
105
+ (*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \
106
+ _PyGreenlet_API[PyGreenlet_Switch_NUM])
107
+
108
+ /*
109
+ * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent)
110
+ *
111
+ * g.parent = new_parent
112
+ */
113
+ # define PyGreenlet_SetParent \
114
+ (*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \
115
+ _PyGreenlet_API[PyGreenlet_SetParent_NUM])
116
+
117
+ /*
118
+ * PyGreenlet_GetParent(PyObject* greenlet)
119
+ *
120
+ * return greenlet.parent;
121
+ *
122
+ * This could return NULL even if there is no exception active.
123
+ * If it does not return NULL, you are responsible for decrementing the
124
+ * reference count.
125
+ */
126
+ # define PyGreenlet_GetParent \
127
+ (*(PyGreenlet* (*)(PyGreenlet*)) \
128
+ _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM])
129
+
130
+ /*
131
+ * deprecated, undocumented alias.
132
+ */
133
+ # define PyGreenlet_GET_PARENT PyGreenlet_GetParent
134
+
135
+ # define PyGreenlet_MAIN \
136
+ (*(int (*)(PyGreenlet*)) \
137
+ _PyGreenlet_API[PyGreenlet_MAIN_NUM])
138
+
139
+ # define PyGreenlet_STARTED \
140
+ (*(int (*)(PyGreenlet*)) \
141
+ _PyGreenlet_API[PyGreenlet_STARTED_NUM])
142
+
143
+ # define PyGreenlet_ACTIVE \
144
+ (*(int (*)(PyGreenlet*)) \
145
+ _PyGreenlet_API[PyGreenlet_ACTIVE_NUM])
146
+
147
+
148
+
149
+
150
+ /* Macro that imports greenlet and initializes C API */
151
+ /* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we
152
+ keep the older definition to be sure older code that might have a copy of
153
+ the header still works. */
154
+ # define PyGreenlet_Import() \
155
+ { \
156
+ _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \
157
+ }
158
+
159
+ #endif /* GREENLET_MODULE */
160
+
161
+ #ifdef __cplusplus
162
+ }
163
+ #endif
164
+ #endif /* !Py_GREENLETOBJECT_H */
myenv/Lib/site-packages/.DS_Store ADDED
Binary file (6.15 kB). View file
 
myenv/Lib/site-packages/GitPython-3.1.44.dist-info/AUTHORS ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ GitPython was originally written by Michael Trier.
2
+ GitPython 0.2 was partially (re)written by Sebastian Thiel, based on 0.1.6 and git-dulwich.
3
+
4
+ Contributors are:
5
+
6
+ -Michael Trier <mtrier _at_ gmail.com>
7
+ -Alan Briolat
8
+ -Florian Apolloner <florian _at_ apolloner.eu>
9
+ -David Aguilar <davvid _at_ gmail.com>
10
+ -Jelmer Vernooij <jelmer _at_ samba.org>
11
+ -Steve Frécinaux <code _at_ istique.net>
12
+ -Kai Lautaportti <kai _at_ lautaportti.fi>
13
+ -Paul Sowden <paul _at_ idontsmoke.co.uk>
14
+ -Sebastian Thiel <byronimo _at_ gmail.com>
15
+ -Jonathan Chu <jonathan.chu _at_ me.com>
16
+ -Vincent Driessen <me _at_ nvie.com>
17
+ -Phil Elson <pelson _dot_ pub _at_ gmail.com>
18
+ -Bernard `Guyzmo` Pratz <guyzmo+gitpython+pub@m0g.net>
19
+ -Timothy B. Hartman <tbhartman _at_ gmail.com>
20
+ -Konstantin Popov <konstantin.popov.89 _at_ yandex.ru>
21
+ -Peter Jones <pjones _at_ redhat.com>
22
+ -Anson Mansfield <anson.mansfield _at_ gmail.com>
23
+ -Ken Odegard <ken.odegard _at_ gmail.com>
24
+ -Alexis Horgix Chotard
25
+ -Piotr Babij <piotr.babij _at_ gmail.com>
26
+ -Mikuláš Poul <mikulaspoul _at_ gmail.com>
27
+ -Charles Bouchard-Légaré <cblegare.atl _at_ ntis.ca>
28
+ -Yaroslav Halchenko <debian _at_ onerussian.com>
29
+ -Tim Swast <swast _at_ google.com>
30
+ -William Luc Ritchie
31
+ -David Host <hostdm _at_ outlook.com>
32
+ -A. Jesse Jiryu Davis <jesse _at_ emptysquare.net>
33
+ -Steven Whitman <ninloot _at_ gmail.com>
34
+ -Stefan Stancu <stefan.stancu _at_ gmail.com>
35
+ -César Izurieta <cesar _at_ caih.org>
36
+ -Arthur Milchior <arthur _at_ milchior.fr>
37
+ -Anil Khatri <anil.soccer.khatri _at_ gmail.com>
38
+ -JJ Graham <thetwoj _at_ gmail.com>
39
+ -Ben Thayer <ben _at_ benthayer.com>
40
+ -Dries Kennes <admin _at_ dries007.net>
41
+ -Pratik Anurag <panurag247365 _at_ gmail.com>
42
+ -Harmon <harmon.public _at_ gmail.com>
43
+ -Liam Beguin <liambeguin _at_ gmail.com>
44
+ -Ram Rachum <ram _at_ rachum.com>
45
+ -Alba Mendez <me _at_ alba.sh>
46
+ -Robert Westman <robert _at_ byteflux.io>
47
+ -Hugo van Kemenade
48
+ -Hiroki Tokunaga <tokusan441 _at_ gmail.com>
49
+ -Julien Mauroy <pro.julien.mauroy _at_ gmail.com>
50
+ -Patrick Gerard
51
+ -Luke Twist <itsluketwist@gmail.com>
52
+ -Joseph Hale <me _at_ jhale.dev>
53
+ -Santos Gallegos <stsewd _at_ proton.me>
54
+ -Wenhan Zhu <wzhu.cosmos _at_ gmail.com>
55
+ -Eliah Kagan <eliah.kagan _at_ gmail.com>
56
+ -Ethan Lin <et.repositories _at_ gmail.com>
57
+ -Jonas Scharpf <jonas.scharpf _at_ checkmk.com>
58
+
59
+ Portions derived from other open source works and are clearly marked.
myenv/Lib/site-packages/GitPython-3.1.44.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
myenv/Lib/site-packages/GitPython-3.1.44.dist-info/LICENSE ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (C) 2008, 2009 Michael Trier and contributors
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions
6
+ are met:
7
+
8
+ * Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+
11
+ * Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+
15
+ * Neither the name of the GitPython project nor the names of
16
+ its contributors may be used to endorse or promote products derived
17
+ from this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
myenv/Lib/site-packages/GitPython-3.1.44.dist-info/METADATA ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.1
2
+ Name: GitPython
3
+ Version: 3.1.44
4
+ Summary: GitPython is a Python library used to interact with Git repositories
5
+ Home-page: https://github.com/gitpython-developers/GitPython
6
+ Author: Sebastian Thiel, Michael Trier
7
+ Author-email: byronimo@gmail.com, mtrier@gmail.com
8
+ License: BSD-3-Clause
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: BSD License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Operating System :: POSIX
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Operating System :: MacOS :: MacOS X
17
+ Classifier: Typing :: Typed
18
+ Classifier: Programming Language :: Python
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.7
21
+ Classifier: Programming Language :: Python :: 3.8
22
+ Classifier: Programming Language :: Python :: 3.9
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Requires-Python: >=3.7
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ License-File: AUTHORS
30
+ Requires-Dist: gitdb<5,>=4.0.1
31
+ Requires-Dist: typing-extensions>=3.7.4.3; python_version < "3.8"
32
+ Provides-Extra: test
33
+ Requires-Dist: coverage[toml]; extra == "test"
34
+ Requires-Dist: ddt!=1.4.3,>=1.1.1; extra == "test"
35
+ Requires-Dist: mock; python_version < "3.8" and extra == "test"
36
+ Requires-Dist: mypy; extra == "test"
37
+ Requires-Dist: pre-commit; extra == "test"
38
+ Requires-Dist: pytest>=7.3.1; extra == "test"
39
+ Requires-Dist: pytest-cov; extra == "test"
40
+ Requires-Dist: pytest-instafail; extra == "test"
41
+ Requires-Dist: pytest-mock; extra == "test"
42
+ Requires-Dist: pytest-sugar; extra == "test"
43
+ Requires-Dist: typing-extensions; python_version < "3.11" and extra == "test"
44
+ Provides-Extra: doc
45
+ Requires-Dist: sphinx<7.2,>=7.1.2; extra == "doc"
46
+ Requires-Dist: sphinx_rtd_theme; extra == "doc"
47
+ Requires-Dist: sphinx-autodoc-typehints; extra == "doc"
48
+
49
+ ![Python package](https://github.com/gitpython-developers/GitPython/workflows/Python%20package/badge.svg)
50
+ [![Documentation Status](https://readthedocs.org/projects/gitpython/badge/?version=stable)](https://readthedocs.org/projects/gitpython/?badge=stable)
51
+ [![Packaging status](https://repology.org/badge/tiny-repos/python:gitpython.svg)](https://repology.org/metapackage/python:gitpython/versions)
52
+
53
+ ## [Gitoxide](https://github.com/Byron/gitoxide): A peek into the future…
54
+
55
+ I started working on GitPython in 2009, back in the days when Python was 'my thing' and I had great plans with it.
56
+ Of course, back in the days, I didn't really know what I was doing and this shows in many places. Somewhat similar to
57
+ Python this happens to be 'good enough', but at the same time is deeply flawed and broken beyond repair.
58
+
59
+ By now, GitPython is widely used and I am sure there is a good reason for that, it's something to be proud of and happy about.
60
+ The community is maintaining the software and is keeping it relevant for which I am absolutely grateful. For the time to come I am happy to continue maintaining GitPython, remaining hopeful that one day it won't be needed anymore.
61
+
62
+ More than 15 years after my first meeting with 'git' I am still in excited about it, and am happy to finally have the tools and
63
+ probably the skills to scratch that itch of mine: implement `git` in a way that makes tool creation a piece of cake for most.
64
+
65
+ If you like the idea and want to learn more, please head over to [gitoxide](https://github.com/Byron/gitoxide), an
66
+ implementation of 'git' in [Rust](https://www.rust-lang.org).
67
+
68
+ *(Please note that `gitoxide` is not currently available for use in Python, and that Rust is required.)*
69
+
70
+ ## GitPython
71
+
72
+ GitPython is a python library used to interact with git repositories, high-level like git-porcelain,
73
+ or low-level like git-plumbing.
74
+
75
+ It provides abstractions of git objects for easy access of repository data often backed by calling the `git`
76
+ command-line program.
77
+
78
+ ### DEVELOPMENT STATUS
79
+
80
+ This project is in **maintenance mode**, which means that
81
+
82
+ - …there will be no feature development, unless these are contributed
83
+ - …there will be no bug fixes, unless they are relevant to the safety of users, or contributed
84
+ - …issues will be responded to with waiting times of up to a month
85
+
86
+ The project is open to contributions of all kinds, as well as new maintainers.
87
+
88
+ ### REQUIREMENTS
89
+
90
+ GitPython needs the `git` executable to be installed on the system and available in your
91
+ `PATH` for most operations. If it is not in your `PATH`, you can help GitPython find it
92
+ by setting the `GIT_PYTHON_GIT_EXECUTABLE=<path/to/git>` environment variable.
93
+
94
+ - Git (1.7.x or newer)
95
+ - Python >= 3.7
96
+
97
+ The list of dependencies are listed in `./requirements.txt` and `./test-requirements.txt`.
98
+ The installer takes care of installing them for you.
99
+
100
+ ### INSTALL
101
+
102
+ GitPython and its required package dependencies can be installed in any of the following ways, all of which should typically be done in a [virtual environment](https://docs.python.org/3/tutorial/venv.html).
103
+
104
+ #### From PyPI
105
+
106
+ To obtain and install a copy [from PyPI](https://pypi.org/project/GitPython/), run:
107
+
108
+ ```sh
109
+ pip install GitPython
110
+ ```
111
+
112
+ (A distribution package can also be downloaded for manual installation at [the PyPI page](https://pypi.org/project/GitPython/).)
113
+
114
+ #### From downloaded source code
115
+
116
+ If you have downloaded the source code, run this from inside the unpacked `GitPython` directory:
117
+
118
+ ```sh
119
+ pip install .
120
+ ```
121
+
122
+ #### By cloning the source code repository
123
+
124
+ To clone the [the GitHub repository](https://github.com/gitpython-developers/GitPython) from source to work on the code, you can do it like so:
125
+
126
+ ```sh
127
+ git clone https://github.com/gitpython-developers/GitPython
128
+ cd GitPython
129
+ ./init-tests-after-clone.sh
130
+ ```
131
+
132
+ On Windows, `./init-tests-after-clone.sh` can be run in a Git Bash shell.
133
+
134
+ If you are cloning [your own fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks), then replace the above `git clone` command with one that gives the URL of your fork. Or use this [`gh`](https://cli.github.com/) command (assuming you have `gh` and your fork is called `GitPython`):
135
+
136
+ ```sh
137
+ gh repo clone GitPython
138
+ ```
139
+
140
+ Having cloned the repo, create and activate your [virtual environment](https://docs.python.org/3/tutorial/venv.html).
141
+
142
+ Then make an [editable install](https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs):
143
+
144
+ ```sh
145
+ pip install -e ".[test]"
146
+ ```
147
+
148
+ In the less common case that you do not want to install test dependencies, `pip install -e .` can be used instead.
149
+
150
+ #### With editable *dependencies* (not preferred, and rarely needed)
151
+
152
+ In rare cases, you may want to work on GitPython and one or both of its [gitdb](https://github.com/gitpython-developers/gitdb) and [smmap](https://github.com/gitpython-developers/smmap) dependencies at the same time, with changes in your local working copy of gitdb or smmap immediately reflected in the behavior of your local working copy of GitPython. This can be done by making editable installations of those dependencies in the same virtual environment where you install GitPython.
153
+
154
+ If you want to do that *and* you want the versions in GitPython's git submodules to be used, then pass `-e git/ext/gitdb` and/or `-e git/ext/gitdb/gitdb/ext/smmap` to `pip install`. This can be done in any order, and in separate `pip install` commands or the same one, so long as `-e` appears before *each* path. For example, you can install GitPython, gitdb, and smmap editably in the currently active virtual environment this way:
155
+
156
+ ```sh
157
+ pip install -e ".[test]" -e git/ext/gitdb -e git/ext/gitdb/gitdb/ext/smmap
158
+ ```
159
+
160
+ The submodules must have been cloned for that to work, but that will already be the case if you have run `./init-tests-after-clone.sh`. You can use `pip list` to check which packages are installed editably and which are installed normally.
161
+
162
+ To reiterate, this approach should only rarely be used. For most development it is preferable to allow the gitdb and smmap dependencices to be retrieved automatically from PyPI in their latest stable packaged versions.
163
+
164
+ ### Limitations
165
+
166
+ #### Leakage of System Resources
167
+
168
+ GitPython is not suited for long-running processes (like daemons) as it tends to
169
+ leak system resources. It was written in a time where destructors (as implemented
170
+ in the `__del__` method) still ran deterministically.
171
+
172
+ In case you still want to use it in such a context, you will want to search the
173
+ codebase for `__del__` implementations and call these yourself when you see fit.
174
+
175
+ Another way assure proper cleanup of resources is to factor out GitPython into a
176
+ separate process which can be dropped periodically.
177
+
178
+ #### Windows support
179
+
180
+ See [Issue #525](https://github.com/gitpython-developers/GitPython/issues/525).
181
+
182
+ ### RUNNING TESTS
183
+
184
+ _Important_: Right after cloning this repository, please be sure to have executed
185
+ the `./init-tests-after-clone.sh` script in the repository root. Otherwise
186
+ you will encounter test failures.
187
+
188
+ #### Install test dependencies
189
+
190
+ Ensure testing libraries are installed. This is taken care of already if you installed with:
191
+
192
+ ```sh
193
+ pip install -e ".[test]"
194
+ ```
195
+
196
+ If you had installed with a command like `pip install -e .` instead, you can still run
197
+ the above command to add the testing dependencies.
198
+
199
+ #### Test commands
200
+
201
+ To test, run:
202
+
203
+ ```sh
204
+ pytest
205
+ ```
206
+
207
+ To lint, and apply some linting fixes as well as automatic code formatting, run:
208
+
209
+ ```sh
210
+ pre-commit run --all-files
211
+ ```
212
+
213
+ This includes the linting and autoformatting done by Ruff, as well as some other checks.
214
+
215
+ To typecheck, run:
216
+
217
+ ```sh
218
+ mypy
219
+ ```
220
+
221
+ #### CI (and tox)
222
+
223
+ Style and formatting checks, and running tests on all the different supported Python versions, will be performed:
224
+
225
+ - Upon submitting a pull request.
226
+ - On each push, *if* you have a fork with GitHub Actions enabled.
227
+ - Locally, if you run [`tox`](https://tox.wiki/) (this skips any Python versions you don't have installed).
228
+
229
+ #### Configuration files
230
+
231
+ Specific tools are all configured in the `./pyproject.toml` file:
232
+
233
+ - `pytest` (test runner)
234
+ - `coverage.py` (code coverage)
235
+ - `ruff` (linter and formatter)
236
+ - `mypy` (type checker)
237
+
238
+ Orchestration tools:
239
+
240
+ - Configuration for `pre-commit` is in the `./.pre-commit-config.yaml` file.
241
+ - Configuration for `tox` is in `./tox.ini`.
242
+ - Configuration for GitHub Actions (CI) is in files inside `./.github/workflows/`.
243
+
244
+ ### Contributions
245
+
246
+ Please have a look at the [contributions file][contributing].
247
+
248
+ ### INFRASTRUCTURE
249
+
250
+ - [User Documentation](http://gitpython.readthedocs.org)
251
+ - [Questions and Answers](http://stackexchange.com/filters/167317/gitpython)
252
+ - Please post on Stack Overflow and use the `gitpython` tag
253
+ - [Issue Tracker](https://github.com/gitpython-developers/GitPython/issues)
254
+ - Post reproducible bugs and feature requests as a new issue.
255
+ Please be sure to provide the following information if posting bugs:
256
+ - GitPython version (e.g. `import git; git.__version__`)
257
+ - Python version (e.g. `python --version`)
258
+ - The encountered stack-trace, if applicable
259
+ - Enough information to allow reproducing the issue
260
+
261
+ ### How to make a new release
262
+
263
+ 1. Update/verify the **version** in the `VERSION` file.
264
+ 2. Update/verify that the `doc/source/changes.rst` changelog file was updated. It should include a link to the forthcoming release page: `https://github.com/gitpython-developers/GitPython/releases/tag/<version>`
265
+ 3. Commit everything.
266
+ 4. Run `git tag -s <version>` to tag the version in Git.
267
+ 5. _Optionally_ create and activate a [virtual environment](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment). (Then the next step can install `build` and `twine`.)
268
+ 6. Run `make release`.
269
+ 7. Go to [GitHub Releases](https://github.com/gitpython-developers/GitPython/releases) and publish a new one with the recently pushed tag. Generate the changelog.
270
+
271
+ ### Projects using GitPython
272
+
273
+ - [PyDriller](https://github.com/ishepard/pydriller)
274
+ - [Kivy Designer](https://github.com/kivy/kivy-designer)
275
+ - [Prowl](https://github.com/nettitude/Prowl)
276
+ - [Python Taint](https://github.com/python-security/pyt)
277
+ - [Buster](https://github.com/axitkhurana/buster)
278
+ - [git-ftp](https://github.com/ezyang/git-ftp)
279
+ - [Git-Pandas](https://github.com/wdm0006/git-pandas)
280
+ - [PyGitUp](https://github.com/msiemens/PyGitUp)
281
+ - [PyJFuzz](https://github.com/mseclab/PyJFuzz)
282
+ - [Loki](https://github.com/Neo23x0/Loki)
283
+ - [Omniwallet](https://github.com/OmniLayer/omniwallet)
284
+ - [GitViper](https://github.com/BeayemX/GitViper)
285
+ - [Git Gud](https://github.com/bthayer2365/git-gud)
286
+
287
+ ### LICENSE
288
+
289
+ [3-Clause BSD License](https://opensource.org/license/bsd-3-clause/), also known as the New BSD License. See the [LICENSE file][license].
290
+
291
+ One file exclusively used for fuzz testing is subject to [a separate license, detailed here](./fuzzing/README.md#license).
292
+ This file is not included in the wheel or sdist packages published by the maintainers of GitPython.
293
+
294
+ [contributing]: https://github.com/gitpython-developers/GitPython/blob/main/CONTRIBUTING.md
295
+ [license]: https://github.com/gitpython-developers/GitPython/blob/main/LICENSE
myenv/Lib/site-packages/GitPython-3.1.44.dist-info/RECORD ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ GitPython-3.1.44.dist-info/AUTHORS,sha256=tZ9LuyBks2V2HKTPK7kCmtd9Guu_LyU1oZHvU0NiAok,2334
2
+ GitPython-3.1.44.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
3
+ GitPython-3.1.44.dist-info/LICENSE,sha256=hvyUwyGpr7wRUUcTURuv3tIl8lEA3MD3NQ6CvCMbi-s,1503
4
+ GitPython-3.1.44.dist-info/METADATA,sha256=0O_Fr2Y7A-DlPYhlbSxGjblBC2mWkw3USNUhyL80Ip8,13245
5
+ GitPython-3.1.44.dist-info/RECORD,,
6
+ GitPython-3.1.44.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
7
+ GitPython-3.1.44.dist-info/top_level.txt,sha256=0hzDuIp8obv624V3GmbqsagBWkk8ohtGU-Bc1PmTT0o,4
8
+ git/__init__.py,sha256=nkQImgv-bWdiZOFDjzN-gbt93FoRHD0nY6_t9LQxy4Y,8899
9
+ git/__pycache__/__init__.cpython-311.pyc,,
10
+ git/__pycache__/cmd.cpython-311.pyc,,
11
+ git/__pycache__/compat.cpython-311.pyc,,
12
+ git/__pycache__/config.cpython-311.pyc,,
13
+ git/__pycache__/db.cpython-311.pyc,,
14
+ git/__pycache__/diff.cpython-311.pyc,,
15
+ git/__pycache__/exc.cpython-311.pyc,,
16
+ git/__pycache__/remote.cpython-311.pyc,,
17
+ git/__pycache__/types.cpython-311.pyc,,
18
+ git/__pycache__/util.cpython-311.pyc,,
19
+ git/cmd.py,sha256=QwiaBy0mFbi9xjRKhRgUVK-_-K6xVdFqh9l0cxPqPSc,67724
20
+ git/compat.py,sha256=y1E6y6O2q5r8clSlr8ZNmuIWG9nmHuehQEsVsmBffs8,4526
21
+ git/config.py,sha256=vTUlK6d8ORqFqjOv4Vbq_Hm-5mp-jOAt1dkq0IdzJ3U,34933
22
+ git/db.py,sha256=vIW9uWSbqu99zbuU2ZDmOhVOv1UPTmxrnqiCtRHCfjE,2368
23
+ git/diff.py,sha256=wmpMCIdMiVOqreGVPOGYyO4gFboGOAicyrvvI7PPjEg,27095
24
+ git/exc.py,sha256=Gc7g1pHpn8OmTse30NHmJVsBJ2CYH8LxaR8y8UA3lIM,7119
25
+ git/index/__init__.py,sha256=i-Nqb8Lufp9aFbmxpQBORmmQnjEVVM1Pn58fsQkyGgQ,406
26
+ git/index/__pycache__/__init__.cpython-311.pyc,,
27
+ git/index/__pycache__/base.cpython-311.pyc,,
28
+ git/index/__pycache__/fun.cpython-311.pyc,,
29
+ git/index/__pycache__/typ.cpython-311.pyc,,
30
+ git/index/__pycache__/util.cpython-311.pyc,,
31
+ git/index/base.py,sha256=nDD7XVLNbgBKpJMrrTVyHBy6NVLWgDkk7oUw6ZOegPc,60808
32
+ git/index/fun.py,sha256=37cA3DBC9vpAnSVu5TGA072SnoF5XZOkOukExwlejHs,16736
33
+ git/index/typ.py,sha256=uuKNwitUw83FhVaLSwo4pY7PHDQudtZTLJrLGym4jcI,6570
34
+ git/index/util.py,sha256=fULi7GPG-MvprKrRCD5c15GNdzku_1E38We0d97WB3A,3659
35
+ git/objects/__init__.py,sha256=O6ZL_olX7e5-8iIbKviRPkVSJxN37WA-EC0q9d48U5Y,637
36
+ git/objects/__pycache__/__init__.cpython-311.pyc,,
37
+ git/objects/__pycache__/base.cpython-311.pyc,,
38
+ git/objects/__pycache__/blob.cpython-311.pyc,,
39
+ git/objects/__pycache__/commit.cpython-311.pyc,,
40
+ git/objects/__pycache__/fun.cpython-311.pyc,,
41
+ git/objects/__pycache__/tag.cpython-311.pyc,,
42
+ git/objects/__pycache__/tree.cpython-311.pyc,,
43
+ git/objects/__pycache__/util.cpython-311.pyc,,
44
+ git/objects/base.py,sha256=0dqNkSRVH0mk0-7ZKIkGBK7iNYrzLTVxwQFUd6CagsE,10277
45
+ git/objects/blob.py,sha256=zwwq0KfOMYeP5J2tW5CQatoLyeqFRlfkxP1Vwx1h07s,1215
46
+ git/objects/commit.py,sha256=GH1_83C9t7RGTukwozTHDgvxYQPRjTHhPDkXJyBbJyo,30553
47
+ git/objects/fun.py,sha256=B4jCqhAjm6Hl79GK58FPzW1H9K6Wc7Tx0rssyWmAcEE,8935
48
+ git/objects/submodule/__init__.py,sha256=6xySp767LVz3UylWgUalntS_nGXRuVzXxDuFAv_Wc2c,303
49
+ git/objects/submodule/__pycache__/__init__.cpython-311.pyc,,
50
+ git/objects/submodule/__pycache__/base.cpython-311.pyc,,
51
+ git/objects/submodule/__pycache__/root.cpython-311.pyc,,
52
+ git/objects/submodule/__pycache__/util.cpython-311.pyc,,
53
+ git/objects/submodule/base.py,sha256=MQ-2xV8JznGwy2hLQv1aeQNgAkhBhgc5tdtClFL3DmE,63901
54
+ git/objects/submodule/root.py,sha256=5eTtYNHasqdPq6q0oDCPr7IaO6uAHL3b4DxMoiO2LhE,20246
55
+ git/objects/submodule/util.py,sha256=sQqAYaiSJdFkZa9NlAuK_wTsMNiS-kkQnQjvIoJtc_o,3509
56
+ git/objects/tag.py,sha256=jAGESnpmTEv-dLakPzheT5ILZFFArcItnXYqfxfDrgc,4441
57
+ git/objects/tree.py,sha256=jJH888SHiP4dGzE-ra1yenQOyya_0C_MkHr06c1gHpM,13849
58
+ git/objects/util.py,sha256=Nlza4zLgdPmr_Yasyvvs6c1rKtW_wMxI6wDmQpQ3ufw,23846
59
+ git/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
+ git/refs/__init__.py,sha256=DWlJNnsx-4jM_E-VycbP-FZUdn6iWhjnH_uZ_pZXBro,509
61
+ git/refs/__pycache__/__init__.cpython-311.pyc,,
62
+ git/refs/__pycache__/head.cpython-311.pyc,,
63
+ git/refs/__pycache__/log.cpython-311.pyc,,
64
+ git/refs/__pycache__/reference.cpython-311.pyc,,
65
+ git/refs/__pycache__/remote.cpython-311.pyc,,
66
+ git/refs/__pycache__/symbolic.cpython-311.pyc,,
67
+ git/refs/__pycache__/tag.cpython-311.pyc,,
68
+ git/refs/head.py,sha256=SGa3N301HfAi79X6UR5Mcg7mO9TnCH3Bk549kHlJVaQ,10513
69
+ git/refs/log.py,sha256=kXiuAgTo1DIuM_BfbDUk9gQ0YO-mutIMVdHv1_ES90o,12493
70
+ git/refs/reference.py,sha256=l6mhF4YLSEwtjz6b9PpOQH-fkng7EYWMaJhkjn-2jXA,5630
71
+ git/refs/remote.py,sha256=WwqV9T7BbYf3F_WZNUQivu9xktIIKGklCjDpwQrhD-A,2806
72
+ git/refs/symbolic.py,sha256=c8zOwaqzcg-J-rGrpuWdvh8zwMvSUqAHghd4vJoYG_s,34552
73
+ git/refs/tag.py,sha256=kgzV2vhpL4FD2TqHb0BJuMRAHgAvJF-TcoyWlaB-djQ,5010
74
+ git/remote.py,sha256=pYn9dAlz-QwvNMWXD1M57pMPQitthOM86qTRK_cpTqU,46786
75
+ git/repo/__init__.py,sha256=CILSVH36fX_WxVFSjD9o1WF5LgsNedPiJvSngKZqfVU,210
76
+ git/repo/__pycache__/__init__.cpython-311.pyc,,
77
+ git/repo/__pycache__/base.cpython-311.pyc,,
78
+ git/repo/__pycache__/fun.cpython-311.pyc,,
79
+ git/repo/base.py,sha256=0GU6nKNdT8SYjDI5Y5DeZ1zCEX3tHeq1VW2MSpne05g,59891
80
+ git/repo/fun.py,sha256=HSGC0-rqeKKx9fDg7JyQyMZgIwUWn-FnSZR_gRGpG-E,13573
81
+ git/types.py,sha256=MQzIDEOnoueXGsAJF_0MgUc_osH7Eu0Sw3DQofYzCVE,10272
82
+ git/util.py,sha256=2uAv34zZ_827-zJ3-D5ACrVH-4Q4EO_KLUTH23zi2AI,43770
myenv/Lib/site-packages/GitPython-3.1.44.dist-info/WHEEL ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.6.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
myenv/Lib/site-packages/GitPython-3.1.44.dist-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ git
myenv/Lib/site-packages/adodbapi/__init__.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """adodbapi - A python DB API 2.0 (PEP 249) interface to Microsoft ADO
2
+
3
+ Copyright (C) 2002 Henrik Ekelund, version 2.1 by Vernon Cole
4
+ * http://sourceforge.net/projects/adodbapi
5
+ """
6
+
7
+ import time
8
+
9
+ # Re-exports to keep backward compatibility with existing code
10
+ from .adodbapi import (
11
+ Connection as Connection,
12
+ Cursor as Cursor,
13
+ __version__,
14
+ connect as connect,
15
+ dateconverter,
16
+ )
17
+ from .apibase import (
18
+ BINARY as BINARY,
19
+ DATETIME as DATETIME,
20
+ NUMBER as NUMBER,
21
+ ROWID as ROWID,
22
+ STRING as STRING,
23
+ DatabaseError as DatabaseError,
24
+ DataError as DataError,
25
+ Error as Error,
26
+ FetchFailedError as FetchFailedError,
27
+ IntegrityError as IntegrityError,
28
+ InterfaceError as InterfaceError,
29
+ InternalError as InternalError,
30
+ NotSupportedError as NotSupportedError,
31
+ OperationalError as OperationalError,
32
+ ProgrammingError as ProgrammingError,
33
+ Warning as Warning,
34
+ apilevel as apilevel,
35
+ paramstyle as paramstyle,
36
+ threadsafety as threadsafety,
37
+ )
38
+
39
+
40
+ def Binary(aString):
41
+ """This function constructs an object capable of holding a binary (long) string value."""
42
+ return bytes(aString)
43
+
44
+
45
+ def Date(year, month, day):
46
+ "This function constructs an object holding a date value."
47
+ return dateconverter.Date(year, month, day)
48
+
49
+
50
+ def Time(hour, minute, second):
51
+ "This function constructs an object holding a time value."
52
+ return dateconverter.Time(hour, minute, second)
53
+
54
+
55
+ def Timestamp(year, month, day, hour, minute, second):
56
+ "This function constructs an object holding a time stamp value."
57
+ return dateconverter.Timestamp(year, month, day, hour, minute, second)
58
+
59
+
60
+ def DateFromTicks(ticks):
61
+ """This function constructs an object holding a date value from the given ticks value
62
+ (number of seconds since the epoch; see the documentation of the standard Python time module for details).
63
+ """
64
+ return Date(*time.gmtime(ticks)[:3])
65
+
66
+
67
+ def TimeFromTicks(ticks):
68
+ """This function constructs an object holding a time value from the given ticks value
69
+ (number of seconds since the epoch; see the documentation of the standard Python time module for details).
70
+ """
71
+ return Time(*time.gmtime(ticks)[3:6])
72
+
73
+
74
+ def TimestampFromTicks(ticks):
75
+ """This function constructs an object holding a time stamp value from the given
76
+ ticks value (number of seconds since the epoch;
77
+ see the documentation of the standard Python time module for details)."""
78
+ return Timestamp(*time.gmtime(ticks)[:6])
79
+
80
+
81
+ version = "adodbapi v" + __version__
myenv/Lib/site-packages/adodbapi/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (3.64 kB). View file
 
myenv/Lib/site-packages/adodbapi/__pycache__/ado_consts.cpython-311.pyc ADDED
Binary file (6.76 kB). View file
 
myenv/Lib/site-packages/adodbapi/__pycache__/adodbapi.cpython-311.pyc ADDED
Binary file (51.9 kB). View file
 
myenv/Lib/site-packages/adodbapi/__pycache__/apibase.cpython-311.pyc ADDED
Binary file (30.8 kB). View file
 
myenv/Lib/site-packages/adodbapi/__pycache__/is64bit.cpython-311.pyc ADDED
Binary file (1.52 kB). View file
 
myenv/Lib/site-packages/adodbapi/__pycache__/process_connect_string.cpython-311.pyc ADDED
Binary file (5.28 kB). View file
 
myenv/Lib/site-packages/adodbapi/__pycache__/schema_table.cpython-311.pyc ADDED
Binary file (922 Bytes). View file
 
myenv/Lib/site-packages/adodbapi/__pycache__/setup.cpython-311.pyc ADDED
Binary file (2.82 kB). View file
 
myenv/Lib/site-packages/adodbapi/ado_consts.py ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ADO enumerated constants documented on MSDN:
2
+ # http://msdn.microsoft.com/en-us/library/ms678353(VS.85).aspx
3
+
4
+ # IsolationLevelEnum
5
+ adXactUnspecified = -1
6
+ adXactBrowse = 0x100
7
+ adXactChaos = 0x10
8
+ adXactCursorStability = 0x1000
9
+ adXactIsolated = 0x100000
10
+ adXactReadCommitted = 0x1000
11
+ adXactReadUncommitted = 0x100
12
+ adXactRepeatableRead = 0x10000
13
+ adXactSerializable = 0x100000
14
+
15
+ # CursorLocationEnum
16
+ adUseClient = 3
17
+ adUseServer = 2
18
+
19
+ # CursorTypeEnum
20
+ adOpenDynamic = 2
21
+ adOpenForwardOnly = 0
22
+ adOpenKeyset = 1
23
+ adOpenStatic = 3
24
+ adOpenUnspecified = -1
25
+
26
+ # CommandTypeEnum
27
+ adCmdText = 1
28
+ adCmdStoredProc = 4
29
+ adSchemaTables = 20
30
+
31
+ # ParameterDirectionEnum
32
+ adParamInput = 1
33
+ adParamInputOutput = 3
34
+ adParamOutput = 2
35
+ adParamReturnValue = 4
36
+ adParamUnknown = 0
37
+ directions = {
38
+ 0: "Unknown",
39
+ 1: "Input",
40
+ 2: "Output",
41
+ 3: "InputOutput",
42
+ 4: "Return",
43
+ }
44
+
45
+
46
+ def ado_direction_name(ado_dir):
47
+ try:
48
+ return "adParam" + directions[ado_dir]
49
+ except:
50
+ return f"unknown direction ({ado_dir})"
51
+
52
+
53
+ # ObjectStateEnum
54
+ adStateClosed = 0
55
+ adStateOpen = 1
56
+ adStateConnecting = 2
57
+ adStateExecuting = 4
58
+ adStateFetching = 8
59
+
60
+ # FieldAttributeEnum
61
+ adFldMayBeNull = 0x40
62
+
63
+ # ConnectModeEnum
64
+ adModeUnknown = 0
65
+ adModeRead = 1
66
+ adModeWrite = 2
67
+ adModeReadWrite = 3
68
+ adModeShareDenyRead = 4
69
+ adModeShareDenyWrite = 8
70
+ adModeShareExclusive = 12
71
+ adModeShareDenyNone = 16
72
+ adModeRecursive = 0x400000
73
+
74
+ # XactAttributeEnum
75
+ adXactCommitRetaining = 131072
76
+ adXactAbortRetaining = 262144
77
+
78
+ ado_error_TIMEOUT = -2147217871
79
+
80
+ # DataTypeEnum - ADO Data types documented at:
81
+ # http://msdn2.microsoft.com/en-us/library/ms675318.aspx
82
+ adArray = 0x2000
83
+ adEmpty = 0x0
84
+ adBSTR = 0x8
85
+ adBigInt = 0x14
86
+ adBinary = 0x80
87
+ adBoolean = 0xB
88
+ adChapter = 0x88
89
+ adChar = 0x81
90
+ adCurrency = 0x6
91
+ adDBDate = 0x85
92
+ adDBTime = 0x86
93
+ adDBTimeStamp = 0x87
94
+ adDate = 0x7
95
+ adDecimal = 0xE
96
+ adDouble = 0x5
97
+ adError = 0xA
98
+ adFileTime = 0x40
99
+ adGUID = 0x48
100
+ adIDispatch = 0x9
101
+ adIUnknown = 0xD
102
+ adInteger = 0x3
103
+ adLongVarBinary = 0xCD
104
+ adLongVarChar = 0xC9
105
+ adLongVarWChar = 0xCB
106
+ adNumeric = 0x83
107
+ adPropVariant = 0x8A
108
+ adSingle = 0x4
109
+ adSmallInt = 0x2
110
+ adTinyInt = 0x10
111
+ adUnsignedBigInt = 0x15
112
+ adUnsignedInt = 0x13
113
+ adUnsignedSmallInt = 0x12
114
+ adUnsignedTinyInt = 0x11
115
+ adUserDefined = 0x84
116
+ adVarBinary = 0xCC
117
+ adVarChar = 0xC8
118
+ adVarNumeric = 0x8B
119
+ adVarWChar = 0xCA
120
+ adVariant = 0xC
121
+ adWChar = 0x82
122
+ # Additional constants used by introspection but not ADO itself
123
+ AUTO_FIELD_MARKER = -1000
124
+
125
+ adTypeNames = {
126
+ adBSTR: "adBSTR",
127
+ adBigInt: "adBigInt",
128
+ adBinary: "adBinary",
129
+ adBoolean: "adBoolean",
130
+ adChapter: "adChapter",
131
+ adChar: "adChar",
132
+ adCurrency: "adCurrency",
133
+ adDBDate: "adDBDate",
134
+ adDBTime: "adDBTime",
135
+ adDBTimeStamp: "adDBTimeStamp",
136
+ adDate: "adDate",
137
+ adDecimal: "adDecimal",
138
+ adDouble: "adDouble",
139
+ adEmpty: "adEmpty",
140
+ adError: "adError",
141
+ adFileTime: "adFileTime",
142
+ adGUID: "adGUID",
143
+ adIDispatch: "adIDispatch",
144
+ adIUnknown: "adIUnknown",
145
+ adInteger: "adInteger",
146
+ adLongVarBinary: "adLongVarBinary",
147
+ adLongVarChar: "adLongVarChar",
148
+ adLongVarWChar: "adLongVarWChar",
149
+ adNumeric: "adNumeric",
150
+ adPropVariant: "adPropVariant",
151
+ adSingle: "adSingle",
152
+ adSmallInt: "adSmallInt",
153
+ adTinyInt: "adTinyInt",
154
+ adUnsignedBigInt: "adUnsignedBigInt",
155
+ adUnsignedInt: "adUnsignedInt",
156
+ adUnsignedSmallInt: "adUnsignedSmallInt",
157
+ adUnsignedTinyInt: "adUnsignedTinyInt",
158
+ adUserDefined: "adUserDefined",
159
+ adVarBinary: "adVarBinary",
160
+ adVarChar: "adVarChar",
161
+ adVarNumeric: "adVarNumeric",
162
+ adVarWChar: "adVarWChar",
163
+ adVariant: "adVariant",
164
+ adWChar: "adWChar",
165
+ }
166
+
167
+
168
+ def ado_type_name(ado_type):
169
+ return adTypeNames.get(ado_type, f"unknown type ({ado_type})")
170
+
171
+
172
+ # here in decimal, sorted by value
173
+ # adEmpty 0 Specifies no value (DBTYPE_EMPTY).
174
+ # adSmallInt 2 Indicates a two-byte signed integer (DBTYPE_I2).
175
+ # adInteger 3 Indicates a four-byte signed integer (DBTYPE_I4).
176
+ # adSingle 4 Indicates a single-precision floating-point value (DBTYPE_R4).
177
+ # adDouble 5 Indicates a double-precision floating-point value (DBTYPE_R8).
178
+ # adCurrency 6 Indicates a currency value (DBTYPE_CY). Currency is a fixed-point number
179
+ # with four digits to the right of the decimal point. It is stored in an eight-byte signed integer scaled by 10,000.
180
+ # adDate 7 Indicates a date value (DBTYPE_DATE). A date is stored as a double, the whole part of which is
181
+ # the number of days since December 30, 1899, and the fractional part of which is the fraction of a day.
182
+ # adBSTR 8 Indicates a null-terminated character string (Unicode) (DBTYPE_BSTR).
183
+ # adIDispatch 9 Indicates a pointer to an IDispatch interface on a COM object (DBTYPE_IDISPATCH).
184
+ # adError 10 Indicates a 32-bit error code (DBTYPE_ERROR).
185
+ # adBoolean 11 Indicates a boolean value (DBTYPE_BOOL).
186
+ # adVariant 12 Indicates an Automation Variant (DBTYPE_VARIANT).
187
+ # adIUnknown 13 Indicates a pointer to an IUnknown interface on a COM object (DBTYPE_IUNKNOWN).
188
+ # adDecimal 14 Indicates an exact numeric value with a fixed precision and scale (DBTYPE_DECIMAL).
189
+ # adTinyInt 16 Indicates a one-byte signed integer (DBTYPE_I1).
190
+ # adUnsignedTinyInt 17 Indicates a one-byte unsigned integer (DBTYPE_UI1).
191
+ # adUnsignedSmallInt 18 Indicates a two-byte unsigned integer (DBTYPE_UI2).
192
+ # adUnsignedInt 19 Indicates a four-byte unsigned integer (DBTYPE_UI4).
193
+ # adBigInt 20 Indicates an eight-byte signed integer (DBTYPE_I8).
194
+ # adUnsignedBigInt 21 Indicates an eight-byte unsigned integer (DBTYPE_UI8).
195
+ # adFileTime 64 Indicates a 64-bit value representing the number of 100-nanosecond intervals since
196
+ # January 1, 1601 (DBTYPE_FILETIME).
197
+ # adGUID 72 Indicates a globally unique identifier (GUID) (DBTYPE_GUID).
198
+ # adBinary 128 Indicates a binary value (DBTYPE_BYTES).
199
+ # adChar 129 Indicates a string value (DBTYPE_STR).
200
+ # adWChar 130 Indicates a null-terminated Unicode character string (DBTYPE_WSTR).
201
+ # adNumeric 131 Indicates an exact numeric value with a fixed precision and scale (DBTYPE_NUMERIC).
202
+ # adUserDefined 132 Indicates a user-defined variable (DBTYPE_UDT).
203
+ # adUserDefined 132 Indicates a user-defined variable (DBTYPE_UDT).
204
+ # adDBDate 133 Indicates a date value (yyyymmdd) (DBTYPE_DBDATE).
205
+ # adDBTime 134 Indicates a time value (hhmmss) (DBTYPE_DBTIME).
206
+ # adDBTimeStamp 135 Indicates a date/time stamp (yyyymmddhhmmss plus a fraction in billionths) (DBTYPE_DBTIMESTAMP).
207
+ # adChapter 136 Indicates a four-byte chapter value that identifies rows in a child rowset (DBTYPE_HCHAPTER).
208
+ # adPropVariant 138 Indicates an Automation PROPVARIANT (DBTYPE_PROP_VARIANT).
209
+ # adVarNumeric 139 Indicates a numeric value (Parameter object only).
210
+ # adVarChar 200 Indicates a string value (Parameter object only).
211
+ # adLongVarChar 201 Indicates a long string value (Parameter object only).
212
+ # adVarWChar 202 Indicates a null-terminated Unicode character string (Parameter object only).
213
+ # adLongVarWChar 203 Indicates a long null-terminated Unicode string value (Parameter object only).
214
+ # adVarBinary 204 Indicates a binary value (Parameter object only).
215
+ # adLongVarBinary 205 Indicates a long binary value (Parameter object only).
216
+ # adArray (Does not apply to ADOX.) 0x2000 A flag value, always combined with another data type constant,
217
+ # that indicates an array of that other data type.
218
+
219
+ # Error codes to names
220
+ adoErrors = {
221
+ 0xE7B: "adErrBoundToCommand",
222
+ 0xE94: "adErrCannotComplete",
223
+ 0xEA4: "adErrCantChangeConnection",
224
+ 0xC94: "adErrCantChangeProvider",
225
+ 0xE8C: "adErrCantConvertvalue",
226
+ 0xE8D: "adErrCantCreate",
227
+ 0xEA3: "adErrCatalogNotSet",
228
+ 0xE8E: "adErrColumnNotOnThisRow",
229
+ 0xD5D: "adErrDataConversion",
230
+ 0xE89: "adErrDataOverflow",
231
+ 0xE9A: "adErrDelResOutOfScope",
232
+ 0xEA6: "adErrDenyNotSupported",
233
+ 0xEA7: "adErrDenyTypeNotSupported",
234
+ 0xCB3: "adErrFeatureNotAvailable",
235
+ 0xEA5: "adErrFieldsUpdateFailed",
236
+ 0xC93: "adErrIllegalOperation",
237
+ 0xCAE: "adErrInTransaction",
238
+ 0xE87: "adErrIntegrityViolation",
239
+ 0xBB9: "adErrInvalidArgument",
240
+ 0xE7D: "adErrInvalidConnection",
241
+ 0xE7C: "adErrInvalidParamInfo",
242
+ 0xE82: "adErrInvalidTransaction",
243
+ 0xE91: "adErrInvalidURL",
244
+ 0xCC1: "adErrItemNotFound",
245
+ 0xBCD: "adErrNoCurrentRecord",
246
+ 0xE83: "adErrNotExecuting",
247
+ 0xE7E: "adErrNotReentrant",
248
+ 0xE78: "adErrObjectClosed",
249
+ 0xD27: "adErrObjectInCollection",
250
+ 0xD5C: "adErrObjectNotSet",
251
+ 0xE79: "adErrObjectOpen",
252
+ 0xBBA: "adErrOpeningFile",
253
+ 0xE80: "adErrOperationCancelled",
254
+ 0xE96: "adErrOutOfSpace",
255
+ 0xE88: "adErrPermissionDenied",
256
+ 0xE9E: "adErrPropConflicting",
257
+ 0xE9B: "adErrPropInvalidColumn",
258
+ 0xE9C: "adErrPropInvalidOption",
259
+ 0xE9D: "adErrPropInvalidValue",
260
+ 0xE9F: "adErrPropNotAllSettable",
261
+ 0xEA0: "adErrPropNotSet",
262
+ 0xEA1: "adErrPropNotSettable",
263
+ 0xEA2: "adErrPropNotSupported",
264
+ 0xBB8: "adErrProviderFailed",
265
+ 0xE7A: "adErrProviderNotFound",
266
+ 0xBBB: "adErrReadFile",
267
+ 0xE93: "adErrResourceExists",
268
+ 0xE92: "adErrResourceLocked",
269
+ 0xE97: "adErrResourceOutOfScope",
270
+ 0xE8A: "adErrSchemaViolation",
271
+ 0xE8B: "adErrSignMismatch",
272
+ 0xE81: "adErrStillConnecting",
273
+ 0xE7F: "adErrStillExecuting",
274
+ 0xE90: "adErrTreePermissionDenied",
275
+ 0xE8F: "adErrURLDoesNotExist",
276
+ 0xE99: "adErrURLNamedRowDoesNotExist",
277
+ 0xE98: "adErrUnavailable",
278
+ 0xE84: "adErrUnsafeOperation",
279
+ 0xE95: "adErrVolumeNotFound",
280
+ 0xBBC: "adErrWriteFile",
281
+ }
myenv/Lib/site-packages/adodbapi/adodbapi.py ADDED
@@ -0,0 +1,1157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """adodbapi - A python DB API 2.0 (PEP 249) interface to Microsoft ADO
2
+
3
+ Copyright (C) 2002 Henrik Ekelund, versions 2.1 and later by Vernon Cole
4
+ * http://sourceforge.net/projects/pywin32
5
+ * https://github.com/mhammond/pywin32
6
+ * http://sourceforge.net/projects/adodbapi
7
+
8
+ This library is free software; you can redistribute it and/or
9
+ modify it under the terms of the GNU Lesser General Public
10
+ License as published by the Free Software Foundation; either
11
+ version 2.1 of the License, or (at your option) any later version.
12
+
13
+ This library is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
+ Lesser General Public License for more details.
17
+
18
+ You should have received a copy of the GNU Lesser General Public
19
+ License along with this library; if not, write to the Free Software
20
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
+
22
+ django adaptations and refactoring by Adam Vandenberg
23
+
24
+ DB-API 2.0 specification: http://www.python.org/dev/peps/pep-0249/
25
+
26
+ This module source should run correctly in CPython versions 2.7 and later,
27
+ or CPython 3.4 or later.
28
+ """
29
+
30
+ __version__ = "2.6.2.0"
31
+ version = "adodbapi v" + __version__
32
+
33
+ import copy
34
+ import decimal
35
+ import os
36
+ import sys
37
+ import weakref
38
+
39
+ from . import ado_consts as adc, apibase as api, process_connect_string
40
+
41
+ try:
42
+ verbose = int(os.environ["ADODBAPI_VERBOSE"])
43
+ except:
44
+ verbose = False
45
+ if verbose:
46
+ print(version)
47
+
48
+ try:
49
+ import pythoncom
50
+ import pywintypes
51
+ from win32com.client import Dispatch
52
+ except ImportError:
53
+ import warnings
54
+
55
+ warnings.warn("pywin32 package required for adodbapi.", ImportWarning)
56
+
57
+
58
+ def getIndexedValue(obj, index):
59
+ return obj(index)
60
+
61
+
62
+ from collections.abc import Mapping
63
+
64
+
65
+ # ----------------- The .connect method -----------------
66
+ def make_COM_connecter():
67
+ try:
68
+ pythoncom.CoInitialize() # v2.1 Paj
69
+ c = Dispatch("ADODB.Connection") # connect _after_ CoIninialize v2.1.1 adamvan
70
+ except:
71
+ raise api.InterfaceError(
72
+ "Windows COM Error: Dispatch('ADODB.Connection') failed."
73
+ )
74
+ return c
75
+
76
+
77
+ def connect(*args, **kwargs): # --> a db-api connection object
78
+ """Connect to a database.
79
+
80
+ call using:
81
+ :connection_string -- An ADODB formatted connection string, see:
82
+ * http://www.connectionstrings.com
83
+ * http://www.asp101.com/articles/john/connstring/default.asp
84
+ :timeout -- A command timeout value, in seconds (default 30 seconds)
85
+ """
86
+ co = Connection() # make an empty connection object
87
+
88
+ kwargs = process_connect_string.process(args, kwargs, True)
89
+
90
+ try: # connect to the database, using the connection information in kwargs
91
+ co.connect(kwargs)
92
+ return co
93
+ except Exception as e:
94
+ message = 'Error opening connection to "%s"' % co.connection_string
95
+ raise api.OperationalError(e, message)
96
+
97
+
98
+ # so you could use something like:
99
+ # myConnection.paramstyle = 'named'
100
+ # The programmer may also change the default.
101
+ # For example, if I were using django, I would say:
102
+ # import adodbapi as Database
103
+ # Database.adodbapi.paramstyle = 'format'
104
+
105
+ # ------- other module level defaults --------
106
+ defaultIsolationLevel = adc.adXactReadCommitted
107
+ # Set defaultIsolationLevel on module level before creating the connection.
108
+ # For example:
109
+ # import adodbapi, ado_consts
110
+ # adodbapi.adodbapi.defaultIsolationLevel=ado_consts.adXactBrowse"
111
+ #
112
+ # Set defaultCursorLocation on module level before creating the connection.
113
+ # It may be one of the "adUse..." consts.
114
+ defaultCursorLocation = adc.adUseClient # changed from adUseServer as of v 2.3.0
115
+
116
+ dateconverter = api.pythonDateTimeConverter() # default
117
+
118
+
119
+ def format_parameters(ADOparameters, show_value=False):
120
+ """Format a collection of ADO Command Parameters.
121
+
122
+ Used by error reporting in _execute_command.
123
+ """
124
+ try:
125
+ if show_value:
126
+ desc = [
127
+ 'Name: %s, Dir.: %s, Type: %s, Size: %s, Value: "%s", Precision: %s, NumericScale: %s'
128
+ % (
129
+ p.Name,
130
+ adc.directions[p.Direction],
131
+ adc.adTypeNames.get(p.Type, str(p.Type) + " (unknown type)"),
132
+ p.Size,
133
+ p.Value,
134
+ p.Precision,
135
+ p.NumericScale,
136
+ )
137
+ for p in ADOparameters
138
+ ]
139
+ else:
140
+ desc = [
141
+ "Name: %s, Dir.: %s, Type: %s, Size: %s, Precision: %s, NumericScale: %s"
142
+ % (
143
+ p.Name,
144
+ adc.directions[p.Direction],
145
+ adc.adTypeNames.get(p.Type, str(p.Type) + " (unknown type)"),
146
+ p.Size,
147
+ p.Precision,
148
+ p.NumericScale,
149
+ )
150
+ for p in ADOparameters
151
+ ]
152
+ return "[" + "\n".join(desc) + "]"
153
+ except:
154
+ return "[]"
155
+
156
+
157
+ def _configure_parameter(p, value, adotype, settings_known):
158
+ """Configure the given ADO Parameter 'p' with the Python 'value'."""
159
+
160
+ if adotype in api.adoBinaryTypes:
161
+ p.Size = len(value)
162
+ p.AppendChunk(value)
163
+
164
+ elif isinstance(value, str): # v2.1 Jevon
165
+ L = len(value)
166
+ if adotype in api.adoStringTypes: # v2.2.1 Cole
167
+ if settings_known:
168
+ L = min(L, p.Size) # v2.1 Cole limit data to defined size
169
+ p.Value = value[:L] # v2.1 Jevon & v2.1 Cole
170
+ else:
171
+ p.Value = value # don't limit if db column is numeric
172
+ if L > 0: # v2.1 Cole something does not like p.Size as Zero
173
+ p.Size = L # v2.1 Jevon
174
+
175
+ elif isinstance(value, decimal.Decimal):
176
+ p.Value = value
177
+ exponent = value.as_tuple()[2]
178
+ digit_count = len(value.as_tuple()[1])
179
+ p.Precision = digit_count
180
+ if exponent == 0:
181
+ p.NumericScale = 0
182
+ elif exponent < 0:
183
+ p.NumericScale = -exponent
184
+ if p.Precision < p.NumericScale:
185
+ p.Precision = p.NumericScale
186
+ else: # exponent > 0:
187
+ p.NumericScale = 0
188
+ p.Precision = digit_count + exponent
189
+
190
+ elif type(value) in dateconverter.types:
191
+ if settings_known and adotype in api.adoDateTimeTypes:
192
+ p.Value = dateconverter.COMDate(value)
193
+ else: # probably a string
194
+ # provide the date as a string in the format 'YYYY-MM-dd'
195
+ s = dateconverter.DateObjectToIsoFormatString(value)
196
+ p.Value = s
197
+ p.Size = len(s)
198
+
199
+ elif adotype == adc.adEmpty: # ADO will not let you specify a null column
200
+ p.Type = (
201
+ adc.adInteger
202
+ ) # so we will fake it to be an integer (just to have something)
203
+ p.Value = None # and pass in a Null *value*
204
+
205
+ # For any other type, set the value and let pythoncom do the right thing.
206
+ else:
207
+ p.Value = value
208
+
209
+
210
+ # # # # # ----- the Class that defines a connection ----- # # # # #
211
+ class Connection:
212
+ # include connection attributes as class attributes required by api definition.
213
+ Warning = api.Warning
214
+ Error = api.Error
215
+ InterfaceError = api.InterfaceError
216
+ DataError = api.DataError
217
+ DatabaseError = api.DatabaseError
218
+ OperationalError = api.OperationalError
219
+ IntegrityError = api.IntegrityError
220
+ InternalError = api.InternalError
221
+ NotSupportedError = api.NotSupportedError
222
+ ProgrammingError = api.ProgrammingError
223
+ FetchFailedError = api.FetchFailedError # (special for django)
224
+ # ...class attributes... (can be overridden by instance attributes)
225
+ verbose = api.verbose
226
+
227
+ @property
228
+ def dbapi(self): # a proposed db-api version 3 extension.
229
+ "Return a reference to the DBAPI module for this Connection."
230
+ return api
231
+
232
+ def __init__(self): # now define the instance attributes
233
+ self.connector = None
234
+ self.paramstyle = api.paramstyle
235
+ self.supportsTransactions = False
236
+ self.connection_string = ""
237
+ self.cursors = weakref.WeakValueDictionary()
238
+ self.dbms_name = ""
239
+ self.dbms_version = ""
240
+ self.errorhandler = None # use the standard error handler for this instance
241
+ self.transaction_level = 0 # 0 == Not in a transaction, at the top level
242
+ self._autocommit = False
243
+
244
+ def connect(self, kwargs, connection_maker=make_COM_connecter):
245
+ if verbose > 9:
246
+ print(f"kwargs={kwargs!r}")
247
+ try:
248
+ self.connection_string = (
249
+ kwargs["connection_string"] % kwargs
250
+ ) # insert keyword arguments
251
+ except Exception as e:
252
+ self._raiseConnectionError(
253
+ KeyError, "Python string format error in connection string->"
254
+ )
255
+ self.timeout = kwargs.get("timeout", 30)
256
+ self.mode = kwargs.get("mode", adc.adModeUnknown)
257
+ self.kwargs = kwargs
258
+ if verbose:
259
+ print('%s attempting: "%s"' % (version, self.connection_string))
260
+ self.connector = connection_maker()
261
+ self.connector.ConnectionTimeout = self.timeout
262
+ self.connector.ConnectionString = self.connection_string
263
+ self.connector.Mode = self.mode
264
+
265
+ try:
266
+ self.connector.Open() # Open the ADO connection
267
+ except api.Error:
268
+ self._raiseConnectionError(
269
+ api.DatabaseError,
270
+ "ADO error trying to Open=%s" % self.connection_string,
271
+ )
272
+
273
+ try: # Stefan Fuchs; support WINCCOLEDBProvider
274
+ if getIndexedValue(self.connector.Properties, "Transaction DDL").Value != 0:
275
+ self.supportsTransactions = True
276
+ except pywintypes.com_error:
277
+ pass # Stefan Fuchs
278
+ self.dbms_name = getIndexedValue(self.connector.Properties, "DBMS Name").Value
279
+ try: # Stefan Fuchs
280
+ self.dbms_version = getIndexedValue(
281
+ self.connector.Properties, "DBMS Version"
282
+ ).Value
283
+ except pywintypes.com_error:
284
+ pass # Stefan Fuchs
285
+ self.connector.CursorLocation = defaultCursorLocation # v2.1 Rose
286
+ if self.supportsTransactions:
287
+ self.connector.IsolationLevel = defaultIsolationLevel
288
+ self._autocommit = bool(kwargs.get("autocommit", False))
289
+ if not self._autocommit:
290
+ self.transaction_level = (
291
+ self.connector.BeginTrans()
292
+ ) # Disables autocommit & inits transaction_level
293
+ else:
294
+ self._autocommit = True
295
+ if "paramstyle" in kwargs:
296
+ self.paramstyle = kwargs["paramstyle"] # let setattr do the error checking
297
+ self.messages = []
298
+ if verbose:
299
+ print("adodbapi New connection at %X" % id(self))
300
+
301
+ def _raiseConnectionError(self, errorclass, errorvalue):
302
+ eh = self.errorhandler
303
+ if eh is None:
304
+ eh = api.standardErrorHandler
305
+ eh(self, None, errorclass, errorvalue)
306
+
307
+ def _closeAdoConnection(self): # all v2.1 Rose
308
+ """close the underlying ADO Connection object,
309
+ rolling it back first if it supports transactions."""
310
+ if self.connector is None:
311
+ return
312
+ if not self._autocommit:
313
+ if self.transaction_level:
314
+ try:
315
+ self.connector.RollbackTrans()
316
+ except:
317
+ pass
318
+ self.connector.Close()
319
+ if verbose:
320
+ print("adodbapi Closed connection at %X" % id(self))
321
+
322
+ def close(self):
323
+ """Close the connection now (rather than whenever __del__ is called).
324
+
325
+ The connection will be unusable from this point forward;
326
+ an Error (or subclass) exception will be raised if any operation is attempted with the connection.
327
+ The same applies to all cursor objects trying to use the connection.
328
+ """
329
+ for crsr in list(self.cursors.values())[
330
+ :
331
+ ]: # copy the list, then close each one
332
+ crsr.close(dont_tell_me=True) # close without back-link clearing
333
+ self.messages = []
334
+ try:
335
+ self._closeAdoConnection() # v2.1 Rose
336
+ except Exception as e:
337
+ self._raiseConnectionError(sys.exc_info()[0], sys.exc_info()[1])
338
+
339
+ self.connector = None # v2.4.2.2 fix subtle timeout bug
340
+ # per M.Hammond: "I expect the benefits of uninitializing are probably fairly small,
341
+ # so never uninitializing will probably not cause any problems."
342
+
343
+ def commit(self):
344
+ """Commit any pending transaction to the database.
345
+
346
+ Note that if the database supports an auto-commit feature,
347
+ this must be initially off. An interface method may be provided to turn it back on.
348
+ Database modules that do not support transactions should implement this method with void functionality.
349
+ """
350
+ self.messages = []
351
+ if not self.supportsTransactions:
352
+ return
353
+
354
+ try:
355
+ self.transaction_level = self.connector.CommitTrans()
356
+ if verbose > 1:
357
+ print("commit done on connection at %X" % id(self))
358
+ if not (
359
+ self._autocommit
360
+ or (self.connector.Attributes & adc.adXactAbortRetaining)
361
+ ):
362
+ # If attributes has adXactCommitRetaining it performs retaining commits that is,
363
+ # calling CommitTrans automatically starts a new transaction. Not all providers support this.
364
+ # If not, we will have to start a new transaction by this command:
365
+ self.transaction_level = self.connector.BeginTrans()
366
+ except Exception as e:
367
+ self._raiseConnectionError(api.ProgrammingError, e)
368
+
369
+ def _rollback(self):
370
+ """In case a database does provide transactions this method causes the the database to roll back to
371
+ the start of any pending transaction. Closing a connection without committing the changes first will
372
+ cause an implicit rollback to be performed.
373
+
374
+ If the database does not support the functionality required by the method, the interface should
375
+ throw an exception in case the method is used.
376
+ The preferred approach is to not implement the method and thus have Python generate
377
+ an AttributeError in case the method is requested. This allows the programmer to check for database
378
+ capabilities using the standard hasattr() function.
379
+
380
+ For some dynamically configured interfaces it may not be appropriate to require dynamically making
381
+ the method available. These interfaces should then raise a NotSupportedError to indicate the
382
+ non-ability to perform the roll back when the method is invoked.
383
+ """
384
+ self.messages = []
385
+ if (
386
+ self.transaction_level
387
+ ): # trying to roll back with no open transaction causes an error
388
+ try:
389
+ self.transaction_level = self.connector.RollbackTrans()
390
+ if verbose > 1:
391
+ print("rollback done on connection at %X" % id(self))
392
+ if not self._autocommit and not (
393
+ self.connector.Attributes & adc.adXactAbortRetaining
394
+ ):
395
+ # If attributes has adXactAbortRetaining it performs retaining aborts that is,
396
+ # calling RollbackTrans automatically starts a new transaction. Not all providers support this.
397
+ # If not, we will have to start a new transaction by this command:
398
+ if (
399
+ not self.transaction_level
400
+ ): # if self.transaction_level == 0 or self.transaction_level is None:
401
+ self.transaction_level = self.connector.BeginTrans()
402
+ except Exception as e:
403
+ self._raiseConnectionError(api.ProgrammingError, e)
404
+
405
+ def __setattr__(self, name, value):
406
+ if name == "autocommit": # extension: allow user to turn autocommit on or off
407
+ if self.supportsTransactions:
408
+ object.__setattr__(self, "_autocommit", bool(value))
409
+ try:
410
+ self._rollback() # must clear any outstanding transactions
411
+ except:
412
+ pass
413
+ return
414
+ elif name == "paramstyle":
415
+ if value not in api.accepted_paramstyles:
416
+ self._raiseConnectionError(
417
+ api.NotSupportedError,
418
+ f"paramstyle={value!r} not in:{api.accepted_paramstyles!r}",
419
+ )
420
+ elif name == "variantConversions":
421
+ # make a new copy -- no changes in the default, please
422
+ value = copy.copy(value)
423
+ object.__setattr__(self, name, value)
424
+
425
+ def __getattr__(self, item):
426
+ if (
427
+ item == "rollback"
428
+ ): # the rollback method only appears if the database supports transactions
429
+ if self.supportsTransactions:
430
+ return (
431
+ self._rollback
432
+ ) # return the rollback method so the caller can execute it.
433
+ else:
434
+ raise AttributeError("this data provider does not support Rollback")
435
+ elif item == "autocommit":
436
+ return self._autocommit
437
+ else:
438
+ raise AttributeError(
439
+ 'no such attribute in ADO connection object as="%s"' % item
440
+ )
441
+
442
+ def cursor(self):
443
+ "Return a new Cursor Object using the connection."
444
+ self.messages = []
445
+ c = Cursor(self)
446
+ return c
447
+
448
+ def _i_am_here(self, crsr):
449
+ "message from a new cursor proclaiming its existence"
450
+ oid = id(crsr)
451
+ self.cursors[oid] = crsr
452
+
453
+ def _i_am_closing(self, crsr):
454
+ "message from a cursor giving connection a chance to clean up"
455
+ try:
456
+ del self.cursors[id(crsr)]
457
+ except:
458
+ pass
459
+
460
+ def printADOerrors(self):
461
+ j = self.connector.Errors.Count
462
+ if j:
463
+ print("ADO Errors:(%i)" % j)
464
+ for e in self.connector.Errors:
465
+ print("Description: %s" % e.Description)
466
+ print("Error: %s %s " % (e.Number, adc.adoErrors.get(e.Number, "unknown")))
467
+ if e.Number == adc.ado_error_TIMEOUT:
468
+ print(
469
+ "Timeout Error: Try using adodbpi.connect(constr,timeout=Nseconds)"
470
+ )
471
+ print("Source: %s" % e.Source)
472
+ print("NativeError: %s" % e.NativeError)
473
+ print("SQL State: %s" % e.SQLState)
474
+
475
+ def _suggest_error_class(self):
476
+ """Introspect the current ADO Errors and determine an appropriate error class.
477
+
478
+ Error.SQLState is a SQL-defined error condition, per the SQL specification:
479
+ http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt
480
+
481
+ The 23000 class of errors are integrity errors.
482
+ Error 40002 is a transactional integrity error.
483
+ """
484
+ if self.connector is not None:
485
+ for e in self.connector.Errors:
486
+ state = str(e.SQLState)
487
+ if state.startswith("23") or state == "40002":
488
+ return api.IntegrityError
489
+ return api.DatabaseError
490
+
491
+ def __del__(self):
492
+ try:
493
+ self._closeAdoConnection() # v2.1 Rose
494
+ except:
495
+ pass
496
+ self.connector = None
497
+
498
+ def __enter__(self): # Connections are context managers
499
+ return self
500
+
501
+ def __exit__(self, exc_type, exc_val, exc_tb):
502
+ if exc_type:
503
+ self._rollback() # automatic rollback on errors
504
+ else:
505
+ self.commit()
506
+
507
+ def get_table_names(self):
508
+ schema = self.connector.OpenSchema(20) # constant = adSchemaTables
509
+
510
+ tables = []
511
+ while not schema.EOF:
512
+ name = getIndexedValue(schema.Fields, "TABLE_NAME").Value
513
+ tables.append(name)
514
+ schema.MoveNext()
515
+ del schema
516
+ return tables
517
+
518
+
519
+ # # # # # ----- the Class that defines a cursor ----- # # # # #
520
+ class Cursor:
521
+ ## ** api required attributes:
522
+ ## description...
523
+ ## This read-only attribute is a sequence of 7-item sequences.
524
+ ## Each of these sequences contains information describing one result column:
525
+ ## (name, type_code, display_size, internal_size, precision, scale, null_ok).
526
+ ## This attribute will be None for operations that do not return rows or if the
527
+ ## cursor has not had an operation invoked via the executeXXX() method yet.
528
+ ## The type_code can be interpreted by comparing it to the Type Objects specified in the section below.
529
+ ## rowcount...
530
+ ## This read-only attribute specifies the number of rows that the last executeXXX() produced
531
+ ## (for DQL statements like select) or affected (for DML statements like update or insert).
532
+ ## The attribute is -1 in case no executeXXX() has been performed on the cursor or
533
+ ## the rowcount of the last operation is not determinable by the interface.[7]
534
+ ## arraysize...
535
+ ## This read/write attribute specifies the number of rows to fetch at a time with fetchmany().
536
+ ## It defaults to 1 meaning to fetch a single row at a time.
537
+ ## Implementations must observe this value with respect to the fetchmany() method,
538
+ ## but are free to interact with the database a single row at a time.
539
+ ## It may also be used in the implementation of executemany().
540
+ ## ** extension attributes:
541
+ ## paramstyle...
542
+ ## allows the programmer to override the connection's default paramstyle
543
+ ## errorhandler...
544
+ ## allows the programmer to override the connection's default error handler
545
+
546
+ def __init__(self, connection):
547
+ self.command = None
548
+ self._ado_prepared = False
549
+ self.messages = []
550
+ self.connection = connection
551
+ self.paramstyle = connection.paramstyle # used for overriding the paramstyle
552
+ self._parameter_names = []
553
+ self.recordset_is_remote = False
554
+ self.rs = None # the ADO recordset for this cursor
555
+ self.converters = [] # conversion function for each column
556
+ self.columnNames = {} # names of columns {lowercase name : number,...}
557
+ self.numberOfColumns = 0
558
+ self._description = None
559
+ self.rowcount = -1
560
+ self.errorhandler = connection.errorhandler
561
+ self.arraysize = 1
562
+ connection._i_am_here(self)
563
+ if verbose:
564
+ print(
565
+ "%s New cursor at %X on conn %X"
566
+ % (version, id(self), id(self.connection))
567
+ )
568
+
569
+ def __iter__(self): # [2.1 Zamarev]
570
+ return iter(self.fetchone, None) # [2.1 Zamarev]
571
+
572
+ def prepare(self, operation):
573
+ self.command = operation
574
+ self._description = None
575
+ self._ado_prepared = "setup"
576
+
577
+ def __next__(self):
578
+ r = self.fetchone()
579
+ if r:
580
+ return r
581
+ raise StopIteration
582
+
583
+ def __enter__(self):
584
+ "Allow database cursors to be used with context managers."
585
+ return self
586
+
587
+ def __exit__(self, exc_type, exc_val, exc_tb):
588
+ "Allow database cursors to be used with context managers."
589
+ self.close()
590
+
591
+ def _raiseCursorError(self, errorclass, errorvalue):
592
+ eh = self.errorhandler
593
+ if eh is None:
594
+ eh = api.standardErrorHandler
595
+ eh(self.connection, self, errorclass, errorvalue)
596
+
597
+ def build_column_info(self, recordset):
598
+ self.converters = [] # convertion function for each column
599
+ self.columnNames = {} # names of columns {lowercase name : number,...}
600
+ self._description = None
601
+
602
+ # if EOF and BOF are true at the same time, there are no records in the recordset
603
+ if (recordset is None) or (recordset.State == adc.adStateClosed):
604
+ self.rs = None
605
+ self.numberOfColumns = 0
606
+ return
607
+ self.rs = recordset # v2.1.1 bkline
608
+ self.recordset_format = api.RS_WIN_32
609
+ self.numberOfColumns = recordset.Fields.Count
610
+ try:
611
+ varCon = self.connection.variantConversions
612
+ except AttributeError:
613
+ varCon = api.variantConversions
614
+ for i in range(self.numberOfColumns):
615
+ f = getIndexedValue(self.rs.Fields, i)
616
+ try:
617
+ self.converters.append(
618
+ varCon[f.Type]
619
+ ) # conversion function for this column
620
+ except KeyError:
621
+ self._raiseCursorError(
622
+ api.InternalError, "Data column of Unknown ADO type=%s" % f.Type
623
+ )
624
+ self.columnNames[f.Name.lower()] = i # columnNames lookup
625
+
626
+ def _makeDescriptionFromRS(self):
627
+ # Abort if closed or no recordset.
628
+ if self.rs is None:
629
+ self._description = None
630
+ return
631
+ desc = []
632
+ for i in range(self.numberOfColumns):
633
+ f = getIndexedValue(self.rs.Fields, i)
634
+ if self.rs.EOF or self.rs.BOF:
635
+ display_size = None
636
+ else:
637
+ display_size = (
638
+ f.ActualSize
639
+ ) # TODO: Is this the correct defintion according to the DB API 2 Spec ?
640
+ null_ok = bool(f.Attributes & adc.adFldMayBeNull) # v2.1 Cole
641
+ desc.append(
642
+ (
643
+ f.Name,
644
+ f.Type,
645
+ display_size,
646
+ f.DefinedSize,
647
+ f.Precision,
648
+ f.NumericScale,
649
+ null_ok,
650
+ )
651
+ )
652
+ self._description = desc
653
+
654
+ def get_description(self):
655
+ if not self._description:
656
+ self._makeDescriptionFromRS()
657
+ return self._description
658
+
659
+ def __getattr__(self, item):
660
+ if item == "description":
661
+ return self.get_description()
662
+ object.__getattribute__(
663
+ self, item
664
+ ) # may get here on Remote attribute calls for existing attributes
665
+
666
+ def format_description(self, d):
667
+ """Format db_api description tuple for printing."""
668
+ if self.description is None:
669
+ self._makeDescriptionFromRS()
670
+ if isinstance(d, int):
671
+ d = self.description[d]
672
+ desc = (
673
+ "Name= %s, Type= %s, DispSize= %s, IntSize= %s, Precision= %s, Scale= %s NullOK=%s"
674
+ % (
675
+ d[0],
676
+ adc.adTypeNames.get(d[1], str(d[1]) + " (unknown type)"),
677
+ d[2],
678
+ d[3],
679
+ d[4],
680
+ d[5],
681
+ d[6],
682
+ )
683
+ )
684
+ return desc
685
+
686
+ def close(self, dont_tell_me=False):
687
+ """Close the cursor now (rather than whenever __del__ is called).
688
+ The cursor will be unusable from this point forward; an Error (or subclass)
689
+ exception will be raised if any operation is attempted with the cursor.
690
+ """
691
+ if self.connection is None:
692
+ return
693
+ self.messages = []
694
+ if (
695
+ self.rs and self.rs.State != adc.adStateClosed
696
+ ): # rs exists and is open #v2.1 Rose
697
+ self.rs.Close() # v2.1 Rose
698
+ self.rs = None # let go of the recordset so ADO will let it be disposed #v2.1 Rose
699
+ if not dont_tell_me:
700
+ self.connection._i_am_closing(
701
+ self
702
+ ) # take me off the connection's cursors list
703
+ self.connection = (
704
+ None # this will make all future method calls on me throw an exception
705
+ )
706
+ if verbose:
707
+ print("adodbapi Closed cursor at %X" % id(self))
708
+
709
+ def __del__(self):
710
+ try:
711
+ self.close()
712
+ except:
713
+ pass
714
+
715
+ def _new_command(self, command_type=adc.adCmdText):
716
+ self.cmd = None
717
+ self.messages = []
718
+
719
+ if self.connection is None:
720
+ self._raiseCursorError(api.InterfaceError, None)
721
+ return
722
+ try:
723
+ self.cmd = Dispatch("ADODB.Command")
724
+ self.cmd.ActiveConnection = self.connection.connector
725
+ self.cmd.CommandTimeout = self.connection.timeout
726
+ self.cmd.CommandType = command_type
727
+ self.cmd.CommandText = self.commandText
728
+ self.cmd.Prepared = bool(self._ado_prepared)
729
+ except:
730
+ self._raiseCursorError(
731
+ api.DatabaseError,
732
+ f"Error creating new ADODB.Command object for {self.commandText!r}",
733
+ )
734
+
735
+ def _execute_command(self):
736
+ # Stored procedures may have an integer return value
737
+ self.return_value = None
738
+ recordset = None
739
+ count = -1 # default value
740
+ if verbose:
741
+ print('Executing command="%s"' % self.commandText)
742
+ try:
743
+ # ----- the actual SQL is executed here ---
744
+ recordset, count = self.cmd.Execute()
745
+ # ----- ------------------------------- ---
746
+ except Exception as e:
747
+ _message = ""
748
+ if hasattr(e, "args"):
749
+ _message += str(e.args) + "\n"
750
+ _message += "Command:\n%s\nParameters:\n%s" % (
751
+ self.commandText,
752
+ format_parameters(self.cmd.Parameters, True),
753
+ )
754
+ klass = self.connection._suggest_error_class()
755
+ self._raiseCursorError(klass, _message)
756
+ try:
757
+ self.rowcount = recordset.RecordCount
758
+ except:
759
+ self.rowcount = count
760
+ self.build_column_info(recordset)
761
+
762
+ # The ADO documentation hints that obtaining the recordcount may be timeconsuming
763
+ # "If the Recordset object does not support approximate positioning, this property
764
+ # may be a significant drain on resources # [ekelund]
765
+ # Therefore, COM will not return rowcount for server-side cursors. [Cole]
766
+ # Client-side cursors (the default since v2.8) will force a static
767
+ # cursor, and rowcount will then be set accurately [Cole]
768
+
769
+ def get_rowcount(self):
770
+ return self.rowcount
771
+
772
+ def get_returned_parameters(self):
773
+ """with some providers, returned parameters and the .return_value are not available until
774
+ after the last recordset has been read. In that case, you must coll nextset() until it
775
+ returns None, then call this method to get your returned information."""
776
+
777
+ retLst = (
778
+ []
779
+ ) # store procedures may return altered parameters, including an added "return value" item
780
+ for p in tuple(self.cmd.Parameters):
781
+ if verbose > 2:
782
+ print(
783
+ 'Returned=Name: %s, Dir.: %s, Type: %s, Size: %s, Value: "%s",'
784
+ " Precision: %s, NumericScale: %s"
785
+ % (
786
+ p.Name,
787
+ adc.directions[p.Direction],
788
+ adc.adTypeNames.get(p.Type, str(p.Type) + " (unknown type)"),
789
+ p.Size,
790
+ p.Value,
791
+ p.Precision,
792
+ p.NumericScale,
793
+ )
794
+ )
795
+ pyObject = api.convert_to_python(p.Value, api.variantConversions[p.Type])
796
+ if p.Direction == adc.adParamReturnValue:
797
+ self.returnValue = (
798
+ pyObject # also load the undocumented attribute (Vernon's Error!)
799
+ )
800
+ self.return_value = pyObject
801
+ else:
802
+ retLst.append(pyObject)
803
+ return retLst # return the parameter list to the caller
804
+
805
+ def callproc(self, procname, parameters=None):
806
+ """Call a stored database procedure with the given name.
807
+ The sequence of parameters must contain one entry for each
808
+ argument that the sproc expects. The result of the
809
+ call is returned as modified copy of the input
810
+ sequence. Input parameters are left untouched, output and
811
+ input/output parameters replaced with possibly new values.
812
+
813
+ The sproc may also provide a result set as output,
814
+ which is available through the standard .fetch*() methods.
815
+ Extension: A "return_value" property may be set on the
816
+ cursor if the sproc defines an integer return value.
817
+ """
818
+ self._parameter_names = []
819
+ self.commandText = procname
820
+ self._new_command(command_type=adc.adCmdStoredProc)
821
+ self._buildADOparameterList(parameters, sproc=True)
822
+ if verbose > 2:
823
+ print(
824
+ "Calling Stored Proc with Params=",
825
+ format_parameters(self.cmd.Parameters, True),
826
+ )
827
+ self._execute_command()
828
+ return self.get_returned_parameters()
829
+
830
+ def _reformat_operation(self, operation, parameters):
831
+ if self.paramstyle in ("format", "pyformat"): # convert %s to ?
832
+ operation, self._parameter_names = api.changeFormatToQmark(operation)
833
+ elif self.paramstyle == "named" or (
834
+ self.paramstyle == "dynamic" and isinstance(parameters, Mapping)
835
+ ):
836
+ operation, self._parameter_names = api.changeNamedToQmark(
837
+ operation
838
+ ) # convert :name to ?
839
+ return operation
840
+
841
+ def _buildADOparameterList(self, parameters, sproc=False):
842
+ self.parameters = parameters
843
+ if parameters is None:
844
+ parameters = []
845
+
846
+ # Note: ADO does not preserve the parameter list, even if "Prepared" is True, so we must build every time.
847
+ parameters_known = False
848
+ if sproc: # needed only if we are calling a stored procedure
849
+ try: # attempt to use ADO's parameter list
850
+ self.cmd.Parameters.Refresh()
851
+ if verbose > 2:
852
+ print(
853
+ "ADO detected Params=",
854
+ format_parameters(self.cmd.Parameters, True),
855
+ )
856
+ print(f"Program Parameters={parameters!r}")
857
+ parameters_known = True
858
+ except api.Error:
859
+ if verbose:
860
+ print("ADO Parameter Refresh failed")
861
+ pass
862
+ else:
863
+ if len(parameters) != self.cmd.Parameters.Count - 1:
864
+ raise api.ProgrammingError(
865
+ "You must supply %d parameters for this stored procedure"
866
+ % (self.cmd.Parameters.Count - 1)
867
+ )
868
+ if sproc or parameters != []:
869
+ i = 0
870
+ if parameters_known: # use ado parameter list
871
+ if self._parameter_names: # named parameters
872
+ for i, pm_name in enumerate(self._parameter_names):
873
+ p = getIndexedValue(self.cmd.Parameters, i)
874
+ try:
875
+ _configure_parameter(
876
+ p, parameters[pm_name], p.Type, parameters_known
877
+ )
878
+ except Exception as e:
879
+ _message = "Error Converting Parameter {}: {}, {} <- {!r}\n".format(
880
+ p.Name,
881
+ adc.ado_type_name(p.Type),
882
+ p.Value,
883
+ parameters[pm_name],
884
+ )
885
+ self._raiseCursorError(
886
+ api.DataError, f"{_message}->{e.args!r}"
887
+ )
888
+ else: # regular sequence of parameters
889
+ for value in parameters:
890
+ p = getIndexedValue(self.cmd.Parameters, i)
891
+ if (
892
+ p.Direction == adc.adParamReturnValue
893
+ ): # this is an extra parameter added by ADO
894
+ i += 1 # skip the extra
895
+ p = getIndexedValue(self.cmd.Parameters, i)
896
+ try:
897
+ _configure_parameter(p, value, p.Type, parameters_known)
898
+ except Exception as e:
899
+ _message = "Error Converting Parameter {}: {}, {} <- {!r}\n".format(
900
+ p.Name,
901
+ adc.ado_type_name(p.Type),
902
+ p.Value,
903
+ value,
904
+ )
905
+ self._raiseCursorError(
906
+ api.DataError, f"{_message}->{e.args!r}"
907
+ )
908
+ i += 1
909
+ else: # -- build own parameter list
910
+ if (
911
+ self._parameter_names
912
+ ): # we expect a dictionary of parameters, this is the list of expected names
913
+ for parm_name in self._parameter_names:
914
+ elem = parameters[parm_name]
915
+ adotype = api.pyTypeToADOType(elem)
916
+ p = self.cmd.CreateParameter(
917
+ parm_name, adotype, adc.adParamInput
918
+ )
919
+ _configure_parameter(p, elem, adotype, parameters_known)
920
+ try:
921
+ self.cmd.Parameters.Append(p)
922
+ except Exception as e:
923
+ _message = (
924
+ "Error Building Parameter {}: {}, {} <- {!r}\n".format(
925
+ p.Name,
926
+ adc.ado_type_name(p.Type),
927
+ p.Value,
928
+ elem,
929
+ )
930
+ )
931
+ self._raiseCursorError(
932
+ api.DataError, f"{_message}->{e.args!r}"
933
+ )
934
+ else: # expecting the usual sequence of parameters
935
+ if sproc:
936
+ p = self.cmd.CreateParameter(
937
+ "@RETURN_VALUE", adc.adInteger, adc.adParamReturnValue
938
+ )
939
+ self.cmd.Parameters.Append(p)
940
+
941
+ for elem in parameters:
942
+ name = "p%i" % i
943
+ adotype = api.pyTypeToADOType(elem)
944
+ p = self.cmd.CreateParameter(
945
+ name, adotype, adc.adParamInput
946
+ ) # Name, Type, Direction, Size, Value
947
+ _configure_parameter(p, elem, adotype, parameters_known)
948
+ try:
949
+ self.cmd.Parameters.Append(p)
950
+ except Exception as e:
951
+ _message = (
952
+ "Error Building Parameter {}: {}, {} <- {!r}\n".format(
953
+ p.Name,
954
+ adc.ado_type_name(p.Type),
955
+ p.Value,
956
+ elem,
957
+ )
958
+ )
959
+ self._raiseCursorError(
960
+ api.DataError, f"{_message}->{e.args!r}"
961
+ )
962
+ i += 1
963
+ if self._ado_prepared == "setup":
964
+ self._ado_prepared = (
965
+ True # parameters will be "known" by ADO next loop
966
+ )
967
+
968
+ def execute(self, operation, parameters=None):
969
+ """Prepare and execute a database operation (query or command).
970
+
971
+ Parameters may be provided as sequence or mapping and will be bound to variables in the operation.
972
+ Variables are specified in a database-specific notation
973
+ (see the module's paramstyle attribute for details). [5]
974
+ A reference to the operation will be retained by the cursor.
975
+ If the same operation object is passed in again, then the cursor
976
+ can optimize its behavior. This is most effective for algorithms
977
+ where the same operation is used, but different parameters are bound to it (many times).
978
+
979
+ For maximum efficiency when reusing an operation, it is best to use
980
+ the setinputsizes() method to specify the parameter types and sizes ahead of time.
981
+ It is legal for a parameter to not match the predefined information;
982
+ the implementation should compensate, possibly with a loss of efficiency.
983
+
984
+ The parameters may also be specified as list of tuples to e.g. insert multiple rows in
985
+ a single operation, but this kind of usage is depreciated: executemany() should be used instead.
986
+
987
+ Return value is not defined.
988
+
989
+ [5] The module will use the __getitem__ method of the parameters object to map either positions
990
+ (integers) or names (strings) to parameter values. This allows for both sequences and mappings
991
+ to be used as input.
992
+ The term "bound" refers to the process of binding an input value to a database execution buffer.
993
+ In practical terms, this means that the input value is directly used as a value in the operation.
994
+ The client should not be required to "escape" the value so that it can be used -- the value
995
+ should be equal to the actual database value."""
996
+ if (
997
+ self.command is not operation
998
+ or self._ado_prepared == "setup"
999
+ or not hasattr(self, "commandText")
1000
+ ):
1001
+ if self.command is not operation:
1002
+ self._ado_prepared = False
1003
+ self.command = operation
1004
+ self._parameter_names = []
1005
+ self.commandText = (
1006
+ operation
1007
+ if (self.paramstyle == "qmark" or not parameters)
1008
+ else self._reformat_operation(operation, parameters)
1009
+ )
1010
+ self._new_command()
1011
+ self._buildADOparameterList(parameters)
1012
+ if verbose > 3:
1013
+ print("Params=", format_parameters(self.cmd.Parameters, True))
1014
+ self._execute_command()
1015
+
1016
+ def executemany(self, operation, seq_of_parameters):
1017
+ """Prepare a database operation (query or command)
1018
+ and then execute it against all parameter sequences or mappings found in the sequence seq_of_parameters.
1019
+
1020
+ Return values are not defined.
1021
+ """
1022
+ self.messages = list()
1023
+ total_recordcount = 0
1024
+
1025
+ self.prepare(operation)
1026
+ for params in seq_of_parameters:
1027
+ self.execute(self.command, params)
1028
+ if self.rowcount == -1:
1029
+ total_recordcount = -1
1030
+ if total_recordcount != -1:
1031
+ total_recordcount += self.rowcount
1032
+ self.rowcount = total_recordcount
1033
+
1034
+ def _fetch(self, limit=None):
1035
+ """Fetch rows from the current recordset.
1036
+
1037
+ limit -- Number of rows to fetch, or None (default) to fetch all rows.
1038
+ """
1039
+ if self.connection is None or self.rs is None:
1040
+ self._raiseCursorError(
1041
+ api.FetchFailedError, "fetch() on closed connection or empty query set"
1042
+ )
1043
+ return
1044
+
1045
+ if self.rs.State == adc.adStateClosed or self.rs.BOF or self.rs.EOF:
1046
+ return list()
1047
+ if limit: # limit number of rows retrieved
1048
+ ado_results = self.rs.GetRows(limit)
1049
+ else: # get all rows
1050
+ ado_results = self.rs.GetRows()
1051
+ if (
1052
+ self.recordset_format == api.RS_ARRAY
1053
+ ): # result of GetRows is a two-dimension array
1054
+ length = (
1055
+ len(ado_results) // self.numberOfColumns
1056
+ ) # length of first dimension
1057
+ else: # pywin32
1058
+ length = len(ado_results[0]) # result of GetRows is tuples in a tuple
1059
+ fetchObject = api.SQLrows(
1060
+ ado_results, length, self
1061
+ ) # new object to hold the results of the fetch
1062
+ return fetchObject
1063
+
1064
+ def fetchone(self):
1065
+ """Fetch the next row of a query result set, returning a single sequence,
1066
+ or None when no more data is available.
1067
+
1068
+ An Error (or subclass) exception is raised if the previous call to executeXXX()
1069
+ did not produce any result set or no call was issued yet.
1070
+ """
1071
+ self.messages = []
1072
+ result = self._fetch(1)
1073
+ if result: # return record (not list of records)
1074
+ return result[0]
1075
+ return None
1076
+
1077
+ def fetchmany(self, size=None):
1078
+ """Fetch the next set of rows of a query result, returning a list of tuples. An empty sequence is returned when no more rows are available.
1079
+
1080
+ The number of rows to fetch per call is specified by the parameter.
1081
+ If it is not given, the cursor's arraysize determines the number of rows to be fetched.
1082
+ The method should try to fetch as many rows as indicated by the size parameter.
1083
+ If this is not possible due to the specified number of rows not being available,
1084
+ fewer rows may be returned.
1085
+
1086
+ An Error (or subclass) exception is raised if the previous call to executeXXX()
1087
+ did not produce any result set or no call was issued yet.
1088
+
1089
+ Note there are performance considerations involved with the size parameter.
1090
+ For optimal performance, it is usually best to use the arraysize attribute.
1091
+ If the size parameter is used, then it is best for it to retain the same value from
1092
+ one fetchmany() call to the next.
1093
+ """
1094
+ self.messages = []
1095
+ if size is None:
1096
+ size = self.arraysize
1097
+ return self._fetch(size)
1098
+
1099
+ def fetchall(self):
1100
+ """Fetch all (remaining) rows of a query result, returning them as a sequence of sequences (e.g. a list of tuples).
1101
+
1102
+ Note that the cursor's arraysize attribute
1103
+ can affect the performance of this operation.
1104
+ An Error (or subclass) exception is raised if the previous call to executeXXX()
1105
+ did not produce any result set or no call was issued yet.
1106
+ """
1107
+ self.messages = []
1108
+ return self._fetch()
1109
+
1110
+ def nextset(self):
1111
+ """Skip to the next available recordset, discarding any remaining rows from the current recordset.
1112
+
1113
+ If there are no more sets, the method returns None. Otherwise, it returns a true
1114
+ value and subsequent calls to the fetch methods will return rows from the next result set.
1115
+
1116
+ An Error (or subclass) exception is raised if the previous call to executeXXX()
1117
+ did not produce any result set or no call was issued yet.
1118
+ """
1119
+ self.messages = []
1120
+ if self.connection is None or self.rs is None:
1121
+ self._raiseCursorError(
1122
+ api.OperationalError,
1123
+ ("nextset() on closed connection or empty query set"),
1124
+ )
1125
+ return None
1126
+
1127
+ try: # [begin 2.1 ekelund]
1128
+ rsTuple = self.rs.NextRecordset() #
1129
+ except pywintypes.com_error as exc: # return appropriate error
1130
+ self._raiseCursorError(api.NotSupportedError, exc.args) # [end 2.1 ekelund]
1131
+ recordset = rsTuple[0]
1132
+ if recordset is None:
1133
+ return None
1134
+ self.build_column_info(recordset)
1135
+ return True
1136
+
1137
+ def setinputsizes(self, sizes):
1138
+ pass
1139
+
1140
+ def setoutputsize(self, size, column=None):
1141
+ pass
1142
+
1143
+ def _last_query(self): # let the programmer see what query we actually used
1144
+ try:
1145
+ if self.parameters is None:
1146
+ ret = self.commandText
1147
+ else:
1148
+ ret = f"{self.commandText},parameters={self.parameters!r}"
1149
+ except:
1150
+ ret = None
1151
+ return ret
1152
+
1153
+ query = property(_last_query, None, None, "returns the last query executed")
1154
+
1155
+
1156
+ if __name__ == "__main__":
1157
+ raise api.ProgrammingError(version + " cannot be run as a main program.")
myenv/Lib/site-packages/adodbapi/apibase.py ADDED
@@ -0,0 +1,724 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """adodbapi.apibase - A python DB API 2.0 (PEP 249) interface to Microsoft ADO
2
+
3
+ Copyright (C) 2002 Henrik Ekelund, version 2.1 by Vernon Cole
4
+ * http://sourceforge.net/projects/pywin32
5
+ * http://sourceforge.net/projects/adodbapi
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import datetime
11
+ import decimal
12
+ import numbers
13
+ import sys
14
+ import time
15
+ from collections.abc import Callable, Iterable, Mapping
16
+ from typing import Dict
17
+
18
+ # noinspection PyUnresolvedReferences
19
+ from . import ado_consts as adc
20
+
21
+ verbose = False # debugging flag
22
+
23
+
24
+ # ------- Error handlers ------
25
+ def standardErrorHandler(connection, cursor, errorclass, errorvalue):
26
+ err = (errorclass, errorvalue)
27
+ try:
28
+ connection.messages.append(err)
29
+ except:
30
+ pass
31
+ if cursor is not None:
32
+ try:
33
+ cursor.messages.append(err)
34
+ except:
35
+ pass
36
+ raise errorclass(errorvalue)
37
+
38
+
39
+ class Error(Exception):
40
+ pass # Exception that is the base class of all other error
41
+ # exceptions. You can use this to catch all errors with one
42
+ # single 'except' statement. Warnings are not considered
43
+ # errors and thus should not use this class as base. It must
44
+ # be a subclass of the Python StandardError (defined in the
45
+ # module exceptions).
46
+
47
+
48
+ class Warning(Exception):
49
+ pass
50
+
51
+
52
+ class InterfaceError(Error):
53
+ pass
54
+
55
+
56
+ class DatabaseError(Error):
57
+ pass
58
+
59
+
60
+ class InternalError(DatabaseError):
61
+ pass
62
+
63
+
64
+ class OperationalError(DatabaseError):
65
+ pass
66
+
67
+
68
+ class ProgrammingError(DatabaseError):
69
+ pass
70
+
71
+
72
+ class IntegrityError(DatabaseError):
73
+ pass
74
+
75
+
76
+ class DataError(DatabaseError):
77
+ pass
78
+
79
+
80
+ class NotSupportedError(DatabaseError):
81
+ pass
82
+
83
+
84
+ class FetchFailedError(OperationalError):
85
+ """
86
+ Error is used by RawStoredProcedureQuerySet to determine when a fetch
87
+ failed due to a connection being closed or there is no record set
88
+ returned. (Non-standard, added especially for django)
89
+ """
90
+
91
+ pass
92
+
93
+
94
+ # # # # # ----- Type Objects and Constructors ----- # # # # #
95
+ # Many databases need to have the input in a particular format for binding to an operation's input parameters.
96
+ # For example, if an input is destined for a DATE column, then it must be bound to the database in a particular
97
+ # string format. Similar problems exist for "Row ID" columns or large binary items (e.g. blobs or RAW columns).
98
+ # This presents problems for Python since the parameters to the executeXXX() method are untyped.
99
+ # When the database module sees a Python string object, it doesn't know if it should be bound as a simple CHAR
100
+ # column, as a raw BINARY item, or as a DATE.
101
+ #
102
+ # To overcome this problem, a module must provide the constructors defined below to create objects that can
103
+ # hold special values. When passed to the cursor methods, the module can then detect the proper type of
104
+ # the input parameter and bind it accordingly.
105
+
106
+ # A Cursor Object's description attribute returns information about each of the result columns of a query.
107
+ # The type_code must compare equal to one of Type Objects defined below. Type Objects may be equal to more than
108
+ # one type code (e.g. DATETIME could be equal to the type codes for date, time and timestamp columns;
109
+ # see the Implementation Hints below for details).
110
+
111
+ # SQL NULL values are represented by the Python None singleton on input and output.
112
+
113
+ # Note: Usage of Unix ticks for database interfacing can cause troubles because of the limited date range they cover.
114
+
115
+
116
+ # def Date(year,month,day):
117
+ # "This function constructs an object holding a date value. "
118
+ # return dateconverter.date(year,month,day) #dateconverter.Date(year,month,day)
119
+ #
120
+ # def Time(hour,minute,second):
121
+ # "This function constructs an object holding a time value. "
122
+ # return dateconverter.time(hour, minute, second) # dateconverter.Time(hour,minute,second)
123
+ #
124
+ # def Timestamp(year,month,day,hour,minute,second):
125
+ # "This function constructs an object holding a time stamp value. "
126
+ # return dateconverter.datetime(year,month,day,hour,minute,second)
127
+ #
128
+ # def DateFromTicks(ticks):
129
+ # """This function constructs an object holding a date value from the given ticks value
130
+ # (number of seconds since the epoch; see the documentation of the standard Python time module for details). """
131
+ # return Date(*time.gmtime(ticks)[:3])
132
+ #
133
+ # def TimeFromTicks(ticks):
134
+ # """This function constructs an object holding a time value from the given ticks value
135
+ # (number of seconds since the epoch; see the documentation of the standard Python time module for details). """
136
+ # return Time(*time.gmtime(ticks)[3:6])
137
+ #
138
+ # def TimestampFromTicks(ticks):
139
+ # """This function constructs an object holding a time stamp value from the given
140
+ # ticks value (number of seconds since the epoch;
141
+ # see the documentation of the standard Python time module for details). """
142
+ # return Timestamp(*time.gmtime(ticks)[:6])
143
+ #
144
+ # def Binary(aString):
145
+ # """This function constructs an object capable of holding a binary (long) string value. """
146
+ # b = bytes(aString)
147
+ # return b
148
+ # ----- Time converters ----------------------------------------------
149
+ class TimeConverter: # this is a generic time converter skeleton
150
+ def __init__(self): # the details will be filled in by instances
151
+ self._ordinal_1899_12_31 = datetime.date(1899, 12, 31).toordinal() - 1
152
+ # Use cls.types to compare if an input parameter is a datetime
153
+ self.types = {
154
+ # Dynamically get the types as the methods may be overriden
155
+ type(self.Date(2000, 1, 1)),
156
+ type(self.Time(12, 1, 1)),
157
+ type(self.Timestamp(2000, 1, 1, 12, 1, 1)),
158
+ datetime.datetime,
159
+ datetime.time,
160
+ datetime.date,
161
+ }
162
+
163
+ def COMDate(self, obj):
164
+ """Returns a ComDate from a date-time"""
165
+ try: # most likely a datetime
166
+ tt = obj.timetuple()
167
+
168
+ try:
169
+ ms = obj.microsecond
170
+ except:
171
+ ms = 0
172
+ return self.ComDateFromTuple(tt, ms)
173
+ except: # might be a tuple
174
+ try:
175
+ return self.ComDateFromTuple(obj)
176
+ except:
177
+ raise ValueError(f'Cannot convert "{obj!r}" to COMdate.')
178
+
179
+ def ComDateFromTuple(self, t, microseconds=0):
180
+ d = datetime.date(t[0], t[1], t[2])
181
+ integerPart = d.toordinal() - self._ordinal_1899_12_31
182
+ ms = (t[3] * 3600 + t[4] * 60 + t[5]) * 1000000 + microseconds
183
+ fractPart = float(ms) / 86400000000.0
184
+ return integerPart + fractPart
185
+
186
+ def DateObjectFromCOMDate(self, comDate):
187
+ "Returns an object of the wanted type from a ComDate"
188
+ raise NotImplementedError # "Abstract class"
189
+
190
+ def Date(self, year, month, day):
191
+ "This function constructs an object holding a date value."
192
+ raise NotImplementedError # "Abstract class"
193
+
194
+ def Time(self, hour, minute, second):
195
+ "This function constructs an object holding a time value."
196
+ raise NotImplementedError # "Abstract class"
197
+
198
+ def Timestamp(self, year, month, day, hour, minute, second):
199
+ "This function constructs an object holding a time stamp value."
200
+ raise NotImplementedError # "Abstract class"
201
+ # all purpose date to ISO format converter
202
+
203
+ def DateObjectToIsoFormatString(self, obj):
204
+ "This function should return a string in the format 'YYYY-MM-dd HH:MM:SS:ms' (ms optional)"
205
+ try: # most likely, a datetime.datetime
206
+ s = obj.isoformat(" ")
207
+ except (TypeError, AttributeError):
208
+ if isinstance(obj, datetime.date):
209
+ s = obj.isoformat() + " 00:00:00" # return exact midnight
210
+ else:
211
+ try: # but may be time.struct_time
212
+ s = time.strftime("%Y-%m-%d %H:%M:%S", obj)
213
+ except:
214
+ raise ValueError(f'Cannot convert "{obj!r}" to isoformat')
215
+ return s
216
+
217
+
218
+ class pythonDateTimeConverter(TimeConverter): # standard since Python 2.3
219
+ def __init__(self):
220
+ TimeConverter.__init__(self)
221
+
222
+ def DateObjectFromCOMDate(self, comDate):
223
+ if isinstance(comDate, datetime.datetime):
224
+ odn = comDate.toordinal()
225
+ tim = comDate.time()
226
+ new = datetime.datetime.combine(datetime.datetime.fromordinal(odn), tim)
227
+ return new
228
+ # return comDate.replace(tzinfo=None) # make non aware
229
+ else:
230
+ fComDate = float(comDate) # ComDate is number of days since 1899-12-31
231
+ integerPart = int(fComDate)
232
+ floatpart = fComDate - integerPart
233
+ ##if floatpart == 0.0:
234
+ ## return datetime.date.fromordinal(integerPart + self._ordinal_1899_12_31)
235
+ dte = datetime.datetime.fromordinal(
236
+ integerPart + self._ordinal_1899_12_31
237
+ ) + datetime.timedelta(milliseconds=floatpart * 86400000)
238
+ # millisecondsperday=86400000 # 24*60*60*1000
239
+ return dte
240
+
241
+ def Date(self, year, month, day):
242
+ return datetime.date(year, month, day)
243
+
244
+ def Time(self, hour, minute, second):
245
+ return datetime.time(hour, minute, second)
246
+
247
+ def Timestamp(self, year, month, day, hour, minute, second):
248
+ return datetime.datetime(year, month, day, hour, minute, second)
249
+
250
+
251
+ class pythonTimeConverter(TimeConverter): # the old, ?nix type date and time
252
+ def __init__(self): # caution: this Class gets confised by timezones and DST
253
+ TimeConverter.__init__(self)
254
+ self.types.add(time.struct_time)
255
+
256
+ def DateObjectFromCOMDate(self, comDate):
257
+ "Returns ticks since 1970"
258
+ if isinstance(comDate, datetime.datetime):
259
+ return comDate.timetuple()
260
+ else:
261
+ fcomDate = float(comDate)
262
+ secondsperday = 86400 # 24*60*60
263
+ # ComDate is number of days since 1899-12-31, gmtime epoch is 1970-1-1 = 25569 days
264
+ t = time.gmtime(secondsperday * (fcomDate - 25569.0))
265
+ return t # year,month,day,hour,minute,second,weekday,julianday,daylightsaving=t
266
+
267
+ def Date(self, year, month, day):
268
+ return self.Timestamp(year, month, day, 0, 0, 0)
269
+
270
+ def Time(self, hour, minute, second):
271
+ return time.gmtime((hour * 60 + minute) * 60 + second)
272
+
273
+ def Timestamp(self, year, month, day, hour, minute, second):
274
+ return time.localtime(
275
+ time.mktime((year, month, day, hour, minute, second, 0, 0, -1))
276
+ )
277
+
278
+
279
+ base_dateconverter = pythonDateTimeConverter()
280
+
281
+ # ------ DB API required module attributes ---------------------
282
+ threadsafety = 1 # TODO -- find out whether this module is actually BETTER than 1.
283
+
284
+ apilevel = "2.0" # String constant stating the supported DB API level.
285
+
286
+ paramstyle = "qmark" # the default parameter style
287
+
288
+ # ------ control for an extension which may become part of DB API 3.0 ---
289
+ accepted_paramstyles = ("qmark", "named", "format", "pyformat", "dynamic")
290
+
291
+ # ------------------------------------------------------------------------------------------
292
+ # define similar types for generic conversion routines
293
+ adoIntegerTypes = (
294
+ adc.adInteger,
295
+ adc.adSmallInt,
296
+ adc.adTinyInt,
297
+ adc.adUnsignedInt,
298
+ adc.adUnsignedSmallInt,
299
+ adc.adUnsignedTinyInt,
300
+ adc.adBoolean,
301
+ adc.adError,
302
+ ) # max 32 bits
303
+ adoRowIdTypes = (adc.adChapter,) # v2.1 Rose
304
+ adoLongTypes = (adc.adBigInt, adc.adFileTime, adc.adUnsignedBigInt)
305
+ adoExactNumericTypes = (
306
+ adc.adDecimal,
307
+ adc.adNumeric,
308
+ adc.adVarNumeric,
309
+ adc.adCurrency,
310
+ ) # v2.3 Cole
311
+ adoApproximateNumericTypes = (adc.adDouble, adc.adSingle) # v2.1 Cole
312
+ adoStringTypes = (
313
+ adc.adBSTR,
314
+ adc.adChar,
315
+ adc.adLongVarChar,
316
+ adc.adLongVarWChar,
317
+ adc.adVarChar,
318
+ adc.adVarWChar,
319
+ adc.adWChar,
320
+ )
321
+ adoBinaryTypes = (adc.adBinary, adc.adLongVarBinary, adc.adVarBinary)
322
+ adoDateTimeTypes = (adc.adDBTime, adc.adDBTimeStamp, adc.adDate, adc.adDBDate)
323
+ adoRemainingTypes = (
324
+ adc.adEmpty,
325
+ adc.adIDispatch,
326
+ adc.adIUnknown,
327
+ adc.adPropVariant,
328
+ adc.adArray,
329
+ adc.adUserDefined,
330
+ adc.adVariant,
331
+ adc.adGUID,
332
+ )
333
+
334
+
335
+ # this class is a trick to determine whether a type is a member of a related group of types. see PEP notes
336
+ class DBAPITypeObject:
337
+ def __init__(self, valuesTuple):
338
+ self.values = frozenset(valuesTuple)
339
+
340
+ def __eq__(self, other):
341
+ return other in self.values
342
+
343
+ def __ne__(self, other):
344
+ return other not in self.values
345
+
346
+
347
+ """This type object is used to describe columns in a database that are string-based (e.g. CHAR). """
348
+ STRING = DBAPITypeObject(adoStringTypes)
349
+
350
+ """This type object is used to describe (long) binary columns in a database (e.g. LONG, RAW, BLOBs). """
351
+ BINARY = DBAPITypeObject(adoBinaryTypes)
352
+
353
+ """This type object is used to describe numeric columns in a database. """
354
+ NUMBER = DBAPITypeObject(
355
+ adoIntegerTypes + adoLongTypes + adoExactNumericTypes + adoApproximateNumericTypes
356
+ )
357
+
358
+ """This type object is used to describe date/time columns in a database. """
359
+
360
+ DATETIME = DBAPITypeObject(adoDateTimeTypes)
361
+ """This type object is used to describe the "Row ID" column in a database. """
362
+ ROWID = DBAPITypeObject(adoRowIdTypes)
363
+
364
+ OTHER = DBAPITypeObject(adoRemainingTypes)
365
+
366
+ # ------- utilities for translating python data types to ADO data types ---------------------------------
367
+ typeMap = {
368
+ memoryview: adc.adVarBinary,
369
+ float: adc.adDouble,
370
+ type(None): adc.adEmpty,
371
+ str: adc.adBSTR,
372
+ bool: adc.adBoolean, # v2.1 Cole
373
+ decimal.Decimal: adc.adDecimal,
374
+ int: adc.adBigInt,
375
+ bytes: adc.adVarBinary,
376
+ }
377
+
378
+
379
+ def pyTypeToADOType(d):
380
+ tp = type(d)
381
+ try:
382
+ return typeMap[tp]
383
+ except KeyError: # The type was not defined in the pre-computed Type table
384
+ from . import dateconverter
385
+
386
+ # maybe it is one of our supported Date/Time types
387
+ if tp in dateconverter.types:
388
+ return adc.adDate
389
+ # otherwise, attempt to discern the type by probing the data object itself -- to handle duck typing
390
+ if isinstance(d, str):
391
+ return adc.adBSTR
392
+ if isinstance(d, numbers.Integral):
393
+ return adc.adBigInt
394
+ if isinstance(d, numbers.Real):
395
+ return adc.adDouble
396
+ raise DataError(f'cannot convert "{d!r}" (type={tp}) to ADO')
397
+
398
+
399
+ # # # # # # # # # # # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
400
+ # functions to convert database values to Python objects
401
+ # ------------------------------------------------------------------------
402
+ # variant type : function converting variant to Python value
403
+ def variantConvertDate(v):
404
+ from . import dateconverter # this function only called when adodbapi is running
405
+
406
+ return dateconverter.DateObjectFromCOMDate(v)
407
+
408
+
409
+ def cvtString(variant): # use to get old action of adodbapi v1 if desired
410
+ return str(variant)
411
+
412
+
413
+ def cvtDecimal(variant): # better name
414
+ return _convertNumberWithCulture(variant, decimal.Decimal)
415
+
416
+
417
+ def cvtNumeric(variant): # older name - don't break old code
418
+ return cvtDecimal(variant)
419
+
420
+
421
+ def cvtFloat(variant):
422
+ return _convertNumberWithCulture(variant, float)
423
+
424
+
425
+ def _convertNumberWithCulture(variant, f):
426
+ try:
427
+ return f(variant)
428
+ except (ValueError, TypeError, decimal.InvalidOperation):
429
+ try:
430
+ europeVsUS = str(variant).replace(",", ".")
431
+ return f(europeVsUS)
432
+ except (ValueError, TypeError, decimal.InvalidOperation):
433
+ pass
434
+
435
+
436
+ def cvtInt(variant):
437
+ return int(variant)
438
+
439
+
440
+ def cvtLong(variant): # only important in old versions where long and int differ
441
+ return int(variant)
442
+
443
+
444
+ def cvtBuffer(variant):
445
+ return bytes(variant)
446
+
447
+
448
+ def cvtUnicode(variant):
449
+ return str(variant)
450
+
451
+
452
+ def identity(x):
453
+ return x
454
+
455
+
456
+ def cvtUnusual(variant):
457
+ if verbose > 1:
458
+ sys.stderr.write(f"Conversion called for Unusual data={variant!r}\n")
459
+ return variant # cannot find conversion function -- just give the data to the user
460
+
461
+
462
+ def convert_to_python(variant, func): # convert DB value into Python value
463
+ if variant is None:
464
+ return None
465
+ return func(variant) # call the appropriate conversion function
466
+
467
+
468
+ class MultiMap(Dict[int, Callable[[object], object]]):
469
+ # builds a dictionary from {(iterable,of,keys) : function}
470
+ """A dictionary of ado.type : function
471
+ -- but you can set multiple items by passing an iterable of keys"""
472
+
473
+ # useful for defining conversion functions for groups of similar data types.
474
+ def __init__(self, aDict: Mapping[Iterable[int] | int, Callable[[object], object]]):
475
+ for k, v in aDict.items():
476
+ self[k] = v # we must call __setitem__
477
+
478
+ def __setitem__(
479
+ self, adoType: Iterable[int] | int, cvtFn: Callable[[object], object]
480
+ ):
481
+ "set a single item, or a whole iterable of items"
482
+ if isinstance(adoType, Iterable):
483
+ # user passed us an iterable, set them individually
484
+ for type in adoType:
485
+ dict.__setitem__(self, type, cvtFn)
486
+ else:
487
+ dict.__setitem__(self, adoType, cvtFn)
488
+
489
+
490
+ # initialize variantConversions dictionary used to convert SQL to Python
491
+ # this is the dictionary of default conversion functions, built by the class above.
492
+ # this becomes a class attribute for the Connection, and that attribute is used
493
+ # to build the list of column conversion functions for the Cursor
494
+ variantConversions = MultiMap(
495
+ {
496
+ adoDateTimeTypes: variantConvertDate,
497
+ adoApproximateNumericTypes: cvtFloat,
498
+ adoExactNumericTypes: cvtDecimal, # use to force decimal rather than unicode
499
+ adoLongTypes: cvtLong,
500
+ adoIntegerTypes: cvtInt,
501
+ adoRowIdTypes: cvtInt,
502
+ adoStringTypes: identity,
503
+ adoBinaryTypes: cvtBuffer,
504
+ adoRemainingTypes: cvtUnusual,
505
+ }
506
+ )
507
+
508
+ # # # # # classes to emulate the result of cursor.fetchxxx() as a sequence of sequences # # # # #
509
+ # "an ENUM of how my low level records are laid out"
510
+ RS_WIN_32, RS_ARRAY, RS_REMOTE = list(range(1, 4))
511
+
512
+
513
+ class SQLrow: # a single database row
514
+ # class to emulate a sequence, so that a column may be retrieved by either number or name
515
+ def __init__(self, rows, index): # "rows" is an _SQLrows object, index is which row
516
+ self.rows = rows # parent 'fetch' container object
517
+ self.index = index # my row number within parent
518
+
519
+ def __getattr__(self, name): # used for row.columnName type of value access
520
+ try:
521
+ return self._getValue(self.rows.columnNames[name.lower()])
522
+ except KeyError:
523
+ raise AttributeError('Unknown column name "{}"'.format(name))
524
+
525
+ def _getValue(self, key): # key must be an integer
526
+ if (
527
+ self.rows.recordset_format == RS_ARRAY
528
+ ): # retrieve from two-dimensional array
529
+ v = self.rows.ado_results[key, self.index]
530
+ elif self.rows.recordset_format == RS_REMOTE:
531
+ v = self.rows.ado_results[self.index][key]
532
+ else: # pywin32 - retrieve from tuple of tuples
533
+ v = self.rows.ado_results[key][self.index]
534
+ if self.rows.converters is NotImplemented:
535
+ return v
536
+ return convert_to_python(v, self.rows.converters[key])
537
+
538
+ def __len__(self):
539
+ return self.rows.numberOfColumns
540
+
541
+ def __getitem__(self, key): # used for row[key] type of value access
542
+ if isinstance(key, int): # normal row[1] designation
543
+ try:
544
+ return self._getValue(key)
545
+ except IndexError:
546
+ raise
547
+ if isinstance(key, slice):
548
+ indices = key.indices(self.rows.numberOfColumns)
549
+ vl = [self._getValue(i) for i in range(*indices)]
550
+ return tuple(vl)
551
+ try:
552
+ return self._getValue(
553
+ self.rows.columnNames[key.lower()]
554
+ ) # extension row[columnName] designation
555
+ except (KeyError, TypeError):
556
+ er, st, tr = sys.exc_info()
557
+ raise er(f'No such key as "{key!r}" in {self!r}').with_traceback(tr)
558
+
559
+ def __iter__(self):
560
+ return iter(self.__next__())
561
+
562
+ def __next__(self):
563
+ for n in range(self.rows.numberOfColumns):
564
+ yield self._getValue(n)
565
+
566
+ def __repr__(self): # create a human readable representation
567
+ taglist = sorted(list(self.rows.columnNames.items()), key=lambda x: x[1])
568
+ s = "<SQLrow={"
569
+ for name, i in taglist:
570
+ s += f"{name}:{self._getValue(i)!r}, "
571
+ return s[:-2] + "}>"
572
+
573
+ def __str__(self): # create a pretty human readable representation
574
+ return str(
575
+ tuple(str(self._getValue(i)) for i in range(self.rows.numberOfColumns))
576
+ )
577
+
578
+ # TO-DO implement pickling an SQLrow directly
579
+ # def __getstate__(self): return self.__dict__
580
+ # def __setstate__(self, d): self.__dict__.update(d)
581
+ # which basically tell pickle to treat your class just like a normal one,
582
+ # taking self.__dict__ as representing the whole of the instance state,
583
+ # despite the existence of the __getattr__.
584
+ # # # #
585
+
586
+
587
+ class SQLrows:
588
+ # class to emulate a sequence for multiple rows using a container object
589
+ def __init__(self, ado_results, numberOfRows, cursor):
590
+ self.ado_results = ado_results # raw result of SQL get
591
+ try:
592
+ self.recordset_format = cursor.recordset_format
593
+ self.numberOfColumns = cursor.numberOfColumns
594
+ self.converters = cursor.converters
595
+ self.columnNames = cursor.columnNames
596
+ except AttributeError:
597
+ self.recordset_format = RS_ARRAY
598
+ self.numberOfColumns = 0
599
+ self.converters = []
600
+ self.columnNames = {}
601
+ self.numberOfRows = numberOfRows
602
+
603
+ def __len__(self):
604
+ return self.numberOfRows
605
+
606
+ def __getitem__(self, item): # used for row or row,column access
607
+ if not self.ado_results:
608
+ return []
609
+ if isinstance(item, slice): # will return a list of row objects
610
+ indices = item.indices(self.numberOfRows)
611
+ return [SQLrow(self, k) for k in range(*indices)]
612
+ elif isinstance(item, tuple) and len(item) == 2:
613
+ # d = some_rowsObject[i,j] will return a datum from a two-dimension address
614
+ i, j = item
615
+ if not isinstance(j, int):
616
+ try:
617
+ j = self.columnNames[j.lower()] # convert named column to numeric
618
+ except KeyError:
619
+ raise KeyError(f"adodbapi: no such column name as {j!r}")
620
+ if self.recordset_format == RS_ARRAY: # retrieve from two-dimensional array
621
+ v = self.ado_results[j, i]
622
+ elif self.recordset_format == RS_REMOTE:
623
+ v = self.ado_results[i][j]
624
+ else: # pywin32 - retrieve from tuple of tuples
625
+ v = self.ado_results[j][i]
626
+ if self.converters is NotImplemented:
627
+ return v
628
+ return convert_to_python(v, self.converters[j])
629
+ else:
630
+ row = SQLrow(self, item) # new row descriptor
631
+ return row
632
+
633
+ def __iter__(self):
634
+ return iter(self.__next__())
635
+
636
+ def __next__(self):
637
+ for n in range(self.numberOfRows):
638
+ row = SQLrow(self, n)
639
+ yield row
640
+ # # # # #
641
+
642
+ # # # # # functions to re-format SQL requests to other paramstyle requirements # # # # # # # # # #
643
+
644
+
645
+ def changeNamedToQmark(
646
+ op,
647
+ ): # convert from 'named' paramstyle to ADO required '?'mark parameters
648
+ outOp = ""
649
+ outparms = []
650
+ chunks = op.split(
651
+ "'"
652
+ ) # quote all literals -- odd numbered list results are literals.
653
+ inQuotes = False
654
+ for chunk in chunks:
655
+ if inQuotes: # this is inside a quote
656
+ if chunk == "": # double apostrophe to quote one apostrophe
657
+ outOp = outOp[:-1] # so take one away
658
+ else:
659
+ outOp += "'" + chunk + "'" # else pass the quoted string as is.
660
+ else: # is SQL code -- look for a :namedParameter
661
+ while chunk: # some SQL string remains
662
+ sp = chunk.split(":", 1)
663
+ outOp += sp[0] # concat the part up to the :
664
+ s = ""
665
+ try:
666
+ chunk = sp[1]
667
+ except IndexError:
668
+ chunk = None
669
+ if chunk: # there was a parameter - parse it out
670
+ i = 0
671
+ c = chunk[0]
672
+ while c.isalnum() or c == "_":
673
+ i += 1
674
+ try:
675
+ c = chunk[i]
676
+ except IndexError:
677
+ break
678
+ s = chunk[:i]
679
+ chunk = chunk[i:]
680
+ if s:
681
+ outparms.append(s) # list the parameters in order
682
+ outOp += "?" # put in the Qmark
683
+ inQuotes = not inQuotes
684
+ return outOp, outparms
685
+
686
+
687
+ def changeFormatToQmark(
688
+ op,
689
+ ): # convert from 'format' paramstyle to ADO required '?'mark parameters
690
+ outOp = ""
691
+ outparams = []
692
+ chunks = op.split(
693
+ "'"
694
+ ) # quote all literals -- odd numbered list results are literals.
695
+ inQuotes = False
696
+ for chunk in chunks:
697
+ if inQuotes:
698
+ if (
699
+ outOp != "" and chunk == ""
700
+ ): # he used a double apostrophe to quote one apostrophe
701
+ outOp = outOp[:-1] # so take one away
702
+ else:
703
+ outOp += "'" + chunk + "'" # else pass the quoted string as is.
704
+ else: # is SQL code -- look for a %s parameter
705
+ if "%(" in chunk: # ugh! pyformat!
706
+ while chunk: # some SQL string remains
707
+ sp = chunk.split("%(", 1)
708
+ outOp += sp[0] # concat the part up to the %
709
+ if len(sp) > 1:
710
+ try:
711
+ s, chunk = sp[1].split(")s", 1) # find the ')s'
712
+ except ValueError:
713
+ raise ProgrammingError(
714
+ 'Pyformat SQL has incorrect format near "%s"' % chunk
715
+ )
716
+ outparams.append(s)
717
+ outOp += "?" # put in the Qmark
718
+ else:
719
+ chunk = None
720
+ else: # proper '%s' format
721
+ sp = chunk.split("%s") # make each %s
722
+ outOp += "?".join(sp) # into ?
723
+ inQuotes = not inQuotes # every other chunk is a quoted string
724
+ return outOp, outparams
myenv/Lib/site-packages/adodbapi/examples/__pycache__/db_print.cpython-311.pyc ADDED
Binary file (3.33 kB). View file
 
myenv/Lib/site-packages/adodbapi/examples/__pycache__/db_table_names.cpython-311.pyc ADDED
Binary file (962 Bytes). View file
 
myenv/Lib/site-packages/adodbapi/examples/__pycache__/xls_read.cpython-311.pyc ADDED
Binary file (1.82 kB). View file
 
myenv/Lib/site-packages/adodbapi/examples/__pycache__/xls_write.cpython-311.pyc ADDED
Binary file (2.18 kB). View file
 
myenv/Lib/site-packages/adodbapi/examples/db_print.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ db_print.py -- a simple demo for ADO database reads."""
2
+
3
+ import sys
4
+
5
+ import adodbapi.ado_consts as adc
6
+
7
+ cmd_args = ("filename", "table_name")
8
+ if "help" in sys.argv:
9
+ print("possible settings keywords are:", cmd_args)
10
+ sys.exit()
11
+
12
+ kw_args = {} # pick up filename and proxy address from command line (optionally)
13
+ for arg in sys.argv:
14
+ s = arg.split("=")
15
+ if len(s) > 1:
16
+ if s[0] in cmd_args:
17
+ kw_args[s[0]] = s[1]
18
+
19
+ kw_args.setdefault(
20
+ "filename", "test.mdb"
21
+ ) # assumes server is running from examples folder
22
+ kw_args.setdefault("table_name", "Products") # the name of the demo table
23
+
24
+ # the server needs to select the provider based on his Python installation
25
+ provider_switch = ["provider", "Microsoft.ACE.OLEDB.12.0", "Microsoft.Jet.OLEDB.4.0"]
26
+
27
+ # ------------------------ START HERE -------------------------------------
28
+ # create the connection
29
+ constr = "Provider=%(provider)s;Data Source=%(filename)s"
30
+ import adodbapi as db
31
+
32
+ con = db.connect(constr, kw_args, macro_is64bit=provider_switch)
33
+
34
+ if kw_args["table_name"] == "?":
35
+ print("The tables in your database are:")
36
+ for name in con.get_table_names():
37
+ print(name)
38
+ else:
39
+ # make a cursor on the connection
40
+ with con.cursor() as c:
41
+ # run an SQL statement on the cursor
42
+ sql = "select * from %s" % kw_args["table_name"]
43
+ print('performing query="%s"' % sql)
44
+ c.execute(sql)
45
+
46
+ # check the results
47
+ print(
48
+ 'result rowcount shows as= %d. (Note: -1 means "not known")' % (c.rowcount,)
49
+ )
50
+ print("")
51
+ print("result data description is:")
52
+ print(" NAME Type DispSize IntrnlSz Prec Scale Null?")
53
+ for d in c.description:
54
+ print(
55
+ ("%16s %-12s %8s %8d %4d %5d %s")
56
+ % (d[0], adc.adTypeNames[d[1]], d[2], d[3], d[4], d[5], bool(d[6]))
57
+ )
58
+ print("")
59
+ print("str() of first five records are...")
60
+
61
+ # get the results
62
+ db = c.fetchmany(5)
63
+
64
+ # print them
65
+ for rec in db:
66
+ print(rec)
67
+
68
+ print("")
69
+ print("repr() of next row is...")
70
+ print(repr(c.fetchone()))
71
+ print("")
72
+ con.close()
myenv/Lib/site-packages/adodbapi/examples/db_table_names.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ db_table_names.py -- a simple demo for ADO database table listing."""
2
+
3
+ import sys
4
+
5
+ import adodbapi
6
+
7
+ try:
8
+ databasename = sys.argv[1]
9
+ except IndexError:
10
+ databasename = "test.mdb"
11
+
12
+ provider = ["prv", "Microsoft.ACE.OLEDB.12.0", "Microsoft.Jet.OLEDB.4.0"]
13
+ constr = "Provider=%(prv)s;Data Source=%(db)s"
14
+
15
+ # create the connection
16
+ con = adodbapi.connect(constr, db=databasename, macro_is64bit=provider)
17
+
18
+ print("Table names in= %s" % databasename)
19
+
20
+ for table in con.get_table_names():
21
+ print(table)
myenv/Lib/site-packages/adodbapi/examples/xls_read.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+
3
+ import adodbapi
4
+
5
+ try:
6
+ import adodbapi.is64bit as is64bit
7
+
8
+ is64 = is64bit.Python()
9
+ except ImportError:
10
+ is64 = False
11
+
12
+ if is64:
13
+ driver = "Microsoft.ACE.OLEDB.12.0"
14
+ else:
15
+ driver = "Microsoft.Jet.OLEDB.4.0"
16
+ extended = 'Extended Properties="Excel 8.0;HDR=Yes;IMEX=1;"'
17
+
18
+ try: # first command line argument will be xls file name -- default to the one written by xls_write.py
19
+ filename = sys.argv[1]
20
+ except IndexError:
21
+ filename = "xx.xls"
22
+
23
+ constr = "Provider=%s;Data Source=%s;%s" % (driver, filename, extended)
24
+
25
+ conn = adodbapi.connect(constr)
26
+
27
+ try: # second command line argument will be worksheet name -- default to first worksheet
28
+ sheet = sys.argv[2]
29
+ except IndexError:
30
+ # use ADO feature to get the name of the first worksheet
31
+ sheet = conn.get_table_names()[0]
32
+
33
+ print("Shreadsheet=%s Worksheet=%s" % (filename, sheet))
34
+ print("------------------------------------------------------------")
35
+ crsr = conn.cursor()
36
+ sql = "SELECT * from [%s]" % sheet
37
+ crsr.execute(sql)
38
+ for row in crsr.fetchmany(10):
39
+ print(repr(row))
40
+ crsr.close()
41
+ conn.close()
myenv/Lib/site-packages/adodbapi/examples/xls_write.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+
3
+ import adodbapi
4
+
5
+ try:
6
+ import adodbapi.is64bit as is64bit
7
+
8
+ is64 = is64bit.Python()
9
+ except ImportError:
10
+ is64 = False # in case the user has an old version of adodbapi
11
+ if is64:
12
+ driver = "Microsoft.ACE.OLEDB.12.0"
13
+ else:
14
+ driver = "Microsoft.Jet.OLEDB.4.0"
15
+ filename = "xx.xls" # file will be created if it does not exist
16
+ extended = 'Extended Properties="Excel 8.0;Readonly=False;"'
17
+
18
+ constr = "Provider=%s;Data Source=%s;%s" % (driver, filename, extended)
19
+
20
+ conn = adodbapi.connect(constr)
21
+ with conn: # will auto commit if no errors
22
+ with conn.cursor() as crsr:
23
+ try:
24
+ crsr.execute("drop table SheetOne")
25
+ except:
26
+ pass # just is case there is one already there
27
+
28
+ # create the sheet and the header row and set the types for the columns
29
+ crsr.execute(
30
+ "create table SheetOne (Name varchar, Rank varchar, SrvcNum integer, Weight float, Birth date)"
31
+ )
32
+
33
+ sql = "INSERT INTO SheetOne (name, rank , srvcnum, weight, birth) values (?,?,?,?,?)"
34
+
35
+ data = ("Mike Murphy", "SSG", 123456789, 167.8, datetime.date(1922, 12, 27))
36
+ crsr.execute(sql, data) # write the first row of data
37
+ crsr.execute(
38
+ sql, ["John Jones", "Pvt", 987654321, 140.0, datetime.date(1921, 7, 4)]
39
+ ) # another row of data
40
+ conn.close()
41
+ print("Created spreadsheet=%s worksheet=%s" % (filename, "SheetOne"))
myenv/Lib/site-packages/adodbapi/is64bit.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """is64bit.Python() --> boolean value of detected Python word size. is64bit.os() --> os build version"""
2
+
3
+ import sys
4
+
5
+
6
+ def Python():
7
+ return sys.maxsize > 2147483647
8
+
9
+
10
+ def os():
11
+ import platform
12
+
13
+ pm = platform.machine()
14
+ if pm != ".." and pm.endswith("64"): # recent 64 bit Python
15
+ return True
16
+ else:
17
+ import os
18
+
19
+ if "PROCESSOR_ARCHITEW6432" in os.environ:
20
+ return True # 32 bit program running on 64 bit Windows
21
+ try:
22
+ return os.environ["PROCESSOR_ARCHITECTURE"].endswith(
23
+ "64"
24
+ ) # 64 bit Windows 64 bit program
25
+ except (IndexError, KeyError):
26
+ pass # not Windows
27
+ try:
28
+ return "64" in platform.architecture()[0] # this often works in Linux
29
+ except:
30
+ return False # is an older version of Python, assume also an older os (best we can guess)
31
+
32
+
33
+ if __name__ == "__main__":
34
+ print("is64bit.Python() =", Python(), "is64bit.os() =", os())
myenv/Lib/site-packages/adodbapi/license.txt ADDED
@@ -0,0 +1,505 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 2.1, February 1999
3
+
4
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
5
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
6
+ Everyone is permitted to copy and distribute verbatim copies
7
+ of this license document, but changing it is not allowed.
8
+
9
+ [This is the first released version of the Lesser GPL. It also counts
10
+ as the successor of the GNU Library Public License, version 2, hence
11
+ the version number 2.1.]
12
+
13
+ Preamble
14
+
15
+ The licenses for most software are designed to take away your
16
+ freedom to share and change it. By contrast, the GNU General Public
17
+ Licenses are intended to guarantee your freedom to share and change
18
+ free software--to make sure the software is free for all its users.
19
+
20
+ This license, the Lesser General Public License, applies to some
21
+ specially designated software packages--typically libraries--of the
22
+ Free Software Foundation and other authors who decide to use it. You
23
+ can use it too, but we suggest you first think carefully about whether
24
+ this license or the ordinary General Public License is the better
25
+ strategy to use in any particular case, based on the explanations below.
26
+
27
+ When we speak of free software, we are referring to freedom of use,
28
+ not price. Our General Public Licenses are designed to make sure that
29
+ you have the freedom to distribute copies of free software (and charge
30
+ for this service if you wish); that you receive source code or can get
31
+ it if you want it; that you can change the software and use pieces of
32
+ it in new free programs; and that you are informed that you can do
33
+ these things.
34
+
35
+ To protect your rights, we need to make restrictions that forbid
36
+ distributors to deny you these rights or to ask you to surrender these
37
+ rights. These restrictions translate to certain responsibilities for
38
+ you if you distribute copies of the library or if you modify it.
39
+
40
+ For example, if you distribute copies of the library, whether gratis
41
+ or for a fee, you must give the recipients all the rights that we gave
42
+ you. You must make sure that they, too, receive or can get the source
43
+ code. If you link other code with the library, you must provide
44
+ complete object files to the recipients, so that they can relink them
45
+ with the library after making changes to the library and recompiling
46
+ it. And you must show them these terms so they know their rights.
47
+
48
+ We protect your rights with a two-step method: (1) we copyright the
49
+ library, and (2) we offer you this license, which gives you legal
50
+ permission to copy, distribute and/or modify the library.
51
+
52
+ To protect each distributor, we want to make it very clear that
53
+ there is no warranty for the free library. Also, if the library is
54
+ modified by someone else and passed on, the recipients should know
55
+ that what they have is not the original version, so that the original
56
+ author's reputation will not be affected by problems that might be
57
+ introduced by others.
58
+
59
+
60
+
61
+ Finally, software patents pose a constant threat to the existence of
62
+ any free program. We wish to make sure that a company cannot
63
+ effectively restrict the users of a free program by obtaining a
64
+ restrictive license from a patent holder. Therefore, we insist that
65
+ any patent license obtained for a version of the library must be
66
+ consistent with the full freedom of use specified in this license.
67
+
68
+ Most GNU software, including some libraries, is covered by the
69
+ ordinary GNU General Public License. This license, the GNU Lesser
70
+ General Public License, applies to certain designated libraries, and
71
+ is quite different from the ordinary General Public License. We use
72
+ this license for certain libraries in order to permit linking those
73
+ libraries into non-free programs.
74
+
75
+ When a program is linked with a library, whether statically or using
76
+ a shared library, the combination of the two is legally speaking a
77
+ combined work, a derivative of the original library. The ordinary
78
+ General Public License therefore permits such linking only if the
79
+ entire combination fits its criteria of freedom. The Lesser General
80
+ Public License permits more lax criteria for linking other code with
81
+ the library.
82
+
83
+ We call this license the "Lesser" General Public License because it
84
+ does Less to protect the user's freedom than the ordinary General
85
+ Public License. It also provides other free software developers Less
86
+ of an advantage over competing non-free programs. These disadvantages
87
+ are the reason we use the ordinary General Public License for many
88
+ libraries. However, the Lesser license provides advantages in certain
89
+ special circumstances.
90
+
91
+ For example, on rare occasions, there may be a special need to
92
+ encourage the widest possible use of a certain library, so that it becomes
93
+ a de-facto standard. To achieve this, non-free programs must be
94
+ allowed to use the library. A more frequent case is that a free
95
+ library does the same job as widely used non-free libraries. In this
96
+ case, there is little to gain by limiting the free library to free
97
+ software only, so we use the Lesser General Public License.
98
+
99
+ In other cases, permission to use a particular library in non-free
100
+ programs enables a greater number of people to use a large body of
101
+ free software. For example, permission to use the GNU C Library in
102
+ non-free programs enables many more people to use the whole GNU
103
+ operating system, as well as its variant, the GNU/Linux operating
104
+ system.
105
+
106
+ Although the Lesser General Public License is Less protective of the
107
+ users' freedom, it does ensure that the user of a program that is
108
+ linked with the Library has the freedom and the wherewithal to run
109
+ that program using a modified version of the Library.
110
+
111
+ The precise terms and conditions for copying, distribution and
112
+ modification follow. Pay close attention to the difference between a
113
+ "work based on the library" and a "work that uses the library". The
114
+ former contains code derived from the library, whereas the latter must
115
+ be combined with the library in order to run.
116
+
117
+
118
+
119
+ GNU LESSER GENERAL PUBLIC LICENSE
120
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
121
+
122
+ 0. This License Agreement applies to any software library or other
123
+ program which contains a notice placed by the copyright holder or
124
+ other authorized party saying it may be distributed under the terms of
125
+ this Lesser General Public License (also called "this License").
126
+ Each licensee is addressed as "you".
127
+
128
+ A "library" means a collection of software functions and/or data
129
+ prepared so as to be conveniently linked with application programs
130
+ (which use some of those functions and data) to form executables.
131
+
132
+ The "Library", below, refers to any such software library or work
133
+ which has been distributed under these terms. A "work based on the
134
+ Library" means either the Library or any derivative work under
135
+ copyright law: that is to say, a work containing the Library or a
136
+ portion of it, either verbatim or with modifications and/or translated
137
+ straightforwardly into another language. (Hereinafter, translation is
138
+ included without limitation in the term "modification".)
139
+
140
+ "Source code" for a work means the preferred form of the work for
141
+ making modifications to it. For a library, complete source code means
142
+ all the source code for all modules it contains, plus any associated
143
+ interface definition files, plus the scripts used to control compilation
144
+ and installation of the library.
145
+
146
+ Activities other than copying, distribution and modification are not
147
+ covered by this License; they are outside its scope. The act of
148
+ running a program using the Library is not restricted, and output from
149
+ such a program is covered only if its contents constitute a work based
150
+ on the Library (independent of the use of the Library in a tool for
151
+ writing it). Whether that is true depends on what the Library does
152
+ and what the program that uses the Library does.
153
+
154
+ 1. You may copy and distribute verbatim copies of the Library's
155
+ complete source code as you receive it, in any medium, provided that
156
+ you conspicuously and appropriately publish on each copy an
157
+ appropriate copyright notice and disclaimer of warranty; keep intact
158
+ all the notices that refer to this License and to the absence of any
159
+ warranty; and distribute a copy of this License along with the
160
+ Library.
161
+ You may charge a fee for the physical act of transferring a copy,
162
+ and you may at your option offer warranty protection in exchange for a
163
+ fee.
164
+
165
+ 2. You may modify your copy or copies of the Library or any portion
166
+ of it, thus forming a work based on the Library, and copy and
167
+ distribute such modifications or work under the terms of Section 1
168
+ above, provided that you also meet all of these conditions:
169
+
170
+ a) The modified work must itself be a software library.
171
+
172
+ b) You must cause the files modified to carry prominent notices
173
+ stating that you changed the files and the date of any change.
174
+
175
+ c) You must cause the whole of the work to be licensed at no
176
+ charge to all third parties under the terms of this License.
177
+
178
+ d) If a facility in the modified Library refers to a function or a
179
+ table of data to be supplied by an application program that uses
180
+ the facility, other than as an argument passed when the facility
181
+ is invoked, then you must make a good faith effort to ensure that,
182
+ in the event an application does not supply such function or
183
+ table, the facility still operates, and performs whatever part of
184
+ its purpose remains meaningful.
185
+
186
+ (For example, a function in a library to compute square roots has
187
+ a purpose that is entirely well-defined independent of the
188
+ application. Therefore, Subsection 2d requires that any
189
+ application-supplied function or table used by this function must
190
+ be optional: if the application does not supply it, the square
191
+ root function must still compute square roots.)
192
+
193
+ These requirements apply to the modified work as a whole. If
194
+ identifiable sections of that work are not derived from the Library,
195
+ and can be reasonably considered independent and separate works in
196
+ themselves, then this License, and its terms, do not apply to those
197
+ sections when you distribute them as separate works. But when you
198
+ distribute the same sections as part of a whole which is a work based
199
+ on the Library, the distribution of the whole must be on the terms of
200
+ this License, whose permissions for other licensees extend to the
201
+ entire whole, and thus to each and every part regardless of who wrote
202
+ it.
203
+
204
+ Thus, it is not the intent of this section to claim rights or contest
205
+ your rights to work written entirely by you; rather, the intent is to
206
+ exercise the right to control the distribution of derivative or
207
+ collective works based on the Library.
208
+
209
+ In addition, mere aggregation of another work not based on the Library
210
+ with the Library (or with a work based on the Library) on a volume of
211
+ a storage or distribution medium does not bring the other work under
212
+ the scope of this License.
213
+
214
+ 3. You may opt to apply the terms of the ordinary GNU General Public
215
+ License instead of this License to a given copy of the Library. To do
216
+ this, you must alter all the notices that refer to this License, so
217
+ that they refer to the ordinary GNU General Public License, version 2,
218
+ instead of to this License. (If a newer version than version 2 of the
219
+ ordinary GNU General Public License has appeared, then you can specify
220
+ that version instead if you wish.) Do not make any other change in
221
+ these notices.
222
+
223
+ Once this change is made in a given copy, it is irreversible for
224
+ that copy, so the ordinary GNU General Public License applies to all
225
+ subsequent copies and derivative works made from that copy.
226
+
227
+ This option is useful when you wish to copy part of the code of
228
+ the Library into a program that is not a library.
229
+
230
+ 4. You may copy and distribute the Library (or a portion or
231
+ derivative of it, under Section 2) in object code or executable form
232
+ under the terms of Sections 1 and 2 above provided that you accompany
233
+ it with the complete corresponding machine-readable source code, which
234
+ must be distributed under the terms of Sections 1 and 2 above on a
235
+ medium customarily used for software interchange.
236
+
237
+ If distribution of object code is made by offering access to copy
238
+ from a designated place, then offering equivalent access to copy the
239
+ source code from the same place satisfies the requirement to
240
+ distribute the source code, even though third parties are not
241
+ compelled to copy the source along with the object code.
242
+
243
+ 5. A program that contains no derivative of any portion of the
244
+ Library, but is designed to work with the Library by being compiled or
245
+ linked with it, is called a "work that uses the Library". Such a
246
+ work, in isolation, is not a derivative work of the Library, and
247
+ therefore falls outside the scope of this License.
248
+
249
+ However, linking a "work that uses the Library" with the Library
250
+ creates an executable that is a derivative of the Library (because it
251
+ contains portions of the Library), rather than a "work that uses the
252
+ library". The executable is therefore covered by this License.
253
+ Section 6 states terms for distribution of such executables.
254
+
255
+ When a "work that uses the Library" uses material from a header file
256
+ that is part of the Library, the object code for the work may be a
257
+ derivative work of the Library even though the source code is not.
258
+ Whether this is true is especially significant if the work can be
259
+ linked without the Library, or if the work is itself a library. The
260
+ threshold for this to be true is not precisely defined by law.
261
+
262
+ If such an object file uses only numerical parameters, data
263
+ structure layouts and accessors, and small macros and small inline
264
+ functions (ten lines or less in length), then the use of the object
265
+ file is unrestricted, regardless of whether it is legally a derivative
266
+ work. (Executables containing this object code plus portions of the
267
+ Library will still fall under Section 6.)
268
+
269
+ Otherwise, if the work is a derivative of the Library, you may
270
+ distribute the object code for the work under the terms of Section 6.
271
+ Any executables containing that work also fall under Section 6,
272
+ whether or not they are linked directly with the Library itself.
273
+
274
+ 6. As an exception to the Sections above, you may also combine or
275
+ link a "work that uses the Library" with the Library to produce a
276
+ work containing portions of the Library, and distribute that work
277
+ under terms of your choice, provided that the terms permit
278
+ modification of the work for the customer's own use and reverse
279
+ engineering for debugging such modifications.
280
+
281
+ You must give prominent notice with each copy of the work that the
282
+ Library is used in it and that the Library and its use are covered by
283
+ this License. You must supply a copy of this License. If the work
284
+ during execution displays copyright notices, you must include the
285
+ copyright notice for the Library among them, as well as a reference
286
+ directing the user to the copy of this License. Also, you must do one
287
+ of these things:
288
+
289
+ a) Accompany the work with the complete corresponding
290
+ machine-readable source code for the Library including whatever
291
+ changes were used in the work (which must be distributed under
292
+ Sections 1 and 2 above); and, if the work is an executable linked
293
+ with the Library, with the complete machine-readable "work that
294
+ uses the Library", as object code and/or source code, so that the
295
+ user can modify the Library and then relink to produce a modified
296
+ executable containing the modified Library. (It is understood
297
+ that the user who changes the contents of definitions files in the
298
+ Library will not necessarily be able to recompile the application
299
+ to use the modified definitions.)
300
+
301
+ b) Use a suitable shared library mechanism for linking with the
302
+ Library. A suitable mechanism is one that (1) uses at run time a
303
+ copy of the library already present on the user's computer system,
304
+ rather than copying library functions into the executable, and (2)
305
+ will operate properly with a modified version of the library, if
306
+ the user installs one, as long as the modified version is
307
+ interface-compatible with the version that the work was made with.
308
+
309
+ c) Accompany the work with a written offer, valid for at
310
+ least three years, to give the same user the materials
311
+ specified in Subsection 6a, above, for a charge no more
312
+ than the cost of performing this distribution.
313
+
314
+ d) If distribution of the work is made by offering access to copy
315
+ from a designated place, offer equivalent access to copy the above
316
+ specified materials from the same place.
317
+
318
+ e) Verify that the user has already received a copy of these
319
+ materials or that you have already sent this user a copy.
320
+
321
+ For an executable, the required form of the "work that uses the
322
+ Library" must include any data and utility programs needed for
323
+ reproducing the executable from it. However, as a special exception,
324
+ the materials to be distributed need not include anything that is
325
+ normally distributed (in either source or binary form) with the major
326
+ components (compiler, kernel, and so on) of the operating system on
327
+ which the executable runs, unless that component itself accompanies
328
+ the executable.
329
+
330
+ It may happen that this requirement contradicts the license
331
+ restrictions of other proprietary libraries that do not normally
332
+ accompany the operating system. Such a contradiction means you cannot
333
+ use both them and the Library together in an executable that you
334
+ distribute.
335
+
336
+ 7. You may place library facilities that are a work based on the
337
+ Library side-by-side in a single library together with other library
338
+ facilities not covered by this License, and distribute such a combined
339
+ library, provided that the separate distribution of the work based on
340
+ the Library and of the other library facilities is otherwise
341
+ permitted, and provided that you do these two things:
342
+
343
+ a) Accompany the combined library with a copy of the same work
344
+ based on the Library, uncombined with any other library
345
+ facilities. This must be distributed under the terms of the
346
+ Sections above.
347
+
348
+ b) Give prominent notice with the combined library of the fact
349
+ that part of it is a work based on the Library, and explaining
350
+ where to find the accompanying uncombined form of the same work.
351
+
352
+ 8. You may not copy, modify, sublicense, link with, or distribute
353
+ the Library except as expressly provided under this License. Any
354
+ attempt otherwise to copy, modify, sublicense, link with, or
355
+ distribute the Library is void, and will automatically terminate your
356
+ rights under this License. However, parties who have received copies,
357
+ or rights, from you under this License will not have their licenses
358
+ terminated so long as such parties remain in full compliance.
359
+
360
+ 9. You are not required to accept this License, since you have not
361
+ signed it. However, nothing else grants you permission to modify or
362
+ distribute the Library or its derivative works. These actions are
363
+ prohibited by law if you do not accept this License. Therefore, by
364
+ modifying or distributing the Library (or any work based on the
365
+ Library), you indicate your acceptance of this License to do so, and
366
+ all its terms and conditions for copying, distributing or modifying
367
+ the Library or works based on it.
368
+
369
+ 10. Each time you redistribute the Library (or any work based on the
370
+ Library), the recipient automatically receives a license from the
371
+ original licensor to copy, distribute, link with or modify the Library
372
+ subject to these terms and conditions. You may not impose any further
373
+ restrictions on the recipients' exercise of the rights granted herein.
374
+ You are not responsible for enforcing compliance by third parties with
375
+ this License.
376
+
377
+ 11. If, as a consequence of a court judgment or allegation of patent
378
+ infringement or for any other reason (not limited to patent issues),
379
+ conditions are imposed on you (whether by court order, agreement or
380
+ otherwise) that contradict the conditions of this License, they do not
381
+ excuse you from the conditions of this License. If you cannot
382
+ distribute so as to satisfy simultaneously your obligations under this
383
+ License and any other pertinent obligations, then as a consequence you
384
+ may not distribute the Library at all. For example, if a patent
385
+ license would not permit royalty-free redistribution of the Library by
386
+ all those who receive copies directly or indirectly through you, then
387
+ the only way you could satisfy both it and this License would be to
388
+ refrain entirely from distribution of the Library.
389
+
390
+ If any portion of this section is held invalid or unenforceable under any
391
+ particular circumstance, the balance of the section is intended to apply,
392
+ and the section as a whole is intended to apply in other circumstances.
393
+
394
+ It is not the purpose of this section to induce you to infringe any
395
+ patents or other property right claims or to contest validity of any
396
+ such claims; this section has the sole purpose of protecting the
397
+ integrity of the free software distribution system which is
398
+ implemented by public license practices. Many people have made
399
+ generous contributions to the wide range of software distributed
400
+ through that system in reliance on consistent application of that
401
+ system; it is up to the author/donor to decide if he or she is willing
402
+ to distribute software through any other system and a licensee cannot
403
+ impose that choice.
404
+
405
+ This section is intended to make thoroughly clear what is believed to
406
+ be a consequence of the rest of this License.
407
+
408
+ 12. If the distribution and/or use of the Library is restricted in
409
+ certain countries either by patents or by copyrighted interfaces, the
410
+ original copyright holder who places the Library under this License may add
411
+ an explicit geographical distribution limitation excluding those countries,
412
+ so that distribution is permitted only in or among countries not thus
413
+ excluded. In such case, this License incorporates the limitation as if
414
+ written in the body of this License.
415
+
416
+ 13. The Free Software Foundation may publish revised and/or new
417
+ versions of the Lesser General Public License from time to time.
418
+ Such new versions will be similar in spirit to the present version,
419
+ but may differ in detail to address new problems or concerns.
420
+
421
+ Each version is given a distinguishing version number. If the Library
422
+ specifies a version number of this License which applies to it and
423
+ "any later version", you have the option of following the terms and
424
+ conditions either of that version or of any later version published by
425
+ the Free Software Foundation. If the Library does not specify a
426
+ license version number, you may choose any version ever published by
427
+ the Free Software Foundation.
428
+
429
+ 14. If you wish to incorporate parts of the Library into other free
430
+ programs whose distribution conditions are incompatible with these,
431
+ write to the author to ask for permission. For software which is
432
+ copyrighted by the Free Software Foundation, write to the Free
433
+ Software Foundation; we sometimes make exceptions for this. Our
434
+ decision will be guided by the two goals of preserving the free status
435
+ of all derivatives of our free software and of promoting the sharing
436
+ and reuse of software generally.
437
+
438
+ NO WARRANTY
439
+
440
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
441
+ WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
442
+ EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
443
+ OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
444
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
445
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
446
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
447
+ LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
448
+ THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
449
+
450
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
451
+ WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
452
+ AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
453
+ FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
454
+ CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
455
+ LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
456
+ RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
457
+ FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
458
+ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
459
+ DAMAGES.
460
+
461
+ END OF TERMS AND CONDITIONS
462
+
463
+ How to Apply These Terms to Your New Libraries
464
+
465
+ If you develop a new library, and you want it to be of the greatest
466
+ possible use to the public, we recommend making it free software that
467
+ everyone can redistribute and change. You can do so by permitting
468
+ redistribution under these terms (or, alternatively, under the terms of the
469
+ ordinary General Public License).
470
+
471
+ To apply these terms, attach the following notices to the library. It is
472
+ safest to attach them to the start of each source file to most effectively
473
+ convey the exclusion of warranty; and each file should have at least the
474
+ "copyright" line and a pointer to where the full notice is found.
475
+
476
+ <one line to give the library's name and a brief idea of what it does.>
477
+ Copyright (C) <year> <name of author>
478
+
479
+ This library is free software; you can redistribute it and/or
480
+ modify it under the terms of the GNU Lesser General Public
481
+ License as published by the Free Software Foundation; either
482
+ version 2.1 of the License, or (at your option) any later version.
483
+
484
+ This library is distributed in the hope that it will be useful,
485
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
486
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
487
+ Lesser General Public License for more details.
488
+
489
+ You should have received a copy of the GNU Lesser General Public
490
+ License along with this library; if not, write to the Free Software
491
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
492
+
493
+ Also add information on how to contact you by electronic and paper mail.
494
+
495
+ You should also get your employer (if you work as a programmer) or your
496
+ school, if any, to sign a "copyright disclaimer" for the library, if
497
+ necessary. Here is a sample; alter the names:
498
+
499
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
500
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
501
+
502
+ <signature of Ty Coon>, 1 April 1990
503
+ Ty Coon, President of Vice
504
+
505
+ That's all there is to it!
myenv/Lib/site-packages/adodbapi/process_connect_string.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ a clumsy attempt at a macro language to let the programmer execute code on the server (ex: determine 64bit)"""
2
+
3
+ from . import is64bit
4
+
5
+
6
+ def macro_call(macro_name, args, kwargs):
7
+ """allow the programmer to perform limited processing on the server by passing macro names and args
8
+
9
+ :new_key - the key name the macro will create
10
+ :args[0] - macro name
11
+ :args[1:] - any arguments
12
+ :code - the value of the keyword item
13
+ :kwargs - the connection keyword dictionary. ??key has been removed
14
+ --> the value to put in for kwargs['name'] = value
15
+ """
16
+ if isinstance(args, (str, str)):
17
+ args = [
18
+ args
19
+ ] # the user forgot to pass a sequence, so make a string into args[0]
20
+ new_key = args[0]
21
+ try:
22
+ if macro_name == "is64bit":
23
+ if is64bit.Python(): # if on 64 bit Python
24
+ return new_key, args[1] # return first argument
25
+ else:
26
+ try:
27
+ return new_key, args[2] # else return second argument (if defined)
28
+ except IndexError:
29
+ return new_key, "" # else return blank
30
+
31
+ elif (
32
+ macro_name == "getuser"
33
+ ): # get the name of the user the server is logged in under
34
+ if not new_key in kwargs:
35
+ import getpass
36
+
37
+ return new_key, getpass.getuser()
38
+
39
+ elif macro_name == "getnode": # get the name of the computer running the server
40
+ import platform
41
+
42
+ try:
43
+ return new_key, args[1] % platform.node()
44
+ except IndexError:
45
+ return new_key, platform.node()
46
+
47
+ elif macro_name == "getenv": # expand the server's environment variable args[1]
48
+ try:
49
+ dflt = args[2] # if not found, default from args[2]
50
+ except IndexError: # or blank
51
+ dflt = ""
52
+ return new_key, os.environ.get(args[1], dflt)
53
+
54
+ elif macro_name == "auto_security":
55
+ if (
56
+ not "user" in kwargs or not kwargs["user"]
57
+ ): # missing, blank, or Null username
58
+ return new_key, "Integrated Security=SSPI"
59
+ return new_key, "User ID=%(user)s; Password=%(password)s" % kwargs
60
+
61
+ elif (
62
+ macro_name == "find_temp_test_path"
63
+ ): # helper function for testing ado operation -- undocumented
64
+ import os
65
+ import tempfile
66
+
67
+ return new_key, os.path.join(
68
+ tempfile.gettempdir(), "adodbapi_test", args[1]
69
+ )
70
+
71
+ raise ValueError(f"Unknown connect string macro={macro_name}")
72
+ except:
73
+ raise ValueError(f"Error in macro processing {macro_name} {args!r}")
74
+
75
+
76
+ def process(
77
+ args, kwargs, expand_macros=False
78
+ ): # --> connection string with keyword arguments processed.
79
+ """attempts to inject arguments into a connection string using Python "%" operator for strings
80
+
81
+ co: adodbapi connection object
82
+ args: positional parameters from the .connect() call
83
+ kvargs: keyword arguments from the .connect() call
84
+ """
85
+ try:
86
+ dsn = args[0]
87
+ except IndexError:
88
+ dsn = None
89
+ if isinstance(
90
+ dsn, dict
91
+ ): # as a convenience the first argument may be django settings
92
+ kwargs.update(dsn)
93
+ elif (
94
+ dsn
95
+ ): # the connection string is passed to the connection as part of the keyword dictionary
96
+ kwargs["connection_string"] = dsn
97
+ try:
98
+ a1 = args[1]
99
+ except IndexError:
100
+ a1 = None
101
+ # historically, the second positional argument might be a timeout value
102
+ if isinstance(a1, int):
103
+ kwargs["timeout"] = a1
104
+ # if the second positional argument is a string, then it is user
105
+ elif isinstance(a1, str):
106
+ kwargs["user"] = a1
107
+ # if the second positional argument is a dictionary, use it as keyword arguments, too
108
+ elif isinstance(a1, dict):
109
+ kwargs.update(a1)
110
+ try:
111
+ kwargs["password"] = args[2] # the third positional argument is password
112
+ kwargs["host"] = args[3] # the fourth positional argument is host name
113
+ kwargs["database"] = args[4] # the fifth positional argument is database name
114
+ except IndexError:
115
+ pass
116
+
117
+ # make sure connection string is defined somehow
118
+ if not "connection_string" in kwargs:
119
+ try: # perhaps 'dsn' was defined
120
+ kwargs["connection_string"] = kwargs["dsn"]
121
+ except KeyError:
122
+ try: # as a last effort, use the "host" keyword
123
+ kwargs["connection_string"] = kwargs["host"]
124
+ except KeyError:
125
+ raise TypeError("Must define 'connection_string' for ado connections")
126
+ if expand_macros:
127
+ for kwarg in list(kwargs.keys()):
128
+ if kwarg.startswith("macro_"): # If a key defines a macro
129
+ macro_name = kwarg[6:] # name without the "macro_"
130
+ macro_code = kwargs.pop(
131
+ kwarg
132
+ ) # we remove the macro_key and get the code to execute
133
+ new_key, rslt = macro_call(
134
+ macro_name, macro_code, kwargs
135
+ ) # run the code in the local context
136
+ kwargs[new_key] = rslt # put the result back in the keywords dict
137
+ return kwargs
myenv/Lib/site-packages/adodbapi/readme.txt ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Project
2
+ -------
3
+ adodbapi
4
+
5
+ A Python DB-API 2.0 (PEP-249) module that makes it easy to use Microsoft ADO
6
+ for connecting with databases and other data sources using CPython.
7
+
8
+ Home page: <http://sourceforge.net/projects/adodbapi>
9
+
10
+ Features:
11
+ * 100% DB-API 2.0 (PEP-249) compliant (including most extensions and recommendations).
12
+ * Includes pyunit testcases that describe how to use the module.
13
+ * Fully implemented in Python. -- runs in current versions of Python 3
14
+ * Licensed under the LGPL license, which means that it can be used freely even in commercial programs subject to certain restrictions.
15
+ * The user can choose between paramstyles: 'qmark' 'named' 'format' 'pyformat' 'dynamic'
16
+ * Supports data retrieval by column name e.g.:
17
+ for row in myCurser.execute("select name,age from students"):
18
+ print("Student", row.name, "is", row.age, "years old.")
19
+ * Supports user-definable system-to-Python data conversion functions (selected by ADO data type, or by column)
20
+
21
+ Prerequisites:
22
+ * C Python 3.6 or higher
23
+ and pywin32 (Mark Hammond's python for windows extensions.)
24
+
25
+ Installation:
26
+ * (C-Python on Windows): Install pywin32 ("pip install pywin32") which includes adodbapi.
27
+ * (IronPython on Windows): Download adodbapi from http://sf.net/projects/adodbapi. Unpack the zip.
28
+
29
+ NOTE: ...........
30
+ If you do not like the new default operation of returning Numeric columns as decimal.Decimal,
31
+ you can select other options by the user defined conversion feature.
32
+ Try:
33
+ adodbapi.apibase.variantConversions[adodbapi.ado_consts.adNumeric] = adodbapi.apibase.cvtString
34
+ or:
35
+ adodbapi.apibase.variantConversions[adodbapi.ado_consts.adNumeric] = adodbapi.apibase.cvtFloat
36
+ or:
37
+ adodbapi.apibase.variantConversions[adodbapi.ado_consts.adNumeric] = write_your_own_convertion_function
38
+ ............
39
+ notes for 2.6.2:
40
+ The definitive source has been moved to https://github.com/mhammond/pywin32/tree/master/adodbapi.
41
+ Remote has proven too hard to configure and test with Pyro4. I am moving it to unsupported status
42
+ until I can change to a different connection method.
43
+ what's new in version 2.6
44
+ A cursor.prepare() method and support for prepared SQL statements.
45
+ Lots of refactoring, especially of the Remote and Server modules (still to be treated as Beta code).
46
+ The quick start document 'quick_reference.odt' will export as a nice-looking pdf.
47
+ Added paramstyles 'pyformat' and 'dynamic'. If your 'paramstyle' is 'named' you _must_ pass a dictionary of
48
+ parameters to your .execute() method. If your 'paramstyle' is 'format' 'pyformat' or 'dynamic', you _may_
49
+ pass a dictionary of parameters -- provided your SQL operation string is formatted correctly.
50
+
51
+ what's new in version 2.5
52
+ Remote module: (works on Linux!) allows a Windows computer to serve ADO databases via PyRO
53
+ Server module: PyRO server for ADO. Run using a command like= C:>python -m adodbapi.server
54
+ (server has simple connection string macros: is64bit, getuser, sql_provider, auto_security)
55
+ Brief documentation included. See adodbapi/examples folder adodbapi.rtf
56
+ New connection method conn.get_table_names() --> list of names of tables in database
57
+
58
+ Vastly refactored. Data conversion things have been moved to the new adodbapi.apibase module.
59
+ Many former module-level attributes are now class attributes. (Should be more thread-safe)
60
+ Connection objects are now context managers for transactions and will commit or rollback.
61
+ Cursor objects are context managers and will automatically close themselves.
62
+ Autocommit can be switched on and off.
63
+ Keyword and positional arguments on the connect() method work as documented in PEP 249.
64
+ Keyword arguments from the connect call can be formatted into the connection string.
65
+ New keyword arguments defined, such as: autocommit, paramstyle, remote_proxy, remote_port.
66
+ *** Breaking change: variantConversion lookups are simplified: the following will raise KeyError:
67
+ oldconverter=adodbapi.variantConversions[adodbapi.adoStringTypes]
68
+ Refactor as: oldconverter=adodbapi.variantConversions[adodbapi.adoStringTypes[0]]
69
+
70
+ License
71
+ -------
72
+ LGPL, see http://www.opensource.org/licenses/lgpl-license.php
73
+
74
+ Documentation
75
+ -------------
76
+
77
+ Look at adodbapi/quick_reference.md
78
+ http://www.python.org/topics/database/DatabaseAPI-2.0.html
79
+ read the examples in adodbapi/examples
80
+ and look at the test cases in adodbapi/test directory.
81
+
82
+ Mailing lists
83
+ -------------
84
+ The adodbapi mailing lists have been deactivated. Submit comments to the
85
+ pywin32 mailing lists.
86
+ -- the bug tracker on sourceforge.net/projects/adodbapi may be checked, (infrequently).
87
+ -- please use: https://github.com/mhammond/pywin32/issues
myenv/Lib/site-packages/adodbapi/schema_table.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """call using an open ADO connection --> list of table names"""
2
+
3
+ from . import adodbapi
4
+
5
+
6
+ def names(connection_object):
7
+ ado = connection_object.adoConn
8
+ schema = ado.OpenSchema(20) # constant = adSchemaTables
9
+
10
+ tables = []
11
+ while not schema.EOF:
12
+ name = adodbapi.getIndexedValue(schema.Fields, "TABLE_NAME").Value
13
+ tables.append(name)
14
+ schema.MoveNext()
15
+ del schema
16
+ return tables
myenv/Lib/site-packages/adodbapi/setup.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """adodbapi -- a pure Python PEP 249 DB-API package using Microsoft ADO
2
+
3
+ Adodbapi can be run on CPython 3.5 and later.
4
+ """
5
+
6
+ NAME = "adodbapi"
7
+ MAINTAINER = "Vernon Cole"
8
+ MAINTAINER_EMAIL = "vernondcole@gmail.com"
9
+ DESCRIPTION = (
10
+ """A pure Python package implementing PEP 249 DB-API using Microsoft ADO."""
11
+ )
12
+ URL = "http://sourceforge.net/projects/adodbapi"
13
+ LICENSE = "LGPL"
14
+ CLASSIFIERS = [
15
+ "Development Status :: 5 - Production/Stable",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
18
+ "Operating System :: Microsoft :: Windows",
19
+ "Operating System :: POSIX :: Linux",
20
+ "Programming Language :: Python",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: SQL",
23
+ "Topic :: Software Development",
24
+ "Topic :: Software Development :: Libraries :: Python Modules",
25
+ "Topic :: Database",
26
+ ]
27
+ AUTHOR = "Henrik Ekelund, Vernon Cole, et.al."
28
+ AUTHOR_EMAIL = "vernondcole@gmail.com"
29
+ PLATFORMS = ["Windows", "Linux"]
30
+
31
+ VERSION = None # in case searching for version fails
32
+ a = open("adodbapi.py") # find the version string in the source code
33
+ for line in a:
34
+ if "__version__" in line:
35
+ VERSION = line.split("'")[1]
36
+ print('adodbapi version="%s"' % VERSION)
37
+ break
38
+ a.close()
39
+
40
+
41
+ def setup_package():
42
+ from setuptools import setup
43
+ from setuptools.command.build_py import build_py
44
+
45
+ setup(
46
+ cmdclass={"build_py": build_py},
47
+ name=NAME,
48
+ maintainer=MAINTAINER,
49
+ maintainer_email=MAINTAINER_EMAIL,
50
+ description=DESCRIPTION,
51
+ url=URL,
52
+ keywords="database ado odbc dbapi db-api Microsoft SQL",
53
+ ## download_url=DOWNLOAD_URL,
54
+ long_description=open("README.txt").read(),
55
+ license=LICENSE,
56
+ classifiers=CLASSIFIERS,
57
+ author=AUTHOR,
58
+ author_email=AUTHOR_EMAIL,
59
+ platforms=PLATFORMS,
60
+ version=VERSION,
61
+ package_dir={"adodbapi": ""},
62
+ packages=["adodbapi"],
63
+ )
64
+ return
65
+
66
+
67
+ if __name__ == "__main__":
68
+ setup_package()
myenv/Lib/site-packages/adodbapi/test/__pycache__/adodbapitest.cpython-311.pyc ADDED
Binary file (82.8 kB). View file
 
myenv/Lib/site-packages/adodbapi/test/__pycache__/adodbapitestconfig.cpython-311.pyc ADDED
Binary file (5.93 kB). View file
 
myenv/Lib/site-packages/adodbapi/test/__pycache__/dbapi20.cpython-311.pyc ADDED
Binary file (43.8 kB). View file
 
myenv/Lib/site-packages/adodbapi/test/__pycache__/is64bit.cpython-311.pyc ADDED
Binary file (1.49 kB). View file
 
myenv/Lib/site-packages/adodbapi/test/__pycache__/setuptestframework.cpython-311.pyc ADDED
Binary file (4.81 kB). View file
 
myenv/Lib/site-packages/adodbapi/test/__pycache__/test_adodbapi_dbapi20.cpython-311.pyc ADDED
Binary file (8.7 kB). View file
 
myenv/Lib/site-packages/adodbapi/test/__pycache__/tryconnection.cpython-311.pyc ADDED
Binary file (1.64 kB). View file
 
myenv/Lib/site-packages/adodbapi/test/adodbapitest.py ADDED
@@ -0,0 +1,1599 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ Unit tests version 2.6.1.0 for adodbapi"""
2
+
3
+ """
4
+ adodbapi - A python DB API 2.0 interface to Microsoft ADO
5
+
6
+ Copyright (C) 2002 Henrik Ekelund
7
+
8
+ This library is free software; you can redistribute it and/or
9
+ modify it under the terms of the GNU Lesser General Public
10
+ License as published by the Free Software Foundation; either
11
+ version 2.1 of the License, or (at your option) any later version.
12
+
13
+ This library is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
+ Lesser General Public License for more details.
17
+
18
+ You should have received a copy of the GNU Lesser General Public
19
+ License along with this library; if not, write to the Free Software
20
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
+
22
+ Updates by Vernon Cole
23
+ """
24
+
25
+ import copy
26
+ import datetime
27
+ import decimal
28
+ import random
29
+ import string
30
+ import sys
31
+ import time
32
+ import unittest
33
+
34
+ import adodbapitestconfig as config # run the configuration module. # will set sys.path to find correct version of adodbapi
35
+ import tryconnection # in our code below, all our switches are from config.whatever
36
+ import win32com.client
37
+
38
+ import adodbapi
39
+ import adodbapi.apibase as api
40
+
41
+ try:
42
+ import adodbapi.ado_consts as ado_consts
43
+ except ImportError: # we are doing a shortcut import as a module -- so
44
+ try:
45
+ import ado_consts
46
+ except ImportError:
47
+ from adodbapi import ado_consts
48
+
49
+
50
+ def randomstring(length):
51
+ return "".join([random.choice(string.ascii_letters) for n in range(32)])
52
+
53
+
54
+ class CommonDBTests(unittest.TestCase):
55
+ "Self contained super-simple tests in easy syntax, should work on everything between mySQL and Oracle"
56
+
57
+ def setUp(self):
58
+ self.engine = "unknown"
59
+
60
+ def getEngine(self):
61
+ return self.engine
62
+
63
+ def getConnection(self):
64
+ raise NotImplementedError # "This method must be overriden by a subclass"
65
+
66
+ def getCursor(self):
67
+ return self.getConnection().cursor()
68
+
69
+ def testConnection(self):
70
+ crsr = self.getCursor()
71
+ assert crsr.__class__.__name__ == "Cursor"
72
+
73
+ def testErrorHandlerInherits(self):
74
+ conn = self.getConnection()
75
+ mycallable = lambda connection, cursor, errorclass, errorvalue: 1
76
+ conn.errorhandler = mycallable
77
+ crsr = conn.cursor()
78
+ assert (
79
+ crsr.errorhandler == mycallable
80
+ ), "Error handler on crsr should be same as on connection"
81
+
82
+ def testDefaultErrorHandlerConnection(self):
83
+ conn = self.getConnection()
84
+ del conn.messages[:]
85
+ try:
86
+ conn.close()
87
+ conn.commit() # Should not be able to use connection after it is closed
88
+ except:
89
+ assert len(conn.messages) == 1
90
+ assert len(conn.messages[0]) == 2
91
+ assert conn.messages[0][0] == api.ProgrammingError
92
+
93
+ def testOwnErrorHandlerConnection(self):
94
+ mycallable = (
95
+ lambda connection, cursor, errorclass, errorvalue: 1
96
+ ) # does not raise anything
97
+ conn = self.getConnection()
98
+ conn.errorhandler = mycallable
99
+ conn.close()
100
+ conn.commit() # Should not be able to use connection after it is closed
101
+ assert len(conn.messages) == 0
102
+
103
+ conn.errorhandler = None # This should bring back the standard error handler
104
+ try:
105
+ conn.close()
106
+ conn.commit() # Should not be able to use connection after it is closed
107
+ except:
108
+ pass
109
+ # The Standard errorhandler appends error to messages attribute
110
+ assert (
111
+ len(conn.messages) > 0
112
+ ), "Setting errorhandler to none should bring back the standard error handler"
113
+
114
+ def testDefaultErrorHandlerCursor(self):
115
+ crsr = self.getConnection().cursor()
116
+ del crsr.messages[:]
117
+ try:
118
+ crsr.execute("SELECT abbtytddrf FROM dasdasd")
119
+ except:
120
+ assert len(crsr.messages) == 1
121
+ assert len(crsr.messages[0]) == 2
122
+ assert crsr.messages[0][0] == api.DatabaseError
123
+
124
+ def testOwnErrorHandlerCursor(self):
125
+ mycallable = (
126
+ lambda connection, cursor, errorclass, errorvalue: 1
127
+ ) # does not raise anything
128
+ crsr = self.getConnection().cursor()
129
+ crsr.errorhandler = mycallable
130
+ crsr.execute("SELECT abbtytddrf FROM dasdasd")
131
+ assert len(crsr.messages) == 0
132
+
133
+ crsr.errorhandler = None # This should bring back the standard error handler
134
+ try:
135
+ crsr.execute("SELECT abbtytddrf FROM dasdasd")
136
+ except:
137
+ pass
138
+ # The Standard errorhandler appends error to messages attribute
139
+ assert (
140
+ len(crsr.messages) > 0
141
+ ), "Setting errorhandler to none should bring back the standard error handler"
142
+
143
+ def testUserDefinedConversions(self):
144
+ try:
145
+ duplicatingConverter = lambda aStringField: aStringField * 2
146
+ assert duplicatingConverter("gabba") == "gabbagabba"
147
+
148
+ self.helpForceDropOnTblTemp()
149
+ conn = self.getConnection()
150
+ # the variantConversions attribute should not exist on a normal connection object
151
+ self.assertRaises(AttributeError, lambda x: conn.variantConversions[x], [2])
152
+ # create a variantConversions attribute on the connection
153
+ conn.variantConversions = copy.copy(api.variantConversions)
154
+ crsr = conn.cursor()
155
+ tabdef = (
156
+ "CREATE TABLE xx_%s (fldData VARCHAR(100) NOT NULL, fld2 VARCHAR(20))"
157
+ % config.tmp
158
+ )
159
+ crsr.execute(tabdef)
160
+ crsr.execute(
161
+ "INSERT INTO xx_%s(fldData,fld2) VALUES('gabba','booga')" % config.tmp
162
+ )
163
+ crsr.execute(
164
+ "INSERT INTO xx_%s(fldData,fld2) VALUES('hey','yo')" % config.tmp
165
+ )
166
+ # change converter for ALL adoStringTypes columns
167
+ conn.variantConversions[api.adoStringTypes] = duplicatingConverter
168
+ crsr.execute("SELECT fldData,fld2 FROM xx_%s ORDER BY fldData" % config.tmp)
169
+
170
+ rows = crsr.fetchall()
171
+ row = rows[0]
172
+ self.assertEqual(row[0], "gabbagabba")
173
+ row = rows[1]
174
+ self.assertEqual(row[0], "heyhey")
175
+ self.assertEqual(row[1], "yoyo")
176
+
177
+ upcaseConverter = lambda aStringField: aStringField.upper()
178
+ assert upcaseConverter("upThis") == "UPTHIS"
179
+
180
+ # now use a single column converter
181
+ rows.converters[1] = upcaseConverter # convert second column
182
+ self.assertEqual(row[0], "heyhey") # first will be unchanged
183
+ self.assertEqual(row[1], "YO") # second will convert to upper case
184
+
185
+ finally:
186
+ try:
187
+ del conn.variantConversions # Restore the default
188
+ except:
189
+ pass
190
+ self.helpRollbackTblTemp()
191
+
192
+ def testUserDefinedConversionForExactNumericTypes(self):
193
+ # variantConversions is a dictionary of conversion functions
194
+ # held internally in adodbapi.apibase
195
+ #
196
+ # !!! this test intentionally alters the value of what should be constant in the module
197
+ # !!! no new code should use this example, to is only a test to see that the
198
+ # !!! deprecated way of doing this still works. (use connection.variantConversions)
199
+ #
200
+ if sys.version_info < (3, 0): ### Py3 need different test
201
+ oldconverter = adodbapi.variantConversions[
202
+ ado_consts.adNumeric
203
+ ] # keep old function to restore later
204
+ # By default decimal and "numbers" are returned as decimals.
205
+ # Instead, make numbers return as floats
206
+ try:
207
+ adodbapi.variantConversions[ado_consts.adNumeric] = adodbapi.cvtFloat
208
+ self.helpTestDataType(
209
+ "decimal(18,2)", "NUMBER", 3.45, compareAlmostEqual=1
210
+ )
211
+ self.helpTestDataType(
212
+ "numeric(18,2)", "NUMBER", 3.45, compareAlmostEqual=1
213
+ )
214
+ # now return strings
215
+ adodbapi.variantConversions[ado_consts.adNumeric] = adodbapi.cvtString
216
+ self.helpTestDataType("numeric(18,2)", "NUMBER", "3.45")
217
+ # now a completly weird user defined convertion
218
+ adodbapi.variantConversions[ado_consts.adNumeric] = (
219
+ lambda x: "!!This function returns a funny unicode string %s!!" % x
220
+ )
221
+ self.helpTestDataType(
222
+ "numeric(18,2)",
223
+ "NUMBER",
224
+ "3.45",
225
+ allowedReturnValues=[
226
+ "!!This function returns a funny unicode string 3.45!!"
227
+ ],
228
+ )
229
+ finally:
230
+ # now reset the converter to its original function
231
+ adodbapi.variantConversions[ado_consts.adNumeric] = (
232
+ oldconverter # Restore the original convertion function
233
+ )
234
+
235
+ def helpTestDataType(
236
+ self,
237
+ sqlDataTypeString,
238
+ DBAPIDataTypeString,
239
+ pyData,
240
+ pyDataInputAlternatives=None,
241
+ compareAlmostEqual=None,
242
+ allowedReturnValues=None,
243
+ ):
244
+ self.helpForceDropOnTblTemp()
245
+ conn = self.getConnection()
246
+ crsr = conn.cursor()
247
+ tabdef = (
248
+ """
249
+ CREATE TABLE xx_%s (
250
+ fldId integer NOT NULL,
251
+ fldData """
252
+ % config.tmp
253
+ + sqlDataTypeString
254
+ + ")\n"
255
+ )
256
+
257
+ crsr.execute(tabdef)
258
+
259
+ # Test Null values mapped to None
260
+ crsr.execute("INSERT INTO xx_%s (fldId) VALUES (1)" % config.tmp)
261
+
262
+ crsr.execute("SELECT fldId,fldData FROM xx_%s" % config.tmp)
263
+ rs = crsr.fetchone()
264
+ self.assertEqual(rs[1], None) # Null should be mapped to None
265
+ assert rs[0] == 1
266
+
267
+ # Test description related
268
+ descTuple = crsr.description[1]
269
+ assert descTuple[0] in ["fldData", "flddata"], 'was "%s" expected "%s"' % (
270
+ descTuple[0],
271
+ "fldData",
272
+ )
273
+
274
+ if DBAPIDataTypeString == "STRING":
275
+ assert descTuple[1] == api.STRING, 'was "%s" expected "%s"' % (
276
+ descTuple[1],
277
+ api.STRING.values,
278
+ )
279
+ elif DBAPIDataTypeString == "NUMBER":
280
+ assert descTuple[1] == api.NUMBER, 'was "%s" expected "%s"' % (
281
+ descTuple[1],
282
+ api.NUMBER.values,
283
+ )
284
+ elif DBAPIDataTypeString == "BINARY":
285
+ assert descTuple[1] == api.BINARY, 'was "%s" expected "%s"' % (
286
+ descTuple[1],
287
+ api.BINARY.values,
288
+ )
289
+ elif DBAPIDataTypeString == "DATETIME":
290
+ assert descTuple[1] == api.DATETIME, 'was "%s" expected "%s"' % (
291
+ descTuple[1],
292
+ api.DATETIME.values,
293
+ )
294
+ elif DBAPIDataTypeString == "ROWID":
295
+ assert descTuple[1] == api.ROWID, 'was "%s" expected "%s"' % (
296
+ descTuple[1],
297
+ api.ROWID.values,
298
+ )
299
+ elif DBAPIDataTypeString == "UUID":
300
+ assert descTuple[1] == api.OTHER, 'was "%s" expected "%s"' % (
301
+ descTuple[1],
302
+ api.OTHER.values,
303
+ )
304
+ else:
305
+ raise NotImplementedError # "DBAPIDataTypeString not provided"
306
+
307
+ # Test data binding
308
+ inputs = [pyData]
309
+ if pyDataInputAlternatives:
310
+ inputs.extend(pyDataInputAlternatives)
311
+ inputs = set(inputs) # removes redundant string==unicode tests
312
+ fldId = 1
313
+ for inParam in inputs:
314
+ fldId += 1
315
+ try:
316
+ crsr.execute(
317
+ "INSERT INTO xx_%s (fldId,fldData) VALUES (?,?)" % config.tmp,
318
+ (fldId, inParam),
319
+ )
320
+ except:
321
+ conn.printADOerrors()
322
+ raise
323
+ crsr.execute(
324
+ "SELECT fldData FROM xx_%s WHERE ?=fldID" % config.tmp, [fldId]
325
+ )
326
+ rs = crsr.fetchone()
327
+ if allowedReturnValues:
328
+ allowedTypes = tuple([type(aRV) for aRV in allowedReturnValues])
329
+ assert isinstance(
330
+ rs[0], allowedTypes
331
+ ), 'result type "%s" must be one of %s' % (type(rs[0]), allowedTypes)
332
+ else:
333
+ assert isinstance(
334
+ rs[0], type(pyData)
335
+ ), 'result type "%s" must be instance of %s' % (
336
+ type(rs[0]),
337
+ type(pyData),
338
+ )
339
+
340
+ if compareAlmostEqual and DBAPIDataTypeString == "DATETIME":
341
+ iso1 = adodbapi.dateconverter.DateObjectToIsoFormatString(rs[0])
342
+ iso2 = adodbapi.dateconverter.DateObjectToIsoFormatString(pyData)
343
+ self.assertEqual(iso1, iso2)
344
+ elif compareAlmostEqual:
345
+ s = float(pyData)
346
+ v = float(rs[0])
347
+ assert (
348
+ abs(v - s) / s < 0.00001
349
+ ), "Values not almost equal recvd=%s, expected=%f" % (rs[0], s)
350
+ else:
351
+ if allowedReturnValues:
352
+ ok = False
353
+ self.assertTrue(
354
+ rs[0] in allowedReturnValues,
355
+ f'Value "{rs[0]!r}" not in {allowedReturnValues}',
356
+ )
357
+ else:
358
+ self.assertEqual(
359
+ rs[0],
360
+ pyData,
361
+ 'Values are not equal recvd="%s", expected="%s"'
362
+ % (rs[0], pyData),
363
+ )
364
+
365
+ def testDataTypeFloat(self):
366
+ self.helpTestDataType("real", "NUMBER", 3.45, compareAlmostEqual=True)
367
+ self.helpTestDataType("float", "NUMBER", 1.79e37, compareAlmostEqual=True)
368
+
369
+ def testDataTypeDecmal(self):
370
+ self.helpTestDataType(
371
+ "decimal(18,2)",
372
+ "NUMBER",
373
+ 3.45,
374
+ allowedReturnValues=["3.45", "3,45", decimal.Decimal("3.45")],
375
+ )
376
+ self.helpTestDataType(
377
+ "numeric(18,2)",
378
+ "NUMBER",
379
+ 3.45,
380
+ allowedReturnValues=["3.45", "3,45", decimal.Decimal("3.45")],
381
+ )
382
+ self.helpTestDataType(
383
+ "decimal(20,2)",
384
+ "NUMBER",
385
+ 444444444444444444,
386
+ allowedReturnValues=[
387
+ "444444444444444444.00",
388
+ "444444444444444444,00",
389
+ decimal.Decimal("444444444444444444"),
390
+ ],
391
+ )
392
+ if self.getEngine() == "MSSQL":
393
+ self.helpTestDataType(
394
+ "uniqueidentifier",
395
+ "UUID",
396
+ "{71A4F49E-39F3-42B1-A41E-48FF154996E6}",
397
+ allowedReturnValues=["{71A4F49E-39F3-42B1-A41E-48FF154996E6}"],
398
+ )
399
+
400
+ def testDataTypeMoney(self): # v2.1 Cole -- use decimal for money
401
+ if self.getEngine() == "MySQL":
402
+ self.helpTestDataType(
403
+ "DECIMAL(20,4)", "NUMBER", decimal.Decimal("-922337203685477.5808")
404
+ )
405
+ elif self.getEngine() == "PostgreSQL":
406
+ self.helpTestDataType(
407
+ "money",
408
+ "NUMBER",
409
+ decimal.Decimal("-922337203685477.5808"),
410
+ compareAlmostEqual=True,
411
+ allowedReturnValues=[
412
+ -922337203685477.5808,
413
+ decimal.Decimal("-922337203685477.5808"),
414
+ ],
415
+ )
416
+ else:
417
+ self.helpTestDataType("smallmoney", "NUMBER", decimal.Decimal("214748.02"))
418
+ self.helpTestDataType(
419
+ "money", "NUMBER", decimal.Decimal("-922337203685477.5808")
420
+ )
421
+
422
+ def testDataTypeInt(self):
423
+ if self.getEngine() != "PostgreSQL":
424
+ self.helpTestDataType("tinyint", "NUMBER", 115)
425
+ self.helpTestDataType("smallint", "NUMBER", -32768)
426
+ if self.getEngine() not in ["ACCESS", "PostgreSQL"]:
427
+ self.helpTestDataType(
428
+ "bit", "NUMBER", 1
429
+ ) # Does not work correctly with access
430
+ if self.getEngine() in ["MSSQL", "PostgreSQL"]:
431
+ self.helpTestDataType(
432
+ "bigint",
433
+ "NUMBER",
434
+ 3000000000,
435
+ allowedReturnValues=[3000000000, int(3000000000)],
436
+ )
437
+ self.helpTestDataType("int", "NUMBER", 2147483647)
438
+
439
+ def testDataTypeChar(self):
440
+ for sqlDataType in ("char(6)", "nchar(6)"):
441
+ self.helpTestDataType(
442
+ sqlDataType,
443
+ "STRING",
444
+ "spam ",
445
+ allowedReturnValues=["spam", "spam", "spam ", "spam "],
446
+ )
447
+
448
+ def testDataTypeVarChar(self):
449
+ if self.getEngine() == "MySQL":
450
+ stringKinds = ["varchar(10)", "text"]
451
+ elif self.getEngine() == "PostgreSQL":
452
+ stringKinds = ["varchar(10)", "text", "character varying"]
453
+ else:
454
+ stringKinds = [
455
+ "varchar(10)",
456
+ "nvarchar(10)",
457
+ "text",
458
+ "ntext",
459
+ ] # ,"varchar(max)"]
460
+
461
+ for sqlDataType in stringKinds:
462
+ self.helpTestDataType(sqlDataType, "STRING", "spam", ["spam"])
463
+
464
+ def testDataTypeDate(self):
465
+ if self.getEngine() == "PostgreSQL":
466
+ dt = "timestamp"
467
+ else:
468
+ dt = "datetime"
469
+ self.helpTestDataType(
470
+ dt, "DATETIME", adodbapi.Date(2002, 10, 28), compareAlmostEqual=True
471
+ )
472
+ if self.getEngine() not in ["MySQL", "PostgreSQL"]:
473
+ self.helpTestDataType(
474
+ "smalldatetime",
475
+ "DATETIME",
476
+ adodbapi.Date(2002, 10, 28),
477
+ compareAlmostEqual=True,
478
+ )
479
+ if tag != "pythontime" and self.getEngine() not in [
480
+ "MySQL",
481
+ "PostgreSQL",
482
+ ]: # fails when using pythonTime
483
+ self.helpTestDataType(
484
+ dt,
485
+ "DATETIME",
486
+ adodbapi.Timestamp(2002, 10, 28, 12, 15, 1),
487
+ compareAlmostEqual=True,
488
+ )
489
+
490
+ def testDataTypeBinary(self):
491
+ binfld = b"\x07\x00\xE2\x40*"
492
+ arv = [binfld, adodbapi.Binary(binfld), bytes(binfld)]
493
+ if self.getEngine() == "PostgreSQL":
494
+ self.helpTestDataType(
495
+ "bytea", "BINARY", adodbapi.Binary(binfld), allowedReturnValues=arv
496
+ )
497
+ else:
498
+ self.helpTestDataType(
499
+ "binary(5)", "BINARY", adodbapi.Binary(binfld), allowedReturnValues=arv
500
+ )
501
+ self.helpTestDataType(
502
+ "varbinary(100)",
503
+ "BINARY",
504
+ adodbapi.Binary(binfld),
505
+ allowedReturnValues=arv,
506
+ )
507
+ if self.getEngine() != "MySQL":
508
+ self.helpTestDataType(
509
+ "image", "BINARY", adodbapi.Binary(binfld), allowedReturnValues=arv
510
+ )
511
+
512
+ def helpRollbackTblTemp(self):
513
+ self.helpForceDropOnTblTemp()
514
+
515
+ def helpForceDropOnTblTemp(self):
516
+ conn = self.getConnection()
517
+ with conn.cursor() as crsr:
518
+ try:
519
+ crsr.execute("DROP TABLE xx_%s" % config.tmp)
520
+ if not conn.autocommit:
521
+ conn.commit()
522
+ except:
523
+ pass
524
+
525
+ def helpCreateAndPopulateTableTemp(self, crsr):
526
+ tabdef = (
527
+ """
528
+ CREATE TABLE xx_%s (
529
+ fldData INTEGER
530
+ )
531
+ """
532
+ % config.tmp
533
+ )
534
+ try: # EAFP
535
+ crsr.execute(tabdef)
536
+ except api.DatabaseError: # was not dropped before
537
+ self.helpForceDropOnTblTemp() # so drop it now
538
+ crsr.execute(tabdef)
539
+ for i in range(9): # note: this poor SQL code, but a valid test
540
+ crsr.execute("INSERT INTO xx_%s (fldData) VALUES (%i)" % (config.tmp, i))
541
+ # NOTE: building the test table without using parameter substitution
542
+
543
+ def testFetchAll(self):
544
+ crsr = self.getCursor()
545
+ self.helpCreateAndPopulateTableTemp(crsr)
546
+ crsr.execute("SELECT fldData FROM xx_%s" % config.tmp)
547
+ rs = crsr.fetchall()
548
+ assert len(rs) == 9
549
+ # test slice of rows
550
+ i = 3
551
+ for row in rs[3:-2]: # should have rowid 3..6
552
+ assert row[0] == i
553
+ i += 1
554
+ self.helpRollbackTblTemp()
555
+
556
+ def testPreparedStatement(self):
557
+ crsr = self.getCursor()
558
+ self.helpCreateAndPopulateTableTemp(crsr)
559
+ crsr.prepare("SELECT fldData FROM xx_%s" % config.tmp)
560
+ crsr.execute(crsr.command) # remember the one that was prepared
561
+ rs = crsr.fetchall()
562
+ assert len(rs) == 9
563
+ assert rs[2][0] == 2
564
+ self.helpRollbackTblTemp()
565
+
566
+ def testWrongPreparedStatement(self):
567
+ crsr = self.getCursor()
568
+ self.helpCreateAndPopulateTableTemp(crsr)
569
+ crsr.prepare("SELECT * FROM nowhere")
570
+ crsr.execute(
571
+ "SELECT fldData FROM xx_%s" % config.tmp
572
+ ) # should execute this one, not the prepared one
573
+ rs = crsr.fetchall()
574
+ assert len(rs) == 9
575
+ assert rs[2][0] == 2
576
+ self.helpRollbackTblTemp()
577
+
578
+ def testIterator(self):
579
+ crsr = self.getCursor()
580
+ self.helpCreateAndPopulateTableTemp(crsr)
581
+ crsr.execute("SELECT fldData FROM xx_%s" % config.tmp)
582
+ for i, row in enumerate(
583
+ crsr
584
+ ): # using cursor as an iterator, rather than fetchxxx
585
+ assert row[0] == i
586
+ self.helpRollbackTblTemp()
587
+
588
+ def testExecuteMany(self):
589
+ crsr = self.getCursor()
590
+ self.helpCreateAndPopulateTableTemp(crsr)
591
+ seq_of_values = [(111,), (222,)]
592
+ crsr.executemany(
593
+ "INSERT INTO xx_%s (fldData) VALUES (?)" % config.tmp, seq_of_values
594
+ )
595
+ if crsr.rowcount == -1:
596
+ print(
597
+ self.getEngine()
598
+ + " Provider does not support rowcount (on .executemany())"
599
+ )
600
+ else:
601
+ self.assertEqual(crsr.rowcount, 2)
602
+ crsr.execute("SELECT fldData FROM xx_%s" % config.tmp)
603
+ rs = crsr.fetchall()
604
+ assert len(rs) == 11
605
+ self.helpRollbackTblTemp()
606
+
607
+ def testRowCount(self):
608
+ crsr = self.getCursor()
609
+ self.helpCreateAndPopulateTableTemp(crsr)
610
+ crsr.execute("SELECT fldData FROM xx_%s" % config.tmp)
611
+ if crsr.rowcount == -1:
612
+ # print("provider does not support rowcount on select")
613
+ pass
614
+ else:
615
+ self.assertEqual(crsr.rowcount, 9)
616
+ self.helpRollbackTblTemp()
617
+
618
+ def testRowCountNoRecordset(self):
619
+ crsr = self.getCursor()
620
+ self.helpCreateAndPopulateTableTemp(crsr)
621
+ crsr.execute("DELETE FROM xx_%s WHERE fldData >= 5" % config.tmp)
622
+ if crsr.rowcount == -1:
623
+ print(self.getEngine() + " Provider does not support rowcount (on DELETE)")
624
+ else:
625
+ self.assertEqual(crsr.rowcount, 4)
626
+ self.helpRollbackTblTemp()
627
+
628
+ def testFetchMany(self):
629
+ crsr = self.getCursor()
630
+ self.helpCreateAndPopulateTableTemp(crsr)
631
+ crsr.execute("SELECT fldData FROM xx_%s" % config.tmp)
632
+ rs = crsr.fetchmany(3)
633
+ assert len(rs) == 3
634
+ rs = crsr.fetchmany(5)
635
+ assert len(rs) == 5
636
+ rs = crsr.fetchmany(5)
637
+ assert len(rs) == 1 # Asked for five, but there is only one left
638
+ self.helpRollbackTblTemp()
639
+
640
+ def testFetchManyWithArraySize(self):
641
+ crsr = self.getCursor()
642
+ self.helpCreateAndPopulateTableTemp(crsr)
643
+ crsr.execute("SELECT fldData FROM xx_%s" % config.tmp)
644
+ rs = crsr.fetchmany()
645
+ assert len(rs) == 1 # arraysize Defaults to one
646
+ crsr.arraysize = 4
647
+ rs = crsr.fetchmany()
648
+ assert len(rs) == 4
649
+ rs = crsr.fetchmany()
650
+ assert len(rs) == 4
651
+ rs = crsr.fetchmany()
652
+ assert len(rs) == 0
653
+ self.helpRollbackTblTemp()
654
+
655
+ def testErrorConnect(self):
656
+ conn = self.getConnection()
657
+ conn.close()
658
+ self.assertRaises(api.DatabaseError, self.db, "not a valid connect string", {})
659
+
660
+ def testRowIterator(self):
661
+ self.helpForceDropOnTblTemp()
662
+ conn = self.getConnection()
663
+ crsr = conn.cursor()
664
+ tabdef = (
665
+ """
666
+ CREATE TABLE xx_%s (
667
+ fldId integer NOT NULL,
668
+ fldTwo integer,
669
+ fldThree integer,
670
+ fldFour integer)
671
+ """
672
+ % config.tmp
673
+ )
674
+ crsr.execute(tabdef)
675
+
676
+ inputs = [(2, 3, 4), (102, 103, 104)]
677
+ fldId = 1
678
+ for inParam in inputs:
679
+ fldId += 1
680
+ try:
681
+ crsr.execute(
682
+ "INSERT INTO xx_%s (fldId,fldTwo,fldThree,fldFour) VALUES (?,?,?,?)"
683
+ % config.tmp,
684
+ (fldId, inParam[0], inParam[1], inParam[2]),
685
+ )
686
+ except:
687
+ conn.printADOerrors()
688
+ raise
689
+ crsr.execute(
690
+ "SELECT fldTwo,fldThree,fldFour FROM xx_%s WHERE ?=fldID" % config.tmp,
691
+ [fldId],
692
+ )
693
+ rec = crsr.fetchone()
694
+ # check that stepping through an emulated row works
695
+ for j in range(len(inParam)):
696
+ assert (
697
+ rec[j] == inParam[j]
698
+ ), 'returned value:"%s" != test value:"%s"' % (rec[j], inParam[j])
699
+ # check that we can get a complete tuple from a row
700
+ assert (
701
+ tuple(rec) == inParam
702
+ ), f'returned value:"{rec!r}" != test value:"{inParam!r}"'
703
+ # test that slices of rows work
704
+ slice1 = tuple(rec[:-1])
705
+ slice2 = tuple(inParam[0:2])
706
+ assert (
707
+ slice1 == slice2
708
+ ), f'returned value:"{slice1!r}" != test value:"{slice2!r}"'
709
+ # now test named column retrieval
710
+ assert rec["fldTwo"] == inParam[0]
711
+ assert rec.fldThree == inParam[1]
712
+ assert rec.fldFour == inParam[2]
713
+ # test array operation
714
+ # note that the fields vv vv vv are out of order
715
+ crsr.execute("select fldThree,fldFour,fldTwo from xx_%s" % config.tmp)
716
+ recs = crsr.fetchall()
717
+ assert recs[1][0] == 103
718
+ assert recs[0][1] == 4
719
+ assert recs[1]["fldFour"] == 104
720
+ assert recs[0, 0] == 3
721
+ assert recs[0, "fldTwo"] == 2
722
+ assert recs[1, 2] == 102
723
+ for i in range(1):
724
+ for j in range(2):
725
+ assert recs[i][j] == recs[i, j]
726
+
727
+ def testFormatParamstyle(self):
728
+ self.helpForceDropOnTblTemp()
729
+ conn = self.getConnection()
730
+ conn.paramstyle = "format" # test nonstandard use of paramstyle
731
+ crsr = conn.cursor()
732
+ tabdef = (
733
+ """
734
+ CREATE TABLE xx_%s (
735
+ fldId integer NOT NULL,
736
+ fldData varchar(10),
737
+ fldConst varchar(30))
738
+ """
739
+ % config.tmp
740
+ )
741
+ crsr.execute(tabdef)
742
+
743
+ inputs = ["one", "two", "three"]
744
+ fldId = 2
745
+ for inParam in inputs:
746
+ fldId += 1
747
+ sql = (
748
+ "INSERT INTO xx_"
749
+ + config.tmp
750
+ + " (fldId,fldConst,fldData) VALUES (%s,'thi%s :may cause? trouble', %s)"
751
+ )
752
+ try:
753
+ crsr.execute(sql, (fldId, inParam))
754
+ except:
755
+ conn.printADOerrors()
756
+ raise
757
+ crsr.execute(
758
+ "SELECT fldData, fldConst FROM xx_" + config.tmp + " WHERE %s=fldID",
759
+ [fldId],
760
+ )
761
+ rec = crsr.fetchone()
762
+ self.assertEqual(
763
+ rec[0],
764
+ inParam,
765
+ 'returned value:"%s" != test value:"%s"' % (rec[0], inParam),
766
+ )
767
+ self.assertEqual(rec[1], "thi%s :may cause? trouble")
768
+
769
+ # now try an operation with a "%s" as part of a literal
770
+ sel = (
771
+ "insert into xx_" + config.tmp + " (fldId,fldData) VALUES (%s,'four%sfive')"
772
+ )
773
+ params = (20,)
774
+ crsr.execute(sel, params)
775
+
776
+ # test the .query implementation
777
+ assert "(?," in crsr.query, 'expected:"%s" in "%s"' % ("(?,", crsr.query)
778
+ # test the .command attribute
779
+ assert crsr.command == sel, 'expected:"%s" but found "%s"' % (sel, crsr.command)
780
+
781
+ # test the .parameters attribute
782
+ self.assertEqual(crsr.parameters, params)
783
+ # now make sure the data made it
784
+ crsr.execute("SELECT fldData FROM xx_%s WHERE fldID=20" % config.tmp)
785
+ rec = crsr.fetchone()
786
+ self.assertEqual(rec[0], "four%sfive")
787
+
788
+ def testNamedParamstyle(self):
789
+ self.helpForceDropOnTblTemp()
790
+ conn = self.getConnection()
791
+ crsr = conn.cursor()
792
+ crsr.paramstyle = "named" # test nonstandard use of paramstyle
793
+ tabdef = (
794
+ """
795
+ CREATE TABLE xx_%s (
796
+ fldId integer NOT NULL,
797
+ fldData varchar(10))
798
+ """
799
+ % config.tmp
800
+ )
801
+ crsr.execute(tabdef)
802
+
803
+ inputs = ["four", "five", "six"]
804
+ fldId = 10
805
+ for inParam in inputs:
806
+ fldId += 1
807
+ try:
808
+ crsr.execute(
809
+ "INSERT INTO xx_%s (fldId,fldData) VALUES (:Id,:f_Val)"
810
+ % config.tmp,
811
+ {"f_Val": inParam, "Id": fldId},
812
+ )
813
+ except:
814
+ conn.printADOerrors()
815
+ raise
816
+ crsr.execute(
817
+ "SELECT fldData FROM xx_%s WHERE fldID=:Id" % config.tmp, {"Id": fldId}
818
+ )
819
+ rec = crsr.fetchone()
820
+ self.assertEqual(
821
+ rec[0],
822
+ inParam,
823
+ 'returned value:"%s" != test value:"%s"' % (rec[0], inParam),
824
+ )
825
+ # now a test with a ":" as part of a literal
826
+ crsr.execute(
827
+ "insert into xx_%s (fldId,fldData) VALUES (:xyz,'six:five')" % config.tmp,
828
+ {"xyz": 30},
829
+ )
830
+ crsr.execute("SELECT fldData FROM xx_%s WHERE fldID=30" % config.tmp)
831
+ rec = crsr.fetchone()
832
+ self.assertEqual(rec[0], "six:five")
833
+
834
+ def testPyformatParamstyle(self):
835
+ self.helpForceDropOnTblTemp()
836
+ conn = self.getConnection()
837
+ crsr = conn.cursor()
838
+ crsr.paramstyle = "pyformat" # test nonstandard use of paramstyle
839
+ tabdef = (
840
+ """
841
+ CREATE TABLE xx_%s (
842
+ fldId integer NOT NULL,
843
+ fldData varchar(10))
844
+ """
845
+ % config.tmp
846
+ )
847
+ crsr.execute(tabdef)
848
+
849
+ inputs = ["four", "five", "six"]
850
+ fldId = 10
851
+ for inParam in inputs:
852
+ fldId += 1
853
+ try:
854
+ crsr.execute(
855
+ "INSERT INTO xx_%s (fldId,fldData) VALUES (%%(Id)s,%%(f_Val)s)"
856
+ % config.tmp,
857
+ {"f_Val": inParam, "Id": fldId},
858
+ )
859
+ except:
860
+ conn.printADOerrors()
861
+ raise
862
+ crsr.execute(
863
+ "SELECT fldData FROM xx_%s WHERE fldID=%%(Id)s" % config.tmp,
864
+ {"Id": fldId},
865
+ )
866
+ rec = crsr.fetchone()
867
+ self.assertEqual(
868
+ rec[0],
869
+ inParam,
870
+ 'returned value:"%s" != test value:"%s"' % (rec[0], inParam),
871
+ )
872
+ # now a test with a "%" as part of a literal
873
+ crsr.execute(
874
+ "insert into xx_%s (fldId,fldData) VALUES (%%(xyz)s,'six%%five')"
875
+ % config.tmp,
876
+ {"xyz": 30},
877
+ )
878
+ crsr.execute("SELECT fldData FROM xx_%s WHERE fldID=30" % config.tmp)
879
+ rec = crsr.fetchone()
880
+ self.assertEqual(rec[0], "six%five")
881
+
882
+ def testAutomaticParamstyle(self):
883
+ self.helpForceDropOnTblTemp()
884
+ conn = self.getConnection()
885
+ conn.paramstyle = "dynamic" # test nonstandard use of paramstyle
886
+ crsr = conn.cursor()
887
+ tabdef = (
888
+ """
889
+ CREATE TABLE xx_%s (
890
+ fldId integer NOT NULL,
891
+ fldData varchar(10),
892
+ fldConst varchar(30))
893
+ """
894
+ % config.tmp
895
+ )
896
+ crsr.execute(tabdef)
897
+ inputs = ["one", "two", "three"]
898
+ fldId = 2
899
+ for inParam in inputs:
900
+ fldId += 1
901
+ try:
902
+ crsr.execute(
903
+ "INSERT INTO xx_"
904
+ + config.tmp
905
+ + " (fldId,fldConst,fldData) VALUES (?,'thi%s :may cause? troub:1e', ?)",
906
+ (fldId, inParam),
907
+ )
908
+ except:
909
+ conn.printADOerrors()
910
+ raise
911
+ trouble = "thi%s :may cause? troub:1e"
912
+ crsr.execute(
913
+ "SELECT fldData, fldConst FROM xx_" + config.tmp + " WHERE ?=fldID",
914
+ [fldId],
915
+ )
916
+ rec = crsr.fetchone()
917
+ self.assertEqual(
918
+ rec[0],
919
+ inParam,
920
+ 'returned value:"%s" != test value:"%s"' % (rec[0], inParam),
921
+ )
922
+ self.assertEqual(rec[1], trouble)
923
+ # inputs = [u'four',u'five',u'six']
924
+ fldId = 10
925
+ for inParam in inputs:
926
+ fldId += 1
927
+ try:
928
+ crsr.execute(
929
+ "INSERT INTO xx_%s (fldId,fldData) VALUES (:Id,:f_Val)"
930
+ % config.tmp,
931
+ {"f_Val": inParam, "Id": fldId},
932
+ )
933
+ except:
934
+ conn.printADOerrors()
935
+ raise
936
+ crsr.execute(
937
+ "SELECT fldData FROM xx_%s WHERE :Id=fldID" % config.tmp, {"Id": fldId}
938
+ )
939
+ rec = crsr.fetchone()
940
+ self.assertEqual(
941
+ rec[0],
942
+ inParam,
943
+ 'returned value:"%s" != test value:"%s"' % (rec[0], inParam),
944
+ )
945
+ # now a test with a ":" as part of a literal -- and use a prepared query
946
+ ppdcmd = (
947
+ "insert into xx_%s (fldId,fldData) VALUES (:xyz,'six:five')" % config.tmp
948
+ )
949
+ crsr.prepare(ppdcmd)
950
+ crsr.execute(ppdcmd, {"xyz": 30})
951
+ crsr.execute("SELECT fldData FROM xx_%s WHERE fldID=30" % config.tmp)
952
+ rec = crsr.fetchone()
953
+ self.assertEqual(rec[0], "six:five")
954
+
955
+ def testRollBack(self):
956
+ conn = self.getConnection()
957
+ crsr = conn.cursor()
958
+ assert not crsr.connection.autocommit, "Unexpected beginning condition"
959
+ self.helpCreateAndPopulateTableTemp(crsr)
960
+ crsr.connection.commit() # commit the first bunch
961
+
962
+ crsr.execute("INSERT INTO xx_%s (fldData) VALUES(100)" % config.tmp)
963
+
964
+ selectSql = "SELECT fldData FROM xx_%s WHERE fldData=100" % config.tmp
965
+ crsr.execute(selectSql)
966
+ rs = crsr.fetchall()
967
+ assert len(rs) == 1
968
+ self.conn.rollback()
969
+ crsr.execute(selectSql)
970
+ assert (
971
+ crsr.fetchone() is None
972
+ ), "cursor.fetchone should return None if a query retrieves no rows"
973
+ crsr.execute("SELECT fldData from xx_%s" % config.tmp)
974
+ rs = crsr.fetchall()
975
+ assert len(rs) == 9, "the original records should still be present"
976
+ self.helpRollbackTblTemp()
977
+
978
+ def testCommit(self):
979
+ try:
980
+ con2 = self.getAnotherConnection()
981
+ except NotImplementedError:
982
+ return # should be "SKIP" for ACCESS
983
+ assert not con2.autocommit, "default should be manual commit"
984
+ crsr = con2.cursor()
985
+ self.helpCreateAndPopulateTableTemp(crsr)
986
+
987
+ crsr.execute("INSERT INTO xx_%s (fldData) VALUES(100)" % config.tmp)
988
+ con2.commit()
989
+
990
+ selectSql = "SELECT fldData FROM xx_%s WHERE fldData=100" % config.tmp
991
+ crsr.execute(selectSql)
992
+ rs = crsr.fetchall()
993
+ assert len(rs) == 1
994
+ crsr.close()
995
+ con2.close()
996
+ conn = self.getConnection()
997
+ crsr = self.getCursor()
998
+ with conn.cursor() as crsr:
999
+ crsr.execute(selectSql)
1000
+ rs = crsr.fetchall()
1001
+ assert len(rs) == 1
1002
+ assert rs[0][0] == 100
1003
+ self.helpRollbackTblTemp()
1004
+
1005
+ def testAutoRollback(self):
1006
+ try:
1007
+ con2 = self.getAnotherConnection()
1008
+ except NotImplementedError:
1009
+ return # should be "SKIP" for ACCESS
1010
+ assert not con2.autocommit, "unexpected beginning condition"
1011
+ crsr = con2.cursor()
1012
+ self.helpCreateAndPopulateTableTemp(crsr)
1013
+ crsr.execute("INSERT INTO xx_%s (fldData) VALUES(100)" % config.tmp)
1014
+ selectSql = "SELECT fldData FROM xx_%s WHERE fldData=100" % config.tmp
1015
+ crsr.execute(selectSql)
1016
+ rs = crsr.fetchall()
1017
+ assert len(rs) == 1
1018
+ crsr.close()
1019
+ con2.close()
1020
+ crsr = self.getCursor()
1021
+ try:
1022
+ crsr.execute(
1023
+ selectSql
1024
+ ) # closing the connection should have forced rollback
1025
+ row = crsr.fetchone()
1026
+ except api.DatabaseError:
1027
+ row = None # if the entire table disappeared the rollback was perfect and the test passed
1028
+ assert (
1029
+ row is None
1030
+ ), f"cursor.fetchone should return None if a query retrieves no rows. Got {row!r}"
1031
+ self.helpRollbackTblTemp()
1032
+
1033
+ def testAutoCommit(self):
1034
+ try:
1035
+ ac_conn = self.getAnotherConnection({"autocommit": True})
1036
+ except NotImplementedError:
1037
+ return # should be "SKIP" for ACCESS
1038
+ crsr = ac_conn.cursor()
1039
+ self.helpCreateAndPopulateTableTemp(crsr)
1040
+ crsr.execute("INSERT INTO xx_%s (fldData) VALUES(100)" % config.tmp)
1041
+ crsr.close()
1042
+ with self.getCursor() as crsr:
1043
+ selectSql = "SELECT fldData from xx_%s" % config.tmp
1044
+ crsr.execute(
1045
+ selectSql
1046
+ ) # closing the connection should _not_ have forced rollback
1047
+ rs = crsr.fetchall()
1048
+ assert len(rs) == 10, "all records should still be present"
1049
+ ac_conn.close()
1050
+ self.helpRollbackTblTemp()
1051
+
1052
+ def testSwitchedAutoCommit(self):
1053
+ try:
1054
+ ac_conn = self.getAnotherConnection()
1055
+ except NotImplementedError:
1056
+ return # should be "SKIP" for ACCESS
1057
+ ac_conn.autocommit = True
1058
+ crsr = ac_conn.cursor()
1059
+ self.helpCreateAndPopulateTableTemp(crsr)
1060
+ crsr.execute("INSERT INTO xx_%s (fldData) VALUES(100)" % config.tmp)
1061
+ crsr.close()
1062
+ conn = self.getConnection()
1063
+ ac_conn.close()
1064
+ with self.getCursor() as crsr:
1065
+ selectSql = "SELECT fldData from xx_%s" % config.tmp
1066
+ crsr.execute(
1067
+ selectSql
1068
+ ) # closing the connection should _not_ have forced rollback
1069
+ rs = crsr.fetchall()
1070
+ assert len(rs) == 10, "all records should still be present"
1071
+ self.helpRollbackTblTemp()
1072
+
1073
+ def testExtendedTypeHandling(self):
1074
+ class XtendString(str):
1075
+ pass
1076
+
1077
+ class XtendInt(int):
1078
+ pass
1079
+
1080
+ class XtendFloat(float):
1081
+ pass
1082
+
1083
+ xs = XtendString(randomstring(30))
1084
+ xi = XtendInt(random.randint(-100, 500))
1085
+ xf = XtendFloat(random.random())
1086
+ self.helpForceDropOnTblTemp()
1087
+ conn = self.getConnection()
1088
+ crsr = conn.cursor()
1089
+ tabdef = (
1090
+ """
1091
+ CREATE TABLE xx_%s (
1092
+ s VARCHAR(40) NOT NULL,
1093
+ i INTEGER NOT NULL,
1094
+ f REAL NOT NULL)"""
1095
+ % config.tmp
1096
+ )
1097
+ crsr.execute(tabdef)
1098
+ crsr.execute(
1099
+ "INSERT INTO xx_%s (s, i, f) VALUES (?, ?, ?)" % config.tmp, (xs, xi, xf)
1100
+ )
1101
+ crsr.close()
1102
+ conn = self.getConnection()
1103
+ with self.getCursor() as crsr:
1104
+ selectSql = "SELECT s, i, f from xx_%s" % config.tmp
1105
+ crsr.execute(
1106
+ selectSql
1107
+ ) # closing the connection should _not_ have forced rollback
1108
+ row = crsr.fetchone()
1109
+ self.assertEqual(row.s, xs)
1110
+ self.assertEqual(row.i, xi)
1111
+ self.assertAlmostEqual(row.f, xf)
1112
+ self.helpRollbackTblTemp()
1113
+
1114
+
1115
+ class TestADOwithSQLServer(CommonDBTests):
1116
+ def setUp(self):
1117
+ self.conn = config.dbSqlServerconnect(
1118
+ *config.connStrSQLServer[0], **config.connStrSQLServer[1]
1119
+ )
1120
+ self.conn.timeout = 30 # turn timeout back up
1121
+ self.engine = "MSSQL"
1122
+ self.db = config.dbSqlServerconnect
1123
+
1124
+ def tearDown(self):
1125
+ try:
1126
+ self.conn.rollback()
1127
+ except:
1128
+ pass
1129
+ try:
1130
+ self.conn.close()
1131
+ except:
1132
+ pass
1133
+ self.conn = None
1134
+
1135
+ def getConnection(self):
1136
+ return self.conn
1137
+
1138
+ def getAnotherConnection(self, addkeys=None):
1139
+ keys = dict(config.connStrSQLServer[1])
1140
+ if addkeys:
1141
+ keys.update(addkeys)
1142
+ return config.dbSqlServerconnect(*config.connStrSQLServer[0], **keys)
1143
+
1144
+ def testVariableReturningStoredProcedure(self):
1145
+ crsr = self.conn.cursor()
1146
+ spdef = """
1147
+ CREATE PROCEDURE sp_DeleteMeOnlyForTesting
1148
+ @theInput varchar(50),
1149
+ @theOtherInput varchar(50),
1150
+ @theOutput varchar(100) OUTPUT
1151
+ AS
1152
+ SET @theOutput=@theInput+@theOtherInput
1153
+ """
1154
+ try:
1155
+ crsr.execute("DROP PROCEDURE sp_DeleteMeOnlyForTesting")
1156
+ self.conn.commit()
1157
+ except: # Make sure it is empty
1158
+ pass
1159
+ crsr.execute(spdef)
1160
+
1161
+ retvalues = crsr.callproc(
1162
+ "sp_DeleteMeOnlyForTesting", ("Dodsworth", "Anne", " ")
1163
+ )
1164
+ assert retvalues[0] == "Dodsworth", f'{retvalues[0]!r} is not "Dodsworth"'
1165
+ assert retvalues[1] == "Anne", f'{retvalues[1]!r} is not "Anne"'
1166
+ assert (
1167
+ retvalues[2] == "DodsworthAnne"
1168
+ ), f'{retvalues[2]!r} is not "DodsworthAnne"'
1169
+ self.conn.rollback()
1170
+
1171
+ def testMultipleSetReturn(self):
1172
+ crsr = self.getCursor()
1173
+ self.helpCreateAndPopulateTableTemp(crsr)
1174
+
1175
+ spdef = """
1176
+ CREATE PROCEDURE sp_DeleteMe_OnlyForTesting
1177
+ AS
1178
+ SELECT fldData FROM xx_%s ORDER BY fldData ASC
1179
+ SELECT fldData From xx_%s where fldData = -9999
1180
+ SELECT fldData FROM xx_%s ORDER BY fldData DESC
1181
+ """ % (
1182
+ config.tmp,
1183
+ config.tmp,
1184
+ config.tmp,
1185
+ )
1186
+ try:
1187
+ crsr.execute("DROP PROCEDURE sp_DeleteMe_OnlyForTesting")
1188
+ self.conn.commit()
1189
+ except: # Make sure it is empty
1190
+ pass
1191
+ crsr.execute(spdef)
1192
+
1193
+ retvalues = crsr.callproc("sp_DeleteMe_OnlyForTesting")
1194
+ row = crsr.fetchone()
1195
+ self.assertEqual(row[0], 0)
1196
+ assert crsr.nextset() == True, "Operation should succeed"
1197
+ assert not crsr.fetchall(), "Should be an empty second set"
1198
+ assert crsr.nextset() == True, "third set should be present"
1199
+ rowdesc = crsr.fetchall()
1200
+ self.assertEqual(rowdesc[0][0], 8)
1201
+ assert crsr.nextset() is None, "No more return sets, should return None"
1202
+
1203
+ self.helpRollbackTblTemp()
1204
+
1205
+ def testDatetimeProcedureParameter(self):
1206
+ crsr = self.conn.cursor()
1207
+ spdef = """
1208
+ CREATE PROCEDURE sp_DeleteMeOnlyForTesting
1209
+ @theInput DATETIME,
1210
+ @theOtherInput varchar(50),
1211
+ @theOutput varchar(100) OUTPUT
1212
+ AS
1213
+ SET @theOutput = CONVERT(CHARACTER(20), @theInput, 0) + @theOtherInput
1214
+ """
1215
+ try:
1216
+ crsr.execute("DROP PROCEDURE sp_DeleteMeOnlyForTesting")
1217
+ self.conn.commit()
1218
+ except: # Make sure it is empty
1219
+ pass
1220
+ crsr.execute(spdef)
1221
+
1222
+ result = crsr.callproc(
1223
+ "sp_DeleteMeOnlyForTesting",
1224
+ [adodbapi.Timestamp(2014, 12, 25, 0, 1, 0), "Beep", " " * 30],
1225
+ )
1226
+
1227
+ assert result[2] == "Dec 25 2014 12:01AM Beep", 'value was="%s"' % result[2]
1228
+ self.conn.rollback()
1229
+
1230
+ def testIncorrectStoredProcedureParameter(self):
1231
+ crsr = self.conn.cursor()
1232
+ spdef = """
1233
+ CREATE PROCEDURE sp_DeleteMeOnlyForTesting
1234
+ @theInput DATETIME,
1235
+ @theOtherInput varchar(50),
1236
+ @theOutput varchar(100) OUTPUT
1237
+ AS
1238
+ SET @theOutput = CONVERT(CHARACTER(20), @theInput) + @theOtherInput
1239
+ """
1240
+ try:
1241
+ crsr.execute("DROP PROCEDURE sp_DeleteMeOnlyForTesting")
1242
+ self.conn.commit()
1243
+ except: # Make sure it is empty
1244
+ pass
1245
+ crsr.execute(spdef)
1246
+
1247
+ # calling the sproc with a string for the first parameter where a DateTime is expected
1248
+ result = tryconnection.try_operation_with_expected_exception(
1249
+ (api.DataError, api.DatabaseError),
1250
+ crsr.callproc,
1251
+ ["sp_DeleteMeOnlyForTesting"],
1252
+ {"parameters": ["this is wrong", "Anne", "not Alice"]},
1253
+ )
1254
+ if result[0]: # the expected exception was raised
1255
+ assert "@theInput" in str(result[1]) or "DatabaseError" in str(
1256
+ result
1257
+ ), "Identifies the wrong erroneous parameter"
1258
+ else:
1259
+ assert result[0], result[1] # incorrect or no exception
1260
+ self.conn.rollback()
1261
+
1262
+
1263
+ class TestADOwithAccessDB(CommonDBTests):
1264
+ def setUp(self):
1265
+ self.conn = config.dbAccessconnect(
1266
+ *config.connStrAccess[0], **config.connStrAccess[1]
1267
+ )
1268
+ self.conn.timeout = 30 # turn timeout back up
1269
+ self.engine = "ACCESS"
1270
+ self.db = config.dbAccessconnect
1271
+
1272
+ def tearDown(self):
1273
+ try:
1274
+ self.conn.rollback()
1275
+ except:
1276
+ pass
1277
+ try:
1278
+ self.conn.close()
1279
+ except:
1280
+ pass
1281
+ self.conn = None
1282
+
1283
+ def getConnection(self):
1284
+ return self.conn
1285
+
1286
+ def getAnotherConnection(self, addkeys=None):
1287
+ raise NotImplementedError("Jet cannot use a second connection to the database")
1288
+
1289
+ def testOkConnect(self):
1290
+ c = self.db(*config.connStrAccess[0], **config.connStrAccess[1])
1291
+ assert c is not None
1292
+ c.close()
1293
+
1294
+
1295
+ class TestADOwithMySql(CommonDBTests):
1296
+ def setUp(self):
1297
+ self.conn = config.dbMySqlconnect(
1298
+ *config.connStrMySql[0], **config.connStrMySql[1]
1299
+ )
1300
+ self.conn.timeout = 30 # turn timeout back up
1301
+ self.engine = "MySQL"
1302
+ self.db = config.dbMySqlconnect
1303
+
1304
+ def tearDown(self):
1305
+ try:
1306
+ self.conn.rollback()
1307
+ except:
1308
+ pass
1309
+ try:
1310
+ self.conn.close()
1311
+ except:
1312
+ pass
1313
+ self.conn = None
1314
+
1315
+ def getConnection(self):
1316
+ return self.conn
1317
+
1318
+ def getAnotherConnection(self, addkeys=None):
1319
+ keys = dict(config.connStrMySql[1])
1320
+ if addkeys:
1321
+ keys.update(addkeys)
1322
+ return config.dbMySqlconnect(*config.connStrMySql[0], **keys)
1323
+
1324
+ def testOkConnect(self):
1325
+ c = self.db(*config.connStrMySql[0], **config.connStrMySql[1])
1326
+ assert c is not None
1327
+
1328
+ # def testStoredProcedure(self):
1329
+ # crsr = self.conn.cursor()
1330
+ # try:
1331
+ # crsr.execute("DROP PROCEDURE DeleteMeOnlyForTesting")
1332
+ # self.conn.commit()
1333
+ # except: # Make sure it is empty
1334
+ # pass
1335
+ # spdef = """
1336
+ # DELIMITER $$
1337
+ # CREATE PROCEDURE DeleteMeOnlyForTesting (onein CHAR(10), twoin CHAR(10), OUT theout CHAR(20))
1338
+ # DETERMINISTIC
1339
+ # BEGIN
1340
+ # SET theout = onein //|| twoin;
1341
+ # /* (SELECT 'a small string' as result; */
1342
+ # END $$
1343
+ # """
1344
+ # crsr.execute(spdef)
1345
+ # retvalues = crsr.callproc(
1346
+ # "DeleteMeOnlyForTesting", ("Dodsworth", "Anne", " ")
1347
+ # )
1348
+ # # print(f"return value (mysql)={crsr.returnValue!r}")
1349
+ # assert retvalues[0] == "Dodsworth", f'{retvalues[0]!r} is not "Dodsworth"'
1350
+ # assert retvalues[1] == "Anne", f'{retvalues[1]!r} is not "Anne"'
1351
+ # assert (
1352
+ # retvalues[2] == "DodsworthAnne"
1353
+ # ), f'{retvalues[2]!r} is not "DodsworthAnne"'
1354
+ # try:
1355
+ # crsr.execute("DROP PROCEDURE, DeleteMeOnlyForTesting")
1356
+ # self.conn.commit()
1357
+ # except: # Make sure it is empty
1358
+ # pass
1359
+
1360
+
1361
+ class TestADOwithPostgres(CommonDBTests):
1362
+ def setUp(self):
1363
+ self.conn = config.dbPostgresConnect(
1364
+ *config.connStrPostgres[0], **config.connStrPostgres[1]
1365
+ )
1366
+ self.conn.timeout = 30 # turn timeout back up
1367
+ self.engine = "PostgreSQL"
1368
+ self.db = config.dbPostgresConnect
1369
+
1370
+ def tearDown(self):
1371
+ try:
1372
+ self.conn.rollback()
1373
+ except:
1374
+ pass
1375
+ try:
1376
+ self.conn.close()
1377
+ except:
1378
+ pass
1379
+ self.conn = None
1380
+
1381
+ def getConnection(self):
1382
+ return self.conn
1383
+
1384
+ def getAnotherConnection(self, addkeys=None):
1385
+ keys = dict(config.connStrPostgres[1])
1386
+ if addkeys:
1387
+ keys.update(addkeys)
1388
+ return config.dbPostgresConnect(*config.connStrPostgres[0], **keys)
1389
+
1390
+ def testOkConnect(self):
1391
+ c = self.db(*config.connStrPostgres[0], **config.connStrPostgres[1])
1392
+ assert c is not None
1393
+
1394
+ # def testStoredProcedure(self):
1395
+ # crsr = self.conn.cursor()
1396
+ # spdef = """
1397
+ # CREATE OR REPLACE FUNCTION DeleteMeOnlyForTesting (text, text)
1398
+ # RETURNS text AS $funk$
1399
+ # BEGIN
1400
+ # RETURN $1 || $2;
1401
+ # END;
1402
+ # $funk$
1403
+ # LANGUAGE SQL;
1404
+ # """
1405
+
1406
+ # crsr.execute(spdef)
1407
+ # retvalues = crsr.callproc(
1408
+ # "DeleteMeOnlyForTesting", ("Dodsworth", "Anne", " ")
1409
+ # )
1410
+ # # print(f"return value (pg)={crsr.returnValue!r}")
1411
+ # assert retvalues[0] == "Dodsworth", f'{retvalues[0]!r} is not "Dodsworth"'
1412
+ # assert retvalues[1] == "Anne", f'{retvalues[1]!r} is not "Anne"'
1413
+ # assert (
1414
+ # retvalues[2] == "DodsworthAnne"
1415
+ # ), f'{retvalues[2]!r} is not "DodsworthAnne"'
1416
+ # self.conn.rollback()
1417
+ # try:
1418
+ # crsr.execute("DROP PROCEDURE, DeleteMeOnlyForTesting")
1419
+ # self.conn.commit()
1420
+ # except: # Make sure it is empty
1421
+ # pass
1422
+
1423
+
1424
+ class TimeConverterInterfaceTest(unittest.TestCase):
1425
+ def testIDate(self):
1426
+ assert self.tc.Date(1990, 2, 2)
1427
+
1428
+ def testITime(self):
1429
+ assert self.tc.Time(13, 2, 2)
1430
+
1431
+ def testITimestamp(self):
1432
+ assert self.tc.Timestamp(1990, 2, 2, 13, 2, 1)
1433
+
1434
+ def testIDateObjectFromCOMDate(self):
1435
+ assert self.tc.DateObjectFromCOMDate(37435.7604282)
1436
+
1437
+ def testICOMDate(self):
1438
+ assert hasattr(self.tc, "COMDate")
1439
+
1440
+ def testExactDate(self):
1441
+ d = self.tc.Date(1994, 11, 15)
1442
+ comDate = self.tc.COMDate(d)
1443
+ correct = 34653.0
1444
+ assert comDate == correct, comDate
1445
+
1446
+ def testExactTimestamp(self):
1447
+ d = self.tc.Timestamp(1994, 11, 15, 12, 0, 0)
1448
+ comDate = self.tc.COMDate(d)
1449
+ correct = 34653.5
1450
+ self.assertEqual(comDate, correct)
1451
+
1452
+ d = self.tc.Timestamp(2003, 5, 6, 14, 15, 17)
1453
+ comDate = self.tc.COMDate(d)
1454
+ correct = 37747.593946759262
1455
+ self.assertEqual(comDate, correct)
1456
+
1457
+ def testIsoFormat(self):
1458
+ d = self.tc.Timestamp(1994, 11, 15, 12, 3, 10)
1459
+ iso = self.tc.DateObjectToIsoFormatString(d)
1460
+ self.assertEqual(str(iso[:19]), "1994-11-15 12:03:10")
1461
+
1462
+ dt = self.tc.Date(2003, 5, 2)
1463
+ iso = self.tc.DateObjectToIsoFormatString(dt)
1464
+ self.assertEqual(str(iso[:10]), "2003-05-02")
1465
+
1466
+
1467
+ class TestPythonTimeConverter(TimeConverterInterfaceTest):
1468
+ def setUp(self):
1469
+ self.tc = api.pythonTimeConverter()
1470
+
1471
+ def testCOMDate(self):
1472
+ mk = time.mktime((2002, 6, 28, 18, 15, 1, 4, 31 + 28 + 31 + 30 + 31 + 28, -1))
1473
+ t = time.localtime(mk)
1474
+ # Fri, 28 Jun 2002 18:15:01 +0000
1475
+ cmd = self.tc.COMDate(t)
1476
+ assert abs(cmd - 37435.7604282) < 1.0 / 24, "%f more than an hour wrong" % cmd
1477
+
1478
+ def testDateObjectFromCOMDate(self):
1479
+ cmd = self.tc.DateObjectFromCOMDate(37435.7604282)
1480
+ t1 = time.gmtime(
1481
+ time.mktime((2002, 6, 28, 0, 14, 1, 4, 31 + 28 + 31 + 30 + 31 + 28, -1))
1482
+ )
1483
+ # there are errors in the implementation of gmtime which we ignore
1484
+ t2 = time.gmtime(
1485
+ time.mktime((2002, 6, 29, 12, 14, 2, 4, 31 + 28 + 31 + 30 + 31 + 28, -1))
1486
+ )
1487
+ assert t1 < cmd < t2, f'"{cmd}" should be about 2002-6-28 12:15:01'
1488
+
1489
+ def testDate(self):
1490
+ t1 = time.mktime((2002, 6, 28, 18, 15, 1, 4, 31 + 28 + 31 + 30 + 31 + 30, 0))
1491
+ t2 = time.mktime((2002, 6, 30, 18, 15, 1, 4, 31 + 28 + 31 + 30 + 31 + 28, 0))
1492
+ obj = self.tc.Date(2002, 6, 29)
1493
+ assert t1 < time.mktime(obj) < t2, obj
1494
+
1495
+ def testTime(self):
1496
+ self.assertEqual(
1497
+ self.tc.Time(18, 15, 2), time.gmtime(18 * 60 * 60 + 15 * 60 + 2)
1498
+ )
1499
+
1500
+ def testTimestamp(self):
1501
+ t1 = time.localtime(
1502
+ time.mktime((2002, 6, 28, 18, 14, 1, 4, 31 + 28 + 31 + 30 + 31 + 28, -1))
1503
+ )
1504
+ t2 = time.localtime(
1505
+ time.mktime((2002, 6, 28, 18, 16, 1, 4, 31 + 28 + 31 + 30 + 31 + 28, -1))
1506
+ )
1507
+ obj = self.tc.Timestamp(2002, 6, 28, 18, 15, 2)
1508
+ assert t1 < obj < t2, obj
1509
+
1510
+
1511
+ class TestPythonDateTimeConverter(TimeConverterInterfaceTest):
1512
+ def setUp(self):
1513
+ self.tc = api.pythonDateTimeConverter()
1514
+
1515
+ def testCOMDate(self):
1516
+ t = datetime.datetime(2002, 6, 28, 18, 15, 1)
1517
+ # Fri, 28 Jun 2002 18:15:01 +0000
1518
+ cmd = self.tc.COMDate(t)
1519
+ assert abs(cmd - 37435.7604282) < 1.0 / 24, "more than an hour wrong"
1520
+
1521
+ def testDateObjectFromCOMDate(self):
1522
+ cmd = self.tc.DateObjectFromCOMDate(37435.7604282)
1523
+ t1 = datetime.datetime(2002, 6, 28, 18, 14, 1)
1524
+ t2 = datetime.datetime(2002, 6, 28, 18, 16, 1)
1525
+ assert t1 < cmd < t2, cmd
1526
+
1527
+ tx = datetime.datetime(
1528
+ 2002, 6, 28, 18, 14, 1, 900000
1529
+ ) # testing that microseconds don't become milliseconds
1530
+ c1 = self.tc.DateObjectFromCOMDate(self.tc.COMDate(tx))
1531
+ assert t1 < c1 < t2, c1
1532
+
1533
+ def testDate(self):
1534
+ t1 = datetime.date(2002, 6, 28)
1535
+ t2 = datetime.date(2002, 6, 30)
1536
+ obj = self.tc.Date(2002, 6, 29)
1537
+ assert t1 < obj < t2, obj
1538
+
1539
+ def testTime(self):
1540
+ self.assertEqual(self.tc.Time(18, 15, 2).isoformat()[:8], "18:15:02")
1541
+
1542
+ def testTimestamp(self):
1543
+ t1 = datetime.datetime(2002, 6, 28, 18, 14, 1)
1544
+ t2 = datetime.datetime(2002, 6, 28, 18, 16, 1)
1545
+ obj = self.tc.Timestamp(2002, 6, 28, 18, 15, 2)
1546
+ assert t1 < obj < t2, obj
1547
+
1548
+
1549
+ suites = [
1550
+ unittest.defaultTestLoader.loadTestsFromModule(TestPythonDateTimeConverter, "test")
1551
+ ]
1552
+ if config.doTimeTest:
1553
+ suites.append(
1554
+ unittest.defaultTestLoader.loadTestsFromModule(TestPythonTimeConverter, "test")
1555
+ )
1556
+ if config.doAccessTest:
1557
+ suites.append(
1558
+ unittest.defaultTestLoader.loadTestsFromModule(TestADOwithAccessDB, "test")
1559
+ )
1560
+ if config.doSqlServerTest:
1561
+ suites.append(
1562
+ unittest.defaultTestLoader.loadTestsFromModule(TestADOwithSQLServer, "test")
1563
+ )
1564
+ if config.doMySqlTest:
1565
+ suites.append(
1566
+ unittest.defaultTestLoader.loadTestsFromModule(TestADOwithMySql, "test")
1567
+ )
1568
+ if config.doPostgresTest:
1569
+ suites.append(
1570
+ unittest.defaultTestLoader.loadTestsFromModule(TestADOwithPostgres, "test")
1571
+ )
1572
+
1573
+
1574
+ class cleanup_manager:
1575
+ def __enter__(self):
1576
+ pass
1577
+
1578
+ def __exit__(self, exc_type, exc_val, exc_tb):
1579
+ config.cleanup(config.testfolder, config.mdb_name)
1580
+
1581
+
1582
+ suite = unittest.TestSuite(suites)
1583
+ if __name__ == "__main__":
1584
+ mysuite = copy.deepcopy(suite)
1585
+ with cleanup_manager():
1586
+ defaultDateConverter = adodbapi.dateconverter
1587
+ print(__doc__)
1588
+ print("Default Date Converter is %s" % (defaultDateConverter,))
1589
+ dateconverter = defaultDateConverter
1590
+ unittest.TextTestRunner().run(mysuite)
1591
+
1592
+ if config.doTimeTest:
1593
+ mysuite = copy.deepcopy(
1594
+ suite
1595
+ ) # work around a side effect of unittest.TextTestRunner
1596
+ adodbapi.adodbapi.dateconverter = api.pythonTimeConverter()
1597
+ print("Changed dateconverter to ")
1598
+ print(adodbapi.adodbapi.dateconverter)
1599
+ unittest.TextTestRunner().run(mysuite)
myenv/Lib/site-packages/adodbapi/test/adodbapitestconfig.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Configure this to _YOUR_ environment in order to run the testcases.
2
+ "testADOdbapiConfig.py v 2.6.2.B00"
3
+
4
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
5
+ # #
6
+ # # TESTERS:
7
+ # #
8
+ # # You will need to make numerous modifications to this file
9
+ # # to adapt it to your own testing environment.
10
+ # #
11
+ # # Skip down to the next "# #" line --
12
+ # # -- the things you need to change are below it.
13
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
14
+ import platform
15
+ import random
16
+ import sys
17
+
18
+ import is64bit
19
+ import setuptestframework
20
+ import tryconnection
21
+
22
+ print("\nPython", sys.version)
23
+ node = platform.node()
24
+ try:
25
+ print(
26
+ "node=%s, is64bit.os()= %s, is64bit.Python()= %s"
27
+ % (node, is64bit.os(), is64bit.Python())
28
+ )
29
+ except:
30
+ pass
31
+
32
+ if "--help" in sys.argv:
33
+ print(
34
+ """Valid command-line switches are:
35
+ --package - create a temporary test package
36
+ --all - run all possible tests
37
+ --time - do time format test
38
+ --nojet - do not test against an ACCESS database file
39
+ --mssql - test against Microsoft SQL server
40
+ --pg - test against PostgreSQL
41
+ --mysql - test against MariaDB
42
+ """
43
+ )
44
+ exit()
45
+ try:
46
+ onWindows = bool(sys.getwindowsversion()) # seems to work on all versions of Python
47
+ except:
48
+ onWindows = False
49
+
50
+ # create a random name for temporary table names
51
+ _alphabet = (
52
+ "PYFGCRLAOEUIDHTNSQJKXBMWVZ" # why, yes, I do happen to use a dvorak keyboard
53
+ )
54
+ tmp = "".join([random.choice(_alphabet) for x in range(9)])
55
+ mdb_name = "xx_" + tmp + ".mdb" # generate a non-colliding name for the temporary .mdb
56
+ testfolder = setuptestframework.maketemp()
57
+
58
+ if "--package" in sys.argv:
59
+ # create a new adodbapi module
60
+ pth = setuptestframework.makeadopackage(testfolder)
61
+ else:
62
+ # use the adodbapi module in which this file appears
63
+ pth = setuptestframework.find_ado_path()
64
+ if pth not in sys.path:
65
+ # look here _first_ to find modules
66
+ sys.path.insert(1, pth)
67
+
68
+ # function to clean up the temporary folder -- calling program must run this function before exit.
69
+ cleanup = setuptestframework.getcleanupfunction()
70
+
71
+ import adodbapi # will (hopefully) be imported using the "pth" discovered above
72
+
73
+ print(adodbapi.version) # show version
74
+ print(__doc__)
75
+
76
+ verbose = False
77
+ for a in sys.argv:
78
+ if a.startswith("--verbose"):
79
+ arg = True
80
+ try:
81
+ arg = int(a.split("=")[1])
82
+ except IndexError:
83
+ pass
84
+ adodbapi.adodbapi.verbose = arg
85
+ verbose = arg
86
+
87
+ doAllTests = "--all" in sys.argv
88
+ doAccessTest = not ("--nojet" in sys.argv)
89
+ doSqlServerTest = "--mssql" in sys.argv or doAllTests
90
+ doMySqlTest = "--mysql" in sys.argv or doAllTests
91
+ doPostgresTest = "--pg" in sys.argv or doAllTests
92
+ doTimeTest = ("--time" in sys.argv or doAllTests) and onWindows
93
+
94
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
95
+ # # start your environment setup here v v v
96
+ SQL_HOST_NODE = "testsql.2txt.us,1430"
97
+
98
+ if doAccessTest:
99
+ c = {
100
+ "mdb": setuptestframework.makemdb(testfolder, mdb_name),
101
+ # macro definition for keyword "provider" using macro "is64bit" -- see documentation
102
+ # is64bit will return true for 64 bit versions of Python, so the macro will select the ACE provider
103
+ "macro_is64bit": [
104
+ "provider",
105
+ "Microsoft.ACE.OLEDB.12.0", # 64 bit provider
106
+ "Microsoft.Jet.OLEDB.4.0", # 32 bit provider
107
+ ],
108
+ }
109
+
110
+ # ;Mode=ReadWrite;Persist Security Info=False;Jet OLEDB:Bypass UserInfo Validation=True"
111
+ connStrAccess = "Provider=%(provider)s;Data Source=%(mdb)s"
112
+ print(" ...Testing ACCESS connection to {} file...".format(c["mdb"]))
113
+ doAccessTest, connStrAccess, dbAccessconnect = tryconnection.try_connection(
114
+ verbose, connStrAccess, 10, **c
115
+ )
116
+
117
+ if doSqlServerTest:
118
+ c = {
119
+ "host": SQL_HOST_NODE, # name of computer with SQL Server
120
+ "database": "adotest",
121
+ "user": "adotestuser", # None implies Windows security
122
+ "password": "Sq1234567",
123
+ # macro definition for keyword "security" using macro "auto_security"
124
+ "macro_auto_security": "security",
125
+ "provider": "MSOLEDBSQL; MARS Connection=True",
126
+ }
127
+ connStr = "Provider=%(provider)s; Initial Catalog=%(database)s; Data Source=%(host)s; %(security)s;"
128
+ print(" ...Testing MS-SQL login to {}...".format(c["host"]))
129
+ (
130
+ doSqlServerTest,
131
+ connStrSQLServer,
132
+ dbSqlServerconnect,
133
+ ) = tryconnection.try_connection(verbose, connStr, 30, **c)
134
+
135
+ if doMySqlTest:
136
+ c = {
137
+ "host": "testmysql.2txt.us",
138
+ "database": "adodbapitest",
139
+ "user": "adotest",
140
+ "password": "12345678",
141
+ "port": "3330", # note the nonstandard port for obfuscation
142
+ "driver": "MySQL ODBC 5.1 Driver",
143
+ } # or _driver="MySQL ODBC 3.51 Driver
144
+ c["macro_is64bit"] = [
145
+ "provider",
146
+ "Provider=MSDASQL;",
147
+ ] # turn on the 64 bit ODBC adapter only if needed
148
+ cs = (
149
+ "%(provider)sDriver={%(driver)s};Server=%(host)s;Port=3330;"
150
+ + "Database=%(database)s;user=%(user)s;password=%(password)s;Option=3;"
151
+ )
152
+ print(" ...Testing MySql login to {}...".format(c["host"]))
153
+ doMySqlTest, connStrMySql, dbMySqlconnect = tryconnection.try_connection(
154
+ verbose, cs, 5, **c
155
+ )
156
+
157
+
158
+ if doPostgresTest:
159
+ _computername = "testpg.2txt.us"
160
+ _databasename = "adotest"
161
+ _username = "adotestuser"
162
+ _password = "12345678"
163
+ kws = {"timeout": 4}
164
+ kws["macro_is64bit"] = [
165
+ "prov_drv",
166
+ "Provider=MSDASQL;Driver={PostgreSQL Unicode(x64)}",
167
+ "Driver=PostgreSQL Unicode",
168
+ ]
169
+ # get driver from http://www.postgresql.org/ftp/odbc/versions/
170
+ # test using positional and keyword arguments (bad example for real code)
171
+ print(" ...Testing PostgreSQL login to {}...".format(_computername))
172
+ doPostgresTest, connStrPostgres, dbPostgresConnect = tryconnection.try_connection(
173
+ verbose,
174
+ "%(prov_drv)s;Server=%(host)s;Database=%(database)s;uid=%(user)s;pwd=%(password)s;port=5430;", # note nonstandard port
175
+ _username,
176
+ _password,
177
+ _computername,
178
+ _databasename,
179
+ **kws,
180
+ )
181
+
182
+ assert (
183
+ doAccessTest or doSqlServerTest or doMySqlTest or doPostgresTest
184
+ ), "No database engine found for testing"
myenv/Lib/site-packages/adodbapi/test/dbapi20.py ADDED
@@ -0,0 +1,885 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """ Python DB API 2.0 driver compliance unit test suite.
3
+
4
+ This software is Public Domain and may be used without restrictions.
5
+
6
+ "Now we have booze and barflies entering the discussion, plus rumours of
7
+ DBAs on drugs... and I won't tell you what flashes through my mind each
8
+ time I read the subject line with 'Anal Compliance' in it. All around
9
+ this is turning out to be a thoroughly unwholesome unit test."
10
+
11
+ -- Ian Bicking
12
+ """
13
+
14
+ __version__ = "$Revision: 1.15.0 $"[11:-2]
15
+ __author__ = "Stuart Bishop <stuart@stuartbishop.net>"
16
+
17
+ import sys
18
+ import time
19
+ import unittest
20
+
21
+ # set this to "True" to follow API 2.0 to the letter
22
+ TEST_FOR_NON_IDEMPOTENT_CLOSE = False
23
+
24
+ # Revision 1.15 2019/11/22 00:50:00 kf7xm
25
+ # Make Turn off IDEMPOTENT_CLOSE a proper skipTest
26
+
27
+ # Revision 1.14 2013/05/20 11:02:05 kf7xm
28
+ # Add a literal string to the format insertion test to catch trivial re-format algorithms
29
+
30
+ # Revision 1.13 2013/05/08 14:31:50 kf7xm
31
+ # Quick switch to Turn off IDEMPOTENT_CLOSE test. Also: Silence teardown failure
32
+
33
+
34
+ # Revision 1.12 2009/02/06 03:35:11 kf7xm
35
+ # Tested okay with Python 3.0, includes last minute patches from Mark H.
36
+ #
37
+ # Revision 1.1.1.1.2.1 2008/09/20 19:54:59 rupole
38
+ # Include latest changes from main branch
39
+ # Updates for py3k
40
+ #
41
+ # Revision 1.11 2005/01/02 02:41:01 zenzen
42
+ # Update author email address
43
+ #
44
+ # Revision 1.10 2003/10/09 03:14:14 zenzen
45
+ # Add test for DB API 2.0 optional extension, where database exceptions
46
+ # are exposed as attributes on the Connection object.
47
+ #
48
+ # Revision 1.9 2003/08/13 01:16:36 zenzen
49
+ # Minor tweak from Stefan Fleiter
50
+ #
51
+ # Revision 1.8 2003/04/10 00:13:25 zenzen
52
+ # Changes, as per suggestions by M.-A. Lemburg
53
+ # - Add a table prefix, to ensure namespace collisions can always be avoided
54
+ #
55
+ # Revision 1.7 2003/02/26 23:33:37 zenzen
56
+ # Break out DDL into helper functions, as per request by David Rushby
57
+ #
58
+ # Revision 1.6 2003/02/21 03:04:33 zenzen
59
+ # Stuff from Henrik Ekelund:
60
+ # added test_None
61
+ # added test_nextset & hooks
62
+ #
63
+ # Revision 1.5 2003/02/17 22:08:43 zenzen
64
+ # Implement suggestions and code from Henrik Eklund - test that cursor.arraysize
65
+ # defaults to 1 & generic cursor.callproc test added
66
+ #
67
+ # Revision 1.4 2003/02/15 00:16:33 zenzen
68
+ # Changes, as per suggestions and bug reports by M.-A. Lemburg,
69
+ # Matthew T. Kromer, Federico Di Gregorio and Daniel Dittmar
70
+ # - Class renamed
71
+ # - Now a subclass of TestCase, to avoid requiring the driver stub
72
+ # to use multiple inheritance
73
+ # - Reversed the polarity of buggy test in test_description
74
+ # - Test exception heirarchy correctly
75
+ # - self.populate is now self._populate(), so if a driver stub
76
+ # overrides self.ddl1 this change propogates
77
+ # - VARCHAR columns now have a width, which will hopefully make the
78
+ # DDL even more portible (this will be reversed if it causes more problems)
79
+ # - cursor.rowcount being checked after various execute and fetchXXX methods
80
+ # - Check for fetchall and fetchmany returning empty lists after results
81
+ # are exhausted (already checking for empty lists if select retrieved
82
+ # nothing
83
+ # - Fix bugs in test_setoutputsize_basic and test_setinputsizes
84
+ #
85
+
86
+
87
+ class DatabaseAPI20Test(unittest.TestCase):
88
+ """Test a database self.driver for DB API 2.0 compatibility.
89
+ This implementation tests Gadfly, but the TestCase
90
+ is structured so that other self.drivers can subclass this
91
+ test case to ensure compiliance with the DB-API. It is
92
+ expected that this TestCase may be expanded in the future
93
+ if ambiguities or edge conditions are discovered.
94
+
95
+ The 'Optional Extensions' are not yet being tested.
96
+
97
+ self.drivers should subclass this test, overriding setUp, tearDown,
98
+ self.driver, connect_args and connect_kw_args. Class specification
99
+ should be as follows:
100
+
101
+ import dbapi20
102
+ class mytest(dbapi20.DatabaseAPI20Test):
103
+ [...]
104
+
105
+ Don't 'import DatabaseAPI20Test from dbapi20', or you will
106
+ confuse the unit tester - just 'import dbapi20'.
107
+ """
108
+
109
+ # The self.driver module. This should be the module where the 'connect'
110
+ # method is to be found
111
+ driver = None
112
+ connect_args = () # List of arguments to pass to connect
113
+ connect_kw_args = {} # Keyword arguments for connect
114
+ table_prefix = "dbapi20test_" # If you need to specify a prefix for tables
115
+
116
+ ddl1 = "create table %sbooze (name varchar(20))" % table_prefix
117
+ ddl2 = "create table %sbarflys (name varchar(20), drink varchar(30))" % table_prefix
118
+ xddl1 = "drop table %sbooze" % table_prefix
119
+ xddl2 = "drop table %sbarflys" % table_prefix
120
+
121
+ lowerfunc = "lower" # Name of stored procedure to convert string->lowercase
122
+
123
+ # Some drivers may need to override these helpers, for example adding
124
+ # a 'commit' after the execute.
125
+ def executeDDL1(self, cursor):
126
+ cursor.execute(self.ddl1)
127
+
128
+ def executeDDL2(self, cursor):
129
+ cursor.execute(self.ddl2)
130
+
131
+ def setUp(self):
132
+ """self.drivers should override this method to perform required setup
133
+ if any is necessary, such as creating the database.
134
+ """
135
+ pass
136
+
137
+ def tearDown(self):
138
+ """self.drivers should override this method to perform required cleanup
139
+ if any is necessary, such as deleting the test database.
140
+ The default drops the tables that may be created.
141
+ """
142
+ try:
143
+ con = self._connect()
144
+ try:
145
+ cur = con.cursor()
146
+ for ddl in (self.xddl1, self.xddl2):
147
+ try:
148
+ cur.execute(ddl)
149
+ con.commit()
150
+ except self.driver.Error:
151
+ # Assume table didn't exist. Other tests will check if
152
+ # execute is busted.
153
+ pass
154
+ finally:
155
+ con.close()
156
+ except Exception:
157
+ pass
158
+
159
+ def _connect(self):
160
+ try:
161
+ r = self.driver.connect(*self.connect_args, **self.connect_kw_args)
162
+ except AttributeError:
163
+ self.fail("No connect method found in self.driver module")
164
+ return r
165
+
166
+ def test_connect(self):
167
+ con = self._connect()
168
+ con.close()
169
+
170
+ def test_apilevel(self):
171
+ try:
172
+ # Must exist
173
+ apilevel = self.driver.apilevel
174
+ # Must equal 2.0
175
+ self.assertEqual(apilevel, "2.0")
176
+ except AttributeError:
177
+ self.fail("Driver doesn't define apilevel")
178
+
179
+ def test_threadsafety(self):
180
+ try:
181
+ # Must exist
182
+ threadsafety = self.driver.threadsafety
183
+ # Must be a valid value
184
+ self.assertTrue(threadsafety in (0, 1, 2, 3))
185
+ except AttributeError:
186
+ self.fail("Driver doesn't define threadsafety")
187
+
188
+ def test_paramstyle(self):
189
+ try:
190
+ # Must exist
191
+ paramstyle = self.driver.paramstyle
192
+ # Must be a valid value
193
+ self.assertTrue(
194
+ paramstyle in ("qmark", "numeric", "named", "format", "pyformat")
195
+ )
196
+ except AttributeError:
197
+ self.fail("Driver doesn't define paramstyle")
198
+
199
+ def test_Exceptions(self):
200
+ # Make sure required exceptions exist, and are in the
201
+ # defined heirarchy.
202
+ if sys.version[0] == "3": # under Python 3 StardardError no longer exists
203
+ self.assertTrue(issubclass(self.driver.Warning, Exception))
204
+ self.assertTrue(issubclass(self.driver.Error, Exception))
205
+ else:
206
+ self.failUnless(issubclass(self.driver.Warning, Exception))
207
+ self.failUnless(issubclass(self.driver.Error, Exception))
208
+
209
+ self.assertTrue(issubclass(self.driver.InterfaceError, self.driver.Error))
210
+ self.assertTrue(issubclass(self.driver.DatabaseError, self.driver.Error))
211
+ self.assertTrue(issubclass(self.driver.OperationalError, self.driver.Error))
212
+ self.assertTrue(issubclass(self.driver.IntegrityError, self.driver.Error))
213
+ self.assertTrue(issubclass(self.driver.InternalError, self.driver.Error))
214
+ self.assertTrue(issubclass(self.driver.ProgrammingError, self.driver.Error))
215
+ self.assertTrue(issubclass(self.driver.NotSupportedError, self.driver.Error))
216
+
217
+ def test_ExceptionsAsConnectionAttributes(self):
218
+ # OPTIONAL EXTENSION
219
+ # Test for the optional DB API 2.0 extension, where the exceptions
220
+ # are exposed as attributes on the Connection object
221
+ # I figure this optional extension will be implemented by any
222
+ # driver author who is using this test suite, so it is enabled
223
+ # by default.
224
+ con = self._connect()
225
+ drv = self.driver
226
+ self.assertTrue(con.Warning is drv.Warning)
227
+ self.assertTrue(con.Error is drv.Error)
228
+ self.assertTrue(con.InterfaceError is drv.InterfaceError)
229
+ self.assertTrue(con.DatabaseError is drv.DatabaseError)
230
+ self.assertTrue(con.OperationalError is drv.OperationalError)
231
+ self.assertTrue(con.IntegrityError is drv.IntegrityError)
232
+ self.assertTrue(con.InternalError is drv.InternalError)
233
+ self.assertTrue(con.ProgrammingError is drv.ProgrammingError)
234
+ self.assertTrue(con.NotSupportedError is drv.NotSupportedError)
235
+
236
+ def test_commit(self):
237
+ con = self._connect()
238
+ try:
239
+ # Commit must work, even if it doesn't do anything
240
+ con.commit()
241
+ finally:
242
+ con.close()
243
+
244
+ def test_rollback(self):
245
+ con = self._connect()
246
+ # If rollback is defined, it should either work or throw
247
+ # the documented exception
248
+ if hasattr(con, "rollback"):
249
+ try:
250
+ con.rollback()
251
+ except self.driver.NotSupportedError:
252
+ pass
253
+
254
+ def test_cursor(self):
255
+ con = self._connect()
256
+ try:
257
+ cur = con.cursor()
258
+ finally:
259
+ con.close()
260
+
261
+ def test_cursor_isolation(self):
262
+ con = self._connect()
263
+ try:
264
+ # Make sure cursors created from the same connection have
265
+ # the documented transaction isolation level
266
+ cur1 = con.cursor()
267
+ cur2 = con.cursor()
268
+ self.executeDDL1(cur1)
269
+ cur1.execute(
270
+ "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix)
271
+ )
272
+ cur2.execute("select name from %sbooze" % self.table_prefix)
273
+ booze = cur2.fetchall()
274
+ self.assertEqual(len(booze), 1)
275
+ self.assertEqual(len(booze[0]), 1)
276
+ self.assertEqual(booze[0][0], "Victoria Bitter")
277
+ finally:
278
+ con.close()
279
+
280
+ def test_description(self):
281
+ con = self._connect()
282
+ try:
283
+ cur = con.cursor()
284
+ self.executeDDL1(cur)
285
+ self.assertEqual(
286
+ cur.description,
287
+ None,
288
+ "cursor.description should be none after executing a "
289
+ "statement that can return no rows (such as DDL)",
290
+ )
291
+ cur.execute("select name from %sbooze" % self.table_prefix)
292
+ self.assertEqual(
293
+ len(cur.description), 1, "cursor.description describes too many columns"
294
+ )
295
+ self.assertEqual(
296
+ len(cur.description[0]),
297
+ 7,
298
+ "cursor.description[x] tuples must have 7 elements",
299
+ )
300
+ self.assertEqual(
301
+ cur.description[0][0].lower(),
302
+ "name",
303
+ "cursor.description[x][0] must return column name",
304
+ )
305
+ self.assertEqual(
306
+ cur.description[0][1],
307
+ self.driver.STRING,
308
+ "cursor.description[x][1] must return column type. Got %r"
309
+ % cur.description[0][1],
310
+ )
311
+
312
+ # Make sure self.description gets reset
313
+ self.executeDDL2(cur)
314
+ self.assertEqual(
315
+ cur.description,
316
+ None,
317
+ "cursor.description not being set to None when executing "
318
+ "no-result statements (eg. DDL)",
319
+ )
320
+ finally:
321
+ con.close()
322
+
323
+ def test_rowcount(self):
324
+ con = self._connect()
325
+ try:
326
+ cur = con.cursor()
327
+ self.executeDDL1(cur)
328
+ self.assertTrue(
329
+ cur.rowcount in (-1, 0), # Bug #543885
330
+ "cursor.rowcount should be -1 or 0 after executing no-result "
331
+ "statements",
332
+ )
333
+ cur.execute(
334
+ "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix)
335
+ )
336
+ self.assertTrue(
337
+ cur.rowcount in (-1, 1),
338
+ "cursor.rowcount should == number or rows inserted, or "
339
+ "set to -1 after executing an insert statement",
340
+ )
341
+ cur.execute("select name from %sbooze" % self.table_prefix)
342
+ self.assertTrue(
343
+ cur.rowcount in (-1, 1),
344
+ "cursor.rowcount should == number of rows returned, or "
345
+ "set to -1 after executing a select statement",
346
+ )
347
+ self.executeDDL2(cur)
348
+ self.assertEqual(
349
+ cur.rowcount,
350
+ -1,
351
+ "cursor.rowcount not being reset to -1 after executing "
352
+ "no-result statements",
353
+ )
354
+ finally:
355
+ con.close()
356
+
357
+ lower_func = "lower"
358
+
359
+ def test_callproc(self):
360
+ con = self._connect()
361
+ try:
362
+ cur = con.cursor()
363
+ if self.lower_func and hasattr(cur, "callproc"):
364
+ r = cur.callproc(self.lower_func, ("FOO",))
365
+ self.assertEqual(len(r), 1)
366
+ self.assertEqual(r[0], "FOO")
367
+ r = cur.fetchall()
368
+ self.assertEqual(len(r), 1, "callproc produced no result set")
369
+ self.assertEqual(len(r[0]), 1, "callproc produced invalid result set")
370
+ self.assertEqual(r[0][0], "foo", "callproc produced invalid results")
371
+ finally:
372
+ con.close()
373
+
374
+ def test_close(self):
375
+ con = self._connect()
376
+ try:
377
+ cur = con.cursor()
378
+ finally:
379
+ con.close()
380
+
381
+ # cursor.execute should raise an Error if called after connection
382
+ # closed
383
+ self.assertRaises(self.driver.Error, self.executeDDL1, cur)
384
+
385
+ # connection.commit should raise an Error if called after connection'
386
+ # closed.'
387
+ self.assertRaises(self.driver.Error, con.commit)
388
+
389
+ # connection.close should raise an Error if called more than once
390
+ #!!! reasonable persons differ about the usefulness of this test and this feature !!!
391
+ if TEST_FOR_NON_IDEMPOTENT_CLOSE:
392
+ self.assertRaises(self.driver.Error, con.close)
393
+ else:
394
+ self.skipTest(
395
+ "Non-idempotent close is considered a bad thing by some people."
396
+ )
397
+
398
+ def test_execute(self):
399
+ con = self._connect()
400
+ try:
401
+ cur = con.cursor()
402
+ self._paraminsert(cur)
403
+ finally:
404
+ con.close()
405
+
406
+ def _paraminsert(self, cur):
407
+ self.executeDDL2(cur)
408
+ cur.execute(
409
+ "insert into %sbarflys values ('Victoria Bitter', 'thi%%s :may ca%%(u)se? troub:1e')"
410
+ % (self.table_prefix)
411
+ )
412
+ self.assertTrue(cur.rowcount in (-1, 1))
413
+
414
+ if self.driver.paramstyle == "qmark":
415
+ cur.execute(
416
+ "insert into %sbarflys values (?, 'thi%%s :may ca%%(u)se? troub:1e')"
417
+ % self.table_prefix,
418
+ ("Cooper's",),
419
+ )
420
+ elif self.driver.paramstyle == "numeric":
421
+ cur.execute(
422
+ "insert into %sbarflys values (:1, 'thi%%s :may ca%%(u)se? troub:1e')"
423
+ % self.table_prefix,
424
+ ("Cooper's",),
425
+ )
426
+ elif self.driver.paramstyle == "named":
427
+ cur.execute(
428
+ "insert into %sbarflys values (:beer, 'thi%%s :may ca%%(u)se? troub:1e')"
429
+ % self.table_prefix,
430
+ {"beer": "Cooper's"},
431
+ )
432
+ elif self.driver.paramstyle == "format":
433
+ cur.execute(
434
+ "insert into %sbarflys values (%%s, 'thi%%s :may ca%%(u)se? troub:1e')"
435
+ % self.table_prefix,
436
+ ("Cooper's",),
437
+ )
438
+ elif self.driver.paramstyle == "pyformat":
439
+ cur.execute(
440
+ "insert into %sbarflys values (%%(beer)s, 'thi%%s :may ca%%(u)se? troub:1e')"
441
+ % self.table_prefix,
442
+ {"beer": "Cooper's"},
443
+ )
444
+ else:
445
+ self.fail("Invalid paramstyle")
446
+ self.assertTrue(cur.rowcount in (-1, 1))
447
+
448
+ cur.execute("select name, drink from %sbarflys" % self.table_prefix)
449
+ res = cur.fetchall()
450
+ self.assertEqual(len(res), 2, "cursor.fetchall returned too few rows")
451
+ beers = [res[0][0], res[1][0]]
452
+ beers.sort()
453
+ self.assertEqual(
454
+ beers[0],
455
+ "Cooper's",
456
+ "cursor.fetchall retrieved incorrect data, or data inserted incorrectly",
457
+ )
458
+ self.assertEqual(
459
+ beers[1],
460
+ "Victoria Bitter",
461
+ "cursor.fetchall retrieved incorrect data, or data inserted incorrectly",
462
+ )
463
+ trouble = "thi%s :may ca%(u)se? troub:1e"
464
+ self.assertEqual(
465
+ res[0][1],
466
+ trouble,
467
+ "cursor.fetchall retrieved incorrect data, or data inserted "
468
+ f"incorrectly. Got={res[0][1]!r}, Expected={trouble!r}",
469
+ )
470
+ self.assertEqual(
471
+ res[1][1],
472
+ trouble,
473
+ "cursor.fetchall retrieved incorrect data, or data inserted "
474
+ f"incorrectly. Got={res[1][1]!r}, Expected={trouble!r}",
475
+ )
476
+
477
+ def test_executemany(self):
478
+ con = self._connect()
479
+ try:
480
+ cur = con.cursor()
481
+ self.executeDDL1(cur)
482
+ largs = [("Cooper's",), ("Boag's",)]
483
+ margs = [{"beer": "Cooper's"}, {"beer": "Boag's"}]
484
+ if self.driver.paramstyle == "qmark":
485
+ cur.executemany(
486
+ "insert into %sbooze values (?)" % self.table_prefix, largs
487
+ )
488
+ elif self.driver.paramstyle == "numeric":
489
+ cur.executemany(
490
+ "insert into %sbooze values (:1)" % self.table_prefix, largs
491
+ )
492
+ elif self.driver.paramstyle == "named":
493
+ cur.executemany(
494
+ "insert into %sbooze values (:beer)" % self.table_prefix, margs
495
+ )
496
+ elif self.driver.paramstyle == "format":
497
+ cur.executemany(
498
+ "insert into %sbooze values (%%s)" % self.table_prefix, largs
499
+ )
500
+ elif self.driver.paramstyle == "pyformat":
501
+ cur.executemany(
502
+ "insert into %sbooze values (%%(beer)s)" % (self.table_prefix),
503
+ margs,
504
+ )
505
+ else:
506
+ self.fail("Unknown paramstyle")
507
+ self.assertTrue(
508
+ cur.rowcount in (-1, 2),
509
+ "insert using cursor.executemany set cursor.rowcount to "
510
+ "incorrect value %r" % cur.rowcount,
511
+ )
512
+ cur.execute("select name from %sbooze" % self.table_prefix)
513
+ res = cur.fetchall()
514
+ self.assertEqual(
515
+ len(res), 2, "cursor.fetchall retrieved incorrect number of rows"
516
+ )
517
+ beers = [res[0][0], res[1][0]]
518
+ beers.sort()
519
+ self.assertEqual(
520
+ beers[0], "Boag's", 'incorrect data "%s" retrieved' % beers[0]
521
+ )
522
+ self.assertEqual(beers[1], "Cooper's", "incorrect data retrieved")
523
+ finally:
524
+ con.close()
525
+
526
+ def test_fetchone(self):
527
+ con = self._connect()
528
+ try:
529
+ cur = con.cursor()
530
+
531
+ # cursor.fetchone should raise an Error if called before
532
+ # executing a select-type query
533
+ self.assertRaises(self.driver.Error, cur.fetchone)
534
+
535
+ # cursor.fetchone should raise an Error if called after
536
+ # executing a query that cannnot return rows
537
+ self.executeDDL1(cur)
538
+ self.assertRaises(self.driver.Error, cur.fetchone)
539
+
540
+ cur.execute("select name from %sbooze" % self.table_prefix)
541
+ self.assertEqual(
542
+ cur.fetchone(),
543
+ None,
544
+ "cursor.fetchone should return None if a query retrieves no rows",
545
+ )
546
+ self.assertTrue(cur.rowcount in (-1, 0))
547
+
548
+ # cursor.fetchone should raise an Error if called after
549
+ # executing a query that cannnot return rows
550
+ cur.execute(
551
+ "insert into %sbooze values ('Victoria Bitter')" % (self.table_prefix)
552
+ )
553
+ self.assertRaises(self.driver.Error, cur.fetchone)
554
+
555
+ cur.execute("select name from %sbooze" % self.table_prefix)
556
+ r = cur.fetchone()
557
+ self.assertEqual(
558
+ len(r), 1, "cursor.fetchone should have retrieved a single row"
559
+ )
560
+ self.assertEqual(
561
+ r[0], "Victoria Bitter", "cursor.fetchone retrieved incorrect data"
562
+ )
563
+ self.assertEqual(
564
+ cur.fetchone(),
565
+ None,
566
+ "cursor.fetchone should return None if no more rows available",
567
+ )
568
+ self.assertTrue(cur.rowcount in (-1, 1))
569
+ finally:
570
+ con.close()
571
+
572
+ samples = [
573
+ "Carlton Cold",
574
+ "Carlton Draft",
575
+ "Mountain Goat",
576
+ "Redback",
577
+ "Victoria Bitter",
578
+ "XXXX",
579
+ ]
580
+
581
+ def _populate(self):
582
+ """Return a list of sql commands to setup the DB for the fetch
583
+ tests.
584
+ """
585
+ populate = [
586
+ "insert into %sbooze values ('%s')" % (self.table_prefix, s)
587
+ for s in self.samples
588
+ ]
589
+ return populate
590
+
591
+ def test_fetchmany(self):
592
+ con = self._connect()
593
+ try:
594
+ cur = con.cursor()
595
+
596
+ # cursor.fetchmany should raise an Error if called without
597
+ # issuing a query
598
+ self.assertRaises(self.driver.Error, cur.fetchmany, 4)
599
+
600
+ self.executeDDL1(cur)
601
+ for sql in self._populate():
602
+ cur.execute(sql)
603
+
604
+ cur.execute("select name from %sbooze" % self.table_prefix)
605
+ r = cur.fetchmany()
606
+ self.assertEqual(
607
+ len(r),
608
+ 1,
609
+ "cursor.fetchmany retrieved incorrect number of rows, "
610
+ "default of arraysize is one.",
611
+ )
612
+ cur.arraysize = 10
613
+ r = cur.fetchmany(3) # Should get 3 rows
614
+ self.assertEqual(
615
+ len(r), 3, "cursor.fetchmany retrieved incorrect number of rows"
616
+ )
617
+ r = cur.fetchmany(4) # Should get 2 more
618
+ self.assertEqual(
619
+ len(r), 2, "cursor.fetchmany retrieved incorrect number of rows"
620
+ )
621
+ r = cur.fetchmany(4) # Should be an empty sequence
622
+ self.assertEqual(
623
+ len(r),
624
+ 0,
625
+ "cursor.fetchmany should return an empty sequence after "
626
+ "results are exhausted",
627
+ )
628
+ self.assertTrue(cur.rowcount in (-1, 6))
629
+
630
+ # Same as above, using cursor.arraysize
631
+ cur.arraysize = 4
632
+ cur.execute("select name from %sbooze" % self.table_prefix)
633
+ r = cur.fetchmany() # Should get 4 rows
634
+ self.assertEqual(
635
+ len(r), 4, "cursor.arraysize not being honoured by fetchmany"
636
+ )
637
+ r = cur.fetchmany() # Should get 2 more
638
+ self.assertEqual(len(r), 2)
639
+ r = cur.fetchmany() # Should be an empty sequence
640
+ self.assertEqual(len(r), 0)
641
+ self.assertTrue(cur.rowcount in (-1, 6))
642
+
643
+ cur.arraysize = 6
644
+ cur.execute("select name from %sbooze" % self.table_prefix)
645
+ rows = cur.fetchmany() # Should get all rows
646
+ self.assertTrue(cur.rowcount in (-1, 6))
647
+ self.assertEqual(len(rows), 6)
648
+ self.assertEqual(len(rows), 6)
649
+ rows = [r[0] for r in rows]
650
+ rows.sort()
651
+
652
+ # Make sure we get the right data back out
653
+ for i in range(0, 6):
654
+ self.assertEqual(
655
+ rows[i],
656
+ self.samples[i],
657
+ "incorrect data retrieved by cursor.fetchmany",
658
+ )
659
+
660
+ rows = cur.fetchmany() # Should return an empty list
661
+ self.assertEqual(
662
+ len(rows),
663
+ 0,
664
+ "cursor.fetchmany should return an empty sequence if "
665
+ "called after the whole result set has been fetched",
666
+ )
667
+ self.assertTrue(cur.rowcount in (-1, 6))
668
+
669
+ self.executeDDL2(cur)
670
+ cur.execute("select name from %sbarflys" % self.table_prefix)
671
+ r = cur.fetchmany() # Should get empty sequence
672
+ self.assertEqual(
673
+ len(r),
674
+ 0,
675
+ "cursor.fetchmany should return an empty sequence if "
676
+ "query retrieved no rows",
677
+ )
678
+ self.assertTrue(cur.rowcount in (-1, 0))
679
+
680
+ finally:
681
+ con.close()
682
+
683
+ def test_fetchall(self):
684
+ con = self._connect()
685
+ try:
686
+ cur = con.cursor()
687
+ # cursor.fetchall should raise an Error if called
688
+ # without executing a query that may return rows (such
689
+ # as a select)
690
+ self.assertRaises(self.driver.Error, cur.fetchall)
691
+
692
+ self.executeDDL1(cur)
693
+ for sql in self._populate():
694
+ cur.execute(sql)
695
+
696
+ # cursor.fetchall should raise an Error if called
697
+ # after executing a a statement that cannot return rows
698
+ self.assertRaises(self.driver.Error, cur.fetchall)
699
+
700
+ cur.execute("select name from %sbooze" % self.table_prefix)
701
+ rows = cur.fetchall()
702
+ self.assertTrue(cur.rowcount in (-1, len(self.samples)))
703
+ self.assertEqual(
704
+ len(rows),
705
+ len(self.samples),
706
+ "cursor.fetchall did not retrieve all rows",
707
+ )
708
+ rows = [r[0] for r in rows]
709
+ rows.sort()
710
+ for i in range(0, len(self.samples)):
711
+ self.assertEqual(
712
+ rows[i], self.samples[i], "cursor.fetchall retrieved incorrect rows"
713
+ )
714
+ rows = cur.fetchall()
715
+ self.assertEqual(
716
+ len(rows),
717
+ 0,
718
+ "cursor.fetchall should return an empty list if called "
719
+ "after the whole result set has been fetched",
720
+ )
721
+ self.assertTrue(cur.rowcount in (-1, len(self.samples)))
722
+
723
+ self.executeDDL2(cur)
724
+ cur.execute("select name from %sbarflys" % self.table_prefix)
725
+ rows = cur.fetchall()
726
+ self.assertTrue(cur.rowcount in (-1, 0))
727
+ self.assertEqual(
728
+ len(rows),
729
+ 0,
730
+ "cursor.fetchall should return an empty list if "
731
+ "a select query returns no rows",
732
+ )
733
+
734
+ finally:
735
+ con.close()
736
+
737
+ def test_mixedfetch(self):
738
+ con = self._connect()
739
+ try:
740
+ cur = con.cursor()
741
+ self.executeDDL1(cur)
742
+ for sql in self._populate():
743
+ cur.execute(sql)
744
+
745
+ cur.execute("select name from %sbooze" % self.table_prefix)
746
+ rows1 = cur.fetchone()
747
+ rows23 = cur.fetchmany(2)
748
+ rows4 = cur.fetchone()
749
+ rows56 = cur.fetchall()
750
+ self.assertTrue(cur.rowcount in (-1, 6))
751
+ self.assertEqual(
752
+ len(rows23), 2, "fetchmany returned incorrect number of rows"
753
+ )
754
+ self.assertEqual(
755
+ len(rows56), 2, "fetchall returned incorrect number of rows"
756
+ )
757
+
758
+ rows = [rows1[0]]
759
+ rows.extend([rows23[0][0], rows23[1][0]])
760
+ rows.append(rows4[0])
761
+ rows.extend([rows56[0][0], rows56[1][0]])
762
+ rows.sort()
763
+ for i in range(0, len(self.samples)):
764
+ self.assertEqual(
765
+ rows[i], self.samples[i], "incorrect data retrieved or inserted"
766
+ )
767
+ finally:
768
+ con.close()
769
+
770
+ def help_nextset_setUp(self, cur):
771
+ """Should create a procedure called deleteme
772
+ that returns two result sets, first the
773
+ number of rows in booze then "name from booze"
774
+ """
775
+ raise NotImplementedError("Helper not implemented")
776
+ # sql="""
777
+ # create procedure deleteme as
778
+ # begin
779
+ # select count(*) from booze
780
+ # select name from booze
781
+ # end
782
+ # """
783
+ # cur.execute(sql)
784
+
785
+ def help_nextset_tearDown(self, cur):
786
+ "If cleaning up is needed after nextSetTest"
787
+ raise NotImplementedError("Helper not implemented")
788
+ # cur.execute("drop procedure deleteme")
789
+
790
+ def test_nextset(self):
791
+ raise NotImplementedError("Drivers need to override this test")
792
+
793
+ def test_arraysize(self):
794
+ # Not much here - rest of the tests for this are in test_fetchmany
795
+ con = self._connect()
796
+ try:
797
+ cur = con.cursor()
798
+ self.assertTrue(
799
+ hasattr(cur, "arraysize"), "cursor.arraysize must be defined"
800
+ )
801
+ finally:
802
+ con.close()
803
+
804
+ def test_setinputsizes(self):
805
+ con = self._connect()
806
+ try:
807
+ cur = con.cursor()
808
+ cur.setinputsizes((25,))
809
+ self._paraminsert(cur) # Make sure cursor still works
810
+ finally:
811
+ con.close()
812
+
813
+ def test_setoutputsize_basic(self):
814
+ # Basic test is to make sure setoutputsize doesn't blow up
815
+ con = self._connect()
816
+ try:
817
+ cur = con.cursor()
818
+ cur.setoutputsize(1000)
819
+ cur.setoutputsize(2000, 0)
820
+ self._paraminsert(cur) # Make sure the cursor still works
821
+ finally:
822
+ con.close()
823
+
824
+ def test_setoutputsize(self):
825
+ # Real test for setoutputsize is driver dependant
826
+ raise NotImplementedError("Driver needed to override this test")
827
+
828
+ def test_None(self):
829
+ con = self._connect()
830
+ try:
831
+ cur = con.cursor()
832
+ self.executeDDL1(cur)
833
+ cur.execute("insert into %sbooze values (NULL)" % self.table_prefix)
834
+ cur.execute("select name from %sbooze" % self.table_prefix)
835
+ r = cur.fetchall()
836
+ self.assertEqual(len(r), 1)
837
+ self.assertEqual(len(r[0]), 1)
838
+ self.assertEqual(r[0][0], None, "NULL value not returned as None")
839
+ finally:
840
+ con.close()
841
+
842
+ def test_Date(self):
843
+ d1 = self.driver.Date(2002, 12, 25)
844
+ d2 = self.driver.DateFromTicks(time.mktime((2002, 12, 25, 0, 0, 0, 0, 0, 0)))
845
+ # Can we assume this? API doesn't specify, but it seems implied
846
+ # self.assertEqual(str(d1),str(d2))
847
+
848
+ def test_Time(self):
849
+ t1 = self.driver.Time(13, 45, 30)
850
+ t2 = self.driver.TimeFromTicks(time.mktime((2001, 1, 1, 13, 45, 30, 0, 0, 0)))
851
+ # Can we assume this? API doesn't specify, but it seems implied
852
+ # self.assertEqual(str(t1),str(t2))
853
+
854
+ def test_Timestamp(self):
855
+ t1 = self.driver.Timestamp(2002, 12, 25, 13, 45, 30)
856
+ t2 = self.driver.TimestampFromTicks(
857
+ time.mktime((2002, 12, 25, 13, 45, 30, 0, 0, 0))
858
+ )
859
+ # Can we assume this? API doesn't specify, but it seems implied
860
+ # self.assertEqual(str(t1),str(t2))
861
+
862
+ def test_Binary(self):
863
+ b = self.driver.Binary(b"Something")
864
+ b = self.driver.Binary(b"")
865
+
866
+ def test_STRING(self):
867
+ self.assertTrue(hasattr(self.driver, "STRING"), "module.STRING must be defined")
868
+
869
+ def test_BINARY(self):
870
+ self.assertTrue(
871
+ hasattr(self.driver, "BINARY"), "module.BINARY must be defined."
872
+ )
873
+
874
+ def test_NUMBER(self):
875
+ self.assertTrue(
876
+ hasattr(self.driver, "NUMBER"), "module.NUMBER must be defined."
877
+ )
878
+
879
+ def test_DATETIME(self):
880
+ self.assertTrue(
881
+ hasattr(self.driver, "DATETIME"), "module.DATETIME must be defined."
882
+ )
883
+
884
+ def test_ROWID(self):
885
+ self.assertTrue(hasattr(self.driver, "ROWID"), "module.ROWID must be defined.")