pyconverters-openai_vision 0.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ # ignore .git and .cache folders
2
+ .git
3
+ .cache
4
+ .pytest_cache
5
+ models
6
+ tests
7
+ log
8
+ **/__pycache__
9
+ *.pyc
@@ -0,0 +1,18 @@
1
+ /.idea/
2
+ /.vscode/
3
+ /dist/
4
+ dist
5
+ .git
6
+ .cache
7
+ **/.pytest_cache
8
+ models
9
+ log
10
+ **/__pycache__/
11
+ *.pyc
12
+ **/test-reports
13
+ **/.passwd-pypi
14
+ **/.env
15
+ **/results.xml
16
+ .groovylintrc.json
17
+ .emailNotif
18
+ /.tox/
@@ -0,0 +1,19 @@
1
+ FROM python:3.8-slim-buster
2
+ # Install prerequisites
3
+ RUN apt-get update -y && \
4
+ apt-get install -y \
5
+ patch \
6
+ gcc && \
7
+ apt-get install -y --no-install-recommends \
8
+ g++ \
9
+ git && \
10
+ # Final upgrade + clean
11
+ apt-get update -y && \
12
+ apt-get clean all -y
13
+
14
+ # Enable Installing packages as root
15
+ ENV FLIT_ROOT_INSTALL=1
16
+
17
+ # Add pyproject.toml + README.md for flit install
18
+ ADD pyproject.toml pyproject.toml
19
+ ADD README.md README.md
@@ -0,0 +1,408 @@
1
+ pipeline {
2
+ environment {
3
+ PATH_HOME = '/home/jenkins'
4
+ TEST_REPORT_DIR = '/root/test-reports'
5
+ PYTHONPYCACHEPREFIX = '/tmp/.pytest_cache'
6
+ PYTHONDONTWRITEBYTECODE = '1'
7
+ JENKINS_UIDGID = '1004:1004'
8
+
9
+ MAJOR_VERSION = '0'
10
+ MINOR_VERSION = '5'
11
+ }
12
+
13
+ agent none
14
+
15
+ triggers {
16
+ upstream(upstreamProjects: 'pymultirole_plugins/' + BRANCH_NAME.replaceAll('/', '%2F'),\
17
+ threshold: hudson.model.Result.SUCCESS)
18
+ }
19
+
20
+ stages {
21
+ stage('Catch build termination') {
22
+ agent {
23
+ node {
24
+ label 'built-in'
25
+ customWorkspace "${PATH_HOME}/${JOB_NAME}"
26
+ }
27
+ }
28
+ stages {
29
+ stage('Analyse build cause') {
30
+ steps {
31
+ script {
32
+ analyseBuildCause()
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ stage('Generate new version') {
40
+ when {
41
+ environment name: 'SKIP_JOB', value: '0'
42
+ }
43
+
44
+ agent {
45
+ node {
46
+ label 'built-in'
47
+ customWorkspace "${PATH_HOME}/${JOB_NAME}"
48
+ }
49
+ }
50
+
51
+ stages {
52
+ stage('Add credentials') {
53
+ steps {
54
+ script {
55
+ // Add password file for flit publishing
56
+ sh "cp ${PATH_HOME}/.passwd-pypi .env"
57
+ }
58
+ }
59
+ }
60
+
61
+ stage('Commit new version') {
62
+ steps {
63
+ script {
64
+ println("attempt to publish ${JOB_NAME} with version: ${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_ID}")
65
+
66
+ // push updates of file __init__.py
67
+ withCredentials([gitUsernamePassword(credentialsId: 'bitbucket-user', gitToolName: 'git-tool')]) {
68
+ sh 'git pull'
69
+ sh "echo '\"\"\"OpenAIVision converter\"\"\"' > pyconverters_openai_vision/__init__.py"
70
+ sh "echo '__version__ = \"${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_ID}\"' >> pyconverters_openai_vision/__init__.py"
71
+ sh 'git commit pyconverters_openai_vision/__init__.py -m "[Jenkins CI] Commit on version files" || echo "No changes to commit"'
72
+ sh 'git push'
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ stage('Build, test and publish') {
81
+ when {
82
+ beforeAgent true
83
+ environment name: 'SKIP_JOB', value: '0'
84
+ }
85
+
86
+ agent {
87
+ // dockerfile agent
88
+ // Mounted volume for Junit reports
89
+ // - docker: /root/test-reports
90
+ // - host : /tmp/_${JOB_NAME}/test-reports
91
+ dockerfile {
92
+ label 'built-in'
93
+ customWorkspace "${PATH_HOME}/${JOB_NAME}"
94
+ filename 'Dockerfile'
95
+ args "-u root --privileged -v /tmp/_${JOB_NAME}/test-reports:${TEST_REPORT_DIR}"
96
+ }
97
+ }
98
+
99
+ stages {
100
+ stage('Install flit & flake8') {
101
+ steps {
102
+ // remove any previous tox env
103
+ sh 'rm -rf .tox'
104
+ sh 'python -m pip install pip==22.0.3'
105
+ sh 'pip install --no-cache-dir flit==3.2.0 flake8==3.9.2 flakehell tox'
106
+ sh 'flit install'
107
+ }
108
+ }
109
+
110
+ stage('Test & lint python code') {
111
+ steps {
112
+ // remove any previous results.xml file
113
+ sh "rm -f ${TEST_REPORT_DIR}/results.xml"
114
+ sh 'tox'
115
+ }
116
+ }
117
+
118
+ stage('Publish on PyPI') {
119
+ environment {
120
+ FLIT_USERNAME = getUserName '.env'
121
+ FLIT_PASSWORD = getUserPass '.env'
122
+ }
123
+ steps {
124
+ // remove any previous folder dist
125
+ sh 'rm -rf dist'
126
+ // create (as root) folder dist
127
+ sh 'mkdir dist'
128
+ // pull recent updates of file __init__.py
129
+ withCredentials([gitUsernamePassword(credentialsId: 'bitbucket-user', gitToolName: 'git-tool')]) {
130
+ sh 'git config --global pull.rebase false'
131
+ sh "git config --global --add safe.directory ${WORKSPACE}"
132
+ sh 'git pull'
133
+ }
134
+ // put back owner of .git folder
135
+ sh "chown -R ${JENKINS_UIDGID} ${WORKSPACE}/.git"
136
+ // put back owner of pulled file
137
+ sh "chown ${JENKINS_UIDGID} pyconverters_openai_vision/__init__.py"
138
+ // get git status
139
+ sh 'git status'
140
+ // publish on PyPI
141
+ sh '''
142
+ export COMMIT_VERSION=$( cat pyconverters_openai_vision/__init__.py|grep version|cut -d '"' -f2|tr -s '[:blank:]' )
143
+ export BUILD_VERSION="${MAJOR_VERSION}"."${MINOR_VERSION}"."${BUILD_ID}"
144
+ if [ "${COMMIT_VERSION}" = "${BUILD_VERSION}" ] ; then flit publish ; fi
145
+ '''
146
+ // remove current folder dist
147
+ sh 'rm -rf dist'
148
+ // remove current folder .hypothesis
149
+ sh 'rm -rf .hypothesis'
150
+ // remove current folder .tox
151
+ sh 'rm -rf .tox'
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ post {
159
+ // only triggered when blue or green sign
160
+ success {
161
+ // node is specified here to get an agent
162
+ node('built-in') {
163
+ // keep using customWorkspace to store Junit report
164
+ ws("${PATH_HOME}/${JOB_NAME}") {
165
+ script {
166
+ try {
167
+ sh 'rm -f results.xml'
168
+ sh "cp /tmp/_${JOB_NAME}/test-reports/results.xml results.xml"
169
+ } catch (Exception e) {
170
+ println 'Exception occurred: ' + e.toString()
171
+ }
172
+ try {
173
+ junit 'results.xml'
174
+ } catch (Exception e) {
175
+ println 'Exception occurred: ' + e.toString()
176
+ }
177
+ if (sendEmailNotif("${PATH_HOME}/${JOB_NAME}", "${BUILD_NUMBER}")) {
178
+ println 'sending Success Build notification'
179
+ CUSTOM_SUBJECT = '[CI - Jenkinzz SUCCESS] ' + CUSTOM_SUBJECT
180
+ emailext(
181
+ mimeType: 'text/html',
182
+ subject: CUSTOM_SUBJECT,
183
+ body: '${DEFAULT_CONTENT}',
184
+ replyTo: '${DEFAULT_REPLYTO}',
185
+ to: '${ADMIN_RECIPIENTS}' + ';' + CUSTOM_RECIPIENTS
186
+ )
187
+ switchEmailNotif(false, BUILD_NUMBER)
188
+ } else {
189
+ println 'preventing Success Build notification'
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
195
+ // triggered when red sign
196
+ failure {
197
+ // node is specified here to get an agent
198
+ node('built-in') {
199
+ // keep using customWorkspace to store Junit report
200
+ ws("${PATH_HOME}/${JOB_NAME}") {
201
+ script {
202
+ try {
203
+ sh 'rm -f results.xml'
204
+ sh "cp /tmp/_${JOB_NAME}/test-reports/results.xml results.xml"
205
+ } catch (Exception e) {
206
+ println 'Exception occurred: ' + e.toString()
207
+ }
208
+ try {
209
+ junit 'results.xml'
210
+ } catch (Exception e) {
211
+ println 'Exception occurred: ' + e.toString()
212
+ }
213
+ println 'sending Failure Build notification'
214
+ CUSTOM_SUBJECT = '[CI - Jenkinzz FAILURE] ' + CUSTOM_SUBJECT
215
+ emailext(
216
+ mimeType: 'text/html',
217
+ subject: CUSTOM_SUBJECT,
218
+ body: '${DEFAULT_CONTENT}',
219
+ replyTo: '${DEFAULT_REPLYTO}',
220
+ to: '${ADMIN_RECIPIENTS}' + ';' + CUSTOM_RECIPIENTS
221
+ )
222
+ }
223
+ }
224
+ }
225
+ }
226
+ // triggered when black sign
227
+ aborted {
228
+ println 'post-declarative message: abort job'
229
+ }
230
+ // trigger every-works
231
+ //always {
232
+ //}
233
+ }
234
+ }
235
+
236
+ // return FLIT_USERNAME from given file
237
+ def getUserName(path) {
238
+ USERNAME = sh(
239
+ script: "grep FLIT_USERNAME ${path}|cut -d '=' -f2",
240
+ returnStdout: true
241
+ ).trim()
242
+ return USERNAME
243
+ }
244
+
245
+ // return FLIT_PASSWORD from given file
246
+ def getUserPass(path) {
247
+ USERPASS = sh(
248
+ script: "grep FLIT_PASSWORD ${path}|cut -d '=' -f2",
249
+ returnStdout: true
250
+ ).trim()
251
+ return USERPASS
252
+ }
253
+
254
+ // create/remove emailNotif file to trigger email notification
255
+ def switchEmailNotif(toggle, build) {
256
+ if (toggle) {
257
+ sh 'echo ' + build + ' > .emailNotif'
258
+ } else {
259
+ if (build == BUILD_NUMBER) {
260
+ sh 'rm -f .emailNotif'
261
+ }
262
+ }
263
+ }
264
+
265
+ // return true if emailNotif file present
266
+ boolean sendEmailNotif(path, build) {
267
+ emailNotif = sh(
268
+ script: "find ${path} -name '.emailNotif'|wc -l",
269
+ returnStdout: true
270
+ ).trim()
271
+ emailContent = ''
272
+ if (emailNotif == '1') {
273
+ emailContent = sh(
274
+ script: "cat ${path}/.emailNotif",
275
+ returnStdout: true
276
+ ).trim()
277
+ }
278
+ return (emailContent == build)
279
+ }
280
+
281
+ def analyseBuildCause() {
282
+ upstreamProjects = ['pymultirole_plugins']
283
+ boolean upstreamRunning = false
284
+ String jobName
285
+ // iterate over upstreamProjects
286
+ for (upstream_project in upstreamProjects) {
287
+ Jenkins.instance.getItemByFullName(upstream_project).items.each { repository ->
288
+ boolean isRunning = false
289
+ //repository.parent.name: project
290
+ //repository.name: branch
291
+ if ( repository.name == BRANCH_NAME ) {
292
+ // iterate over all jobs of current repository
293
+ repository.allJobs.each { job ->
294
+ // iterate over all builds of current job
295
+ job.builds.each { build ->
296
+ // determine if a build is running or not
297
+ if ( build.result == (null) ) {
298
+ jobName = build.parent.parent.name
299
+ isRunning = true
300
+ }
301
+ }
302
+ if ( isRunning ) {
303
+ upstreamRunning = true
304
+ }
305
+ }
306
+ }
307
+ }
308
+ }
309
+
310
+ // Catch if build has been triggered by CI Commit
311
+ // returnStatus = true when string not found -> Team commit
312
+ // returnStatus = false when string is found -> CI commit
313
+ boolean lastCommitIsTeam = sh(
314
+ script: 'git log -1 | grep "\\[Jenkins CI\\]"',
315
+ returnStatus: true
316
+ )
317
+
318
+ // Skip build when upstream detected
319
+ if (upstreamRunning) {
320
+ println 'Skipping build because upstream job detected (' + jobName + ')'
321
+ env.SKIP_JOB = '1'
322
+ switchEmailNotif(false, 0)
323
+ currentBuild.result = 'NOT_BUILT'
324
+ }
325
+
326
+ // Catch if build has been triggered by User
327
+ boolean isStartedByUser = currentBuild.rawBuild.getCause(hudson.model.Cause$UserIdCause) != null
328
+ if (isStartedByUser && !upstreamRunning) {
329
+ env.SKIP_JOB = '0'
330
+ env.CUSTOM_SUBJECT = JOB_NAME + ' - Manual Build #' + BUILD_NUMBER
331
+ env.CUSTOM_RECIPIENTS = emailextrecipients([[$class: 'RequesterRecipientProvider']])
332
+ switchEmailNotif(true, BUILD_NUMBER)
333
+ println 'Job started by User, proceeding'
334
+ }
335
+
336
+ // Catch if build has been triggered by Upstream
337
+ boolean isStartedByUpstream = currentBuild.rawBuild.getCause(hudson.model.Cause$UpstreamCause) != null
338
+ if (isStartedByUpstream && !upstreamRunning) {
339
+ int changeSetCount = 0
340
+ int ciSkipCount = 0
341
+ String upstreamFullJobName = ''
342
+ for (Run upstreamBuild : currentBuild.upstreamBuilds) {
343
+ upstreamFullJobName = upstreamBuild.rawBuild.fullDisplayName
344
+ if (upstreamBuild.changeSets != null) {
345
+ def changeLogSets = upstreamBuild.changeSets
346
+ for (int i = 0; i < changeLogSets.size(); i++) {
347
+ changeSetCount++
348
+ def entries = changeLogSets[i].items
349
+ for (int j = 0; j < entries.length; j++) {
350
+ def entry = entries[j]
351
+ if (entry.msg.contains('[Jenkins CI]')) {
352
+ ciSkipCount++
353
+ }
354
+ }
355
+ }
356
+ }
357
+ }
358
+ if (changeSetCount > 0 && changeSetCount == ciSkipCount) {
359
+ env.SKIP_JOB = '1'
360
+ switchEmailNotif(false, 0)
361
+ println 'Job started by Upstream [' + upstreamFullJobName + '], with CI commit, skipping'
362
+ currentBuild.result = 'NOT_BUILT'
363
+ } else {
364
+ env.SKIP_JOB = '0'
365
+ env.CUSTOM_SUBJECT = JOB_NAME + ' - Upstream Build #' + BUILD_NUMBER
366
+ env.CUSTOM_RECIPIENTS = emailextrecipients([[$class:'UpstreamComitterRecipientProvider']])
367
+ switchEmailNotif(true, BUILD_NUMBER)
368
+ println 'Job started by Upstream [' + upstreamFullJobName + '], proceeding'
369
+ }
370
+ }
371
+
372
+ // Catch if build has been triggered by User Commit
373
+ boolean isStartedByCommit = currentBuild.rawBuild.getCause(jenkins.branch.BranchEventCause) != null
374
+ if (isStartedByCommit && lastCommitIsTeam && !upstreamRunning) {
375
+ env.SKIP_JOB = '0'
376
+ env.CUSTOM_SUBJECT = JOB_NAME + ' - SCM Build #' + BUILD_NUMBER
377
+ env.CUSTOM_RECIPIENTS = emailextrecipients([[$class: 'DevelopersRecipientProvider'], [$class:'CulpritsRecipientProvider']])
378
+ switchEmailNotif(true, BUILD_NUMBER)
379
+ println 'Job started by User Commit, proceeding'
380
+ }
381
+
382
+ // Catch if build has been triggered by cron
383
+ boolean isStartedByCron = currentBuild.rawBuild.getCause(hudson.triggers.TimerTrigger$TimerTriggerCause) != null
384
+ if (isStartedByCron && lastCommitIsTeam && !upstreamRunning) {
385
+ env.SKIP_JOB = '0'
386
+ env.CUSTOM_SUBJECT = JOB_NAME + ' - CRON Build #' + BUILD_NUMBER
387
+ env.CUSTOM_RECIPIENTS = emailextrecipients([[$class: 'DevelopersRecipientProvider'], [$class:'CulpritsRecipientProvider']])
388
+ switchEmailNotif(true, BUILD_NUMBER)
389
+ println 'Job started by Cron, proceeding'
390
+ }
391
+
392
+ // Catch if build has been triggered by branch discovery
393
+ boolean isStartedByBranchDiscovery = currentBuild.rawBuild.getCause(jenkins.branch.BranchIndexingCause) != null
394
+ if (isStartedByBranchDiscovery && lastCommitIsTeam && !upstreamRunning) {
395
+ env.SKIP_JOB = '0'
396
+ env.CUSTOM_SUBJECT = JOB_NAME + ' - BranchDiscovery Build #' + BUILD_NUMBER
397
+ env.CUSTOM_RECIPIENTS = emailextrecipients([[$class: 'DevelopersRecipientProvider'], [$class:'CulpritsRecipientProvider']])
398
+ switchEmailNotif(true, BUILD_NUMBER)
399
+ println 'Job started by Branch Discovery, proceeding'
400
+ }
401
+
402
+ if (!lastCommitIsTeam && !upstreamRunning && !isStartedByUser && !isStartedByUpstream) {
403
+ println 'Skipping build because last commit has been done by CI'
404
+ env.SKIP_JOB = '1'
405
+ switchEmailNotif(false, 0)
406
+ //currentBuild.result = 'NOT_BUILT'
407
+ }
408
+ }
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.1
2
+ Name: pyconverters-openai_vision
3
+ Version: 0.5.2
4
+ Summary: OpenAIVision converter
5
+ Home-page: https://kairntech.com/
6
+ Author: Olivier Terrier
7
+ Author-email: olivier.terrier@kairntech.com
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Classifier: Intended Audience :: Information Technology
11
+ Classifier: Intended Audience :: System Administrators
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Topic :: Internet
16
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Classifier: Topic :: Software Development
20
+ Classifier: Typing :: Typed
21
+ Classifier: Development Status :: 4 - Beta
22
+ Classifier: Environment :: Web Environment
23
+ Classifier: Framework :: AsyncIO
24
+ Classifier: Intended Audience :: Developers
25
+ Classifier: Programming Language :: Python :: 3 :: Only
26
+ Classifier: Programming Language :: Python :: 3.8
27
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
28
+ Classifier: Topic :: Internet :: WWW/HTTP
29
+ Requires-Dist: pymultirole-plugins>=0.5.0,<0.6.0
30
+ Requires-Dist: openai==1.9.0
31
+ Requires-Dist: Jinja2
32
+ Requires-Dist: tenacity
33
+ Requires-Dist: log-with-context
34
+ Requires-Dist: StrEnum
35
+ Requires-Dist: filetype==1.0.13
36
+ Requires-Dist: requests
37
+ Requires-Dist: flit ; extra == "dev"
38
+ Requires-Dist: pre-commit ; extra == "dev"
39
+ Requires-Dist: bump2version ; extra == "dev"
40
+ Requires-Dist: sphinx ; extra == "docs"
41
+ Requires-Dist: sphinx-rtd-theme ; extra == "docs"
42
+ Requires-Dist: m2r2 ; extra == "docs"
43
+ Requires-Dist: sphinxcontrib.apidoc ; extra == "docs"
44
+ Requires-Dist: jupyter_sphinx ; extra == "docs"
45
+ Requires-Dist: pytest>=7.0 ; extra == "test"
46
+ Requires-Dist: pytest-cov ; extra == "test"
47
+ Requires-Dist: pytest-flake8 ; extra == "test"
48
+ Requires-Dist: pytest-black ; extra == "test"
49
+ Requires-Dist: flake8==3.9.2 ; extra == "test"
50
+ Requires-Dist: tox ; extra == "test"
51
+ Requires-Dist: dirty-equals ; extra == "test"
52
+ Requires-Dist: werkzeug==2.0.0 ; extra == "test"
53
+ Requires-Dist: flask==2.1.3 ; extra == "test"
54
+ Provides-Extra: dev
55
+ Provides-Extra: docs
56
+ Provides-Extra: test
57
+
58
+ ## Requirements
59
+
60
+ - Python 3.8+
61
+ - Flit to put Python packages and modules on PyPI
62
+ - Pydantic for the data parts.
63
+
64
+ ## Installation
65
+ ```
66
+ pip install flit
67
+ pip install pymultirole-plugins
68
+ ```
69
+
70
+ ## Publish the Python Package to PyPI
71
+ - Increment the version of your package in the `__init__.py` file:
72
+ ```
73
+ """An amazing package!"""
74
+
75
+ __version__ = 'x.y.z'
76
+ ```
77
+ - Publish
78
+ ```
79
+ flit publish
80
+ ```
81
+
@@ -0,0 +1,23 @@
1
+ ## Requirements
2
+
3
+ - Python 3.8+
4
+ - Flit to put Python packages and modules on PyPI
5
+ - Pydantic for the data parts.
6
+
7
+ ## Installation
8
+ ```
9
+ pip install flit
10
+ pip install pymultirole-plugins
11
+ ```
12
+
13
+ ## Publish the Python Package to PyPI
14
+ - Increment the version of your package in the `__init__.py` file:
15
+ ```
16
+ """An amazing package!"""
17
+
18
+ __version__ = 'x.y.z'
19
+ ```
20
+ - Publish
21
+ ```
22
+ flit publish
23
+ ```
@@ -0,0 +1,41 @@
1
+ import re
2
+ import sys
3
+ from pathlib import Path
4
+
5
+
6
+ def main(argv):
7
+ part = argv[0].lower() if len(argv) > 0 else "minor"
8
+ Jenkinsfile = Path("./Jenkinsfile")
9
+ pyprojectfile = Path("./pyproject.toml")
10
+ with Jenkinsfile.open("r", encoding="utf-8") as fin:
11
+ data = fin.read()
12
+ for line in data.split(fin.newlines):
13
+ if "MAJOR_VERSION" in line:
14
+ result = re.search(r'MAJOR_VERSION\s*=\s*"([0-9]+)"', line)
15
+ if result:
16
+ major = int(result.group(1))
17
+ if part == "major":
18
+ to_replace = result.group(0)
19
+ by_replace = f'MAJOR_VERSION = "{major + 1}"'
20
+ if "MINOR_VERSION" in line:
21
+ result = re.search(r'MINOR_VERSION\s*=\s*"([0-9]+)"', line)
22
+ if result:
23
+ minor = int(result.group(1))
24
+ if part == "minor":
25
+ to_replace = result.group(0)
26
+ by_replace = f'MINOR_VERSION = "{minor + 1}"'
27
+ data = data.replace(to_replace, by_replace)
28
+ with Jenkinsfile.open("wt") as fout:
29
+ fout.write(data)
30
+
31
+ depend_string = f"{major}.{minor}.0,<{major}.{minor + 1}.0"
32
+ new_depend_string = f"{major}.{minor + 1}.0,<{major}.{minor + 2}.0"
33
+ with pyprojectfile.open("r", encoding="utf-8") as fin:
34
+ data = fin.read()
35
+ data = data.replace(depend_string, new_depend_string)
36
+ with pyprojectfile.open("wt") as fout:
37
+ fout.write(data)
38
+
39
+
40
+ if __name__ == "__main__":
41
+ main(sys.argv[1:])
@@ -0,0 +1,2 @@
1
+ """OpenAIVision converter"""
2
+ __version__ = "0.5.2"
@@ -0,0 +1,117 @@
1
+ import os
2
+ from logging import Logger
3
+
4
+ import requests
5
+ from openai import OpenAI
6
+ from openai.lib.azure import AzureOpenAI
7
+ from pymultirole_plugins.util import comma_separated_to_list
8
+ from strenum import StrEnum
9
+
10
+ logger = Logger("pymultirole")
11
+ DEFAULT_CHAT_GPT_MODEL = "gpt-4o-mini"
12
+
13
+
14
+ # Now use default retry with backoff of openai api
15
+ def openai_chat_completion(prefix, **kwargs):
16
+ client = set_openai(prefix)
17
+ response = client.chat.completions.create(**kwargs)
18
+ return response
19
+
20
+
21
+ def openai_list_models(prefix, **kwargs):
22
+ def sort_by_created(x):
23
+ if 'created' in x:
24
+ return x['created']
25
+ elif 'created_at' in x:
26
+ return x['created_at']
27
+ elif 'deprecated' in x:
28
+ return x['deprecated'] or 9999999999
29
+ else:
30
+ return x.id
31
+
32
+ models = []
33
+ client = set_openai(prefix)
34
+ if prefix.startswith("DEEPINFRA"):
35
+ deepinfra_url = client.base_url
36
+ deepinfra_models = {}
37
+ public_models_list_url = f"{deepinfra_url.scheme}://{deepinfra_url.host}/models/list"
38
+ response = requests.get(public_models_list_url,
39
+ headers={'Accept': "application/json", 'Authorization': f"Bearer {client.api_key}"})
40
+ if response.ok:
41
+ resp = response.json()
42
+ mods = sorted(resp, key=sort_by_created, reverse=True)
43
+ mods = list(
44
+ {m['model_name'] for m in mods if m['type'] == 'text-generation'})
45
+ deepinfra_models.update({m: m for m in mods})
46
+
47
+ private_models_list_url = f"{deepinfra_url.scheme}://{deepinfra_url.host}/models/private/list"
48
+ response = requests.get(private_models_list_url,
49
+ headers={'Accept': "application/json", 'Authorization': f"Bearer {client.api_key}"})
50
+ if response.ok:
51
+ resp = response.json()
52
+ mods = sorted(resp, key=sort_by_created, reverse=True)
53
+ mods = list(
54
+ {m['model_name'] for m in mods if m['type'] == 'text-generation'})
55
+ deepinfra_models.update({m: m for m in mods})
56
+
57
+ deployed_models_list_url = f"{deepinfra_url.scheme}://{deepinfra_url.host}/deploy/list/"
58
+ response = requests.get(deployed_models_list_url,
59
+ headers={'Accept': "application/json", 'Authorization': f"Bearer {client.api_key}"})
60
+ if response.ok:
61
+ resp = response.json()
62
+ mods = sorted(resp, key=sort_by_created, reverse=True)
63
+ mods = list(
64
+ {m['model_name'] for m in mods if m['task'] == 'text-generation' and m['status'] == 'running'})
65
+ deepinfra_models.update({m: m for m in mods})
66
+ models = [m for m in deepinfra_models.keys() if 'vision' in m.lower()]
67
+ elif prefix.startswith("AZURE"):
68
+ models = comma_separated_to_list(os.getenv(prefix + "OPENAI_DEPLOYMENT_ID", None))
69
+ else:
70
+ response = client.models.list(**kwargs)
71
+ models = sorted(response.data, key=sort_by_created, reverse=True)
72
+ models = [m.id for m in models]
73
+ return models
74
+
75
+
76
+ def set_openai(prefix):
77
+ if prefix.startswith("AZURE"):
78
+ client = AzureOpenAI(
79
+ # This is the default and can be omitted
80
+ api_key=os.getenv(prefix + "OPENAI_API_KEY"),
81
+ azure_endpoint=os.getenv(prefix + "OPENAI_API_BASE", None),
82
+ api_version=os.getenv(prefix + "OPENAI_API_VERSION", None),
83
+ # azure_deployment=os.getenv(prefix + "OPENAI_DEPLOYMENT_ID", None)
84
+ )
85
+ else:
86
+ client = OpenAI(
87
+ # This is the default and can be omitted
88
+ api_key=os.getenv(prefix + "OPENAI_API_KEY"),
89
+ base_url=os.getenv(prefix + "OPENAI_API_BASE", None)
90
+ )
91
+ return client
92
+
93
+
94
+ def gpt_filter(m: str):
95
+ return m.startswith('gpt') and not m.startswith('gpt-3.5-turbo-instruct') and 'vision' not in m
96
+
97
+
98
+ NO_DEPLOYED_MODELS = 'no deployed models - check API key'
99
+
100
+
101
+ def create_openai_model_enum(name, prefix="", key=lambda m: m):
102
+ chat_gpt_models = []
103
+ default_chat_gpt_model = None
104
+ try:
105
+ chat_gpt_models = [m for m in openai_list_models(prefix) if key(m)]
106
+ if chat_gpt_models:
107
+ default_chat_gpt_model = DEFAULT_CHAT_GPT_MODEL if DEFAULT_CHAT_GPT_MODEL in chat_gpt_models else \
108
+ chat_gpt_models[0]
109
+ except BaseException:
110
+ logger.warning("Can't list models from endpoint", exc_info=True)
111
+
112
+ if len(chat_gpt_models) == 0:
113
+ chat_gpt_models = [NO_DEPLOYED_MODELS]
114
+ models = [("".join([c if c.isalnum() else "_" for c in m]), m) for m in chat_gpt_models]
115
+ model_enum = StrEnum(name, dict(models))
116
+ default_chat_gpt_model = model_enum(default_chat_gpt_model) if default_chat_gpt_model is not None else None
117
+ return model_enum, default_chat_gpt_model
@@ -0,0 +1,230 @@
1
+ import base64
2
+ import os
3
+ from enum import Enum
4
+ from logging import Logger
5
+ from typing import List, cast, Type, Dict, Any
6
+
7
+ import filetype as filetype
8
+ from pydantic import Field, BaseModel
9
+ from pymultirole_plugins.v1.converter import ConverterParameters, ConverterBase
10
+ from pymultirole_plugins.v1.schema import Document
11
+ from starlette.datastructures import UploadFile
12
+
13
+ from .openai_utils import NO_DEPLOYED_MODELS, \
14
+ openai_chat_completion, create_openai_model_enum
15
+
16
+ logger = Logger("pymultirole")
17
+
18
+
19
+ class OpenAIVisionBaseParameters(ConverterParameters):
20
+ model_str: str = Field(
21
+ None, extra="internal"
22
+ )
23
+ model: str = Field(
24
+ None, extra="internal"
25
+ )
26
+ prompt: str = Field(
27
+ "Describe the image with a lot of details",
28
+ description="""Contains the prompt as a string""",
29
+ extra="multiline",
30
+ )
31
+ max_tokens: int = Field(
32
+ 256,
33
+ description="""The maximum number of tokens to generate in the completion.
34
+ The token count of your prompt plus max_tokens cannot exceed the model's context length.
35
+ Most models have a context length of 2048 tokens (except for the newest models, which support 4096).""",
36
+ )
37
+ system_prompt: str = Field(
38
+ None,
39
+ description="""Contains the system prompt""",
40
+ extra="multiline,advanced",
41
+ )
42
+ temperature: float = Field(
43
+ 1.0,
44
+ description="""What sampling temperature to use, between 0 and 2.
45
+ Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.
46
+ We generally recommend altering this or `top_p` but not both.""",
47
+ extra="advanced",
48
+ )
49
+ top_p: int = Field(
50
+ 1,
51
+ description="""An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass.
52
+ So 0.1 means only the tokens comprising the top 10% probability mass are considered.
53
+ We generally recommend altering this or `temperature` but not both.""",
54
+ extra="advanced",
55
+ )
56
+ n: int = Field(
57
+ 1,
58
+ description="""How many completions to generate for each prompt.
59
+ Note: Because this parameter generates many completions, it can quickly consume your token quota.
60
+ Use carefully and ensure that you have reasonable settings for `max_tokens`.""",
61
+ extra="advanced",
62
+ )
63
+ best_of: int = Field(
64
+ 1,
65
+ description="""Generates best_of completions server-side and returns the "best" (the one with the highest log probability per token).
66
+ Results cannot be streamed.
67
+ When used with `n`, `best_of` controls the number of candidate completions and `n` specifies how many to return – `best_of` must be greater than `n`.
68
+ Use carefully and ensure that you have reasonable settings for `max_tokens`.""",
69
+ extra="advanced",
70
+ )
71
+ presence_penalty: float = Field(
72
+ 0.0,
73
+ description="""Number between -2.0 and 2.0.
74
+ Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.""",
75
+ extra="advanced",
76
+ )
77
+ frequency_penalty: float = Field(
78
+ 0.0,
79
+ description="""Number between -2.0 and 2.0.
80
+ Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.""",
81
+ extra="advanced",
82
+ )
83
+
84
+
85
+ class OpenAIVisionModel(str, Enum):
86
+ gpt_4o_mini = "gpt-4o-mini"
87
+ gpt_4o = "gpt-4o"
88
+
89
+
90
+ class OpenAIVisionParameters(OpenAIVisionBaseParameters):
91
+ model: OpenAIVisionModel = Field(
92
+ OpenAIVisionModel.gpt_4o_mini,
93
+ description="""The [OpenAI model](https://platform.openai.com/docs/models) used for speech to text transcription. Options currently available:</br>
94
+ <li>`whisper-1` - state-of-the-art open source large-v2 Whisper model.
95
+ """, extra="pipeline-naming-hint"
96
+ )
97
+
98
+
99
+ DEEPINFRA_PREFIX = "DEEPINFRA_"
100
+ DEEPINFRA_VISION_MODEL_ENUM, DEEPINFRA_DEFAULT_VISION_MODEL = create_openai_model_enum('DeepInfraVisionModel',
101
+ prefix=DEEPINFRA_PREFIX)
102
+
103
+
104
+ class DeepInfraOpenAIVisionParameters(OpenAIVisionBaseParameters):
105
+ model: DEEPINFRA_VISION_MODEL_ENUM = Field(
106
+ None,
107
+ description="""The [DeepInfra 'OpenAI compatible' model](https://deepinfra.com/models?type=automatic-speech-recognition) used for speech to text transcription. It must be deployed on your [DeepInfra dashboard](https://deepinfra.com/dash).
108
+ """, extra="pipeline-naming-hint"
109
+ )
110
+
111
+
112
+ # AZURE_PREFIX = "AZURE_"
113
+ #
114
+ #
115
+ # class AzureOpenAIVisionParameters(OpenAIVisionBaseParameters):
116
+ # model: OpenAIVisionModel = Field(
117
+ # OpenAIVisionModel.whisper_1,
118
+ # description="""The [Azure OpenAI model](https://platform.openai.com/docs/models) used for speech to text transcription. Options currently available:</br>
119
+ # <li>`whisper-1` - state-of-the-art open source large-v2 Whisper model.
120
+ # """, extra="pipeline-naming-hint"
121
+ # )
122
+
123
+
124
+ class OpenAIVisionConverterBase(ConverterBase):
125
+ __doc__ = """Generate text using [OpenAI Text Completion](https://platform.openai.com/docs/guides/completion) API
126
+ You input some text as a prompt, and the model will generate a text completion that attempts to match whatever context or pattern you gave it."""
127
+ PREFIX: str = ""
128
+
129
+ def compute_args(self, params: OpenAIVisionBaseParameters, source: UploadFile
130
+ ) -> Dict[str, Any]:
131
+ data = source.file.read()
132
+ rv = base64.b64encode(data)
133
+ messages = [{"role": "system", "content": params.system_prompt}] if params.system_prompt is not None else []
134
+ messages.append({"role": "user",
135
+ "content": [
136
+ {
137
+ "type": "text",
138
+ "text": params.prompt
139
+ },
140
+ {
141
+ "type": "image_url",
142
+ "image_url": {
143
+ "url": f"data:image/jpeg;base64,{rv.decode('utf-8')}"
144
+ }
145
+ }]})
146
+ kwargs = {
147
+ 'model': params.model_str,
148
+ 'messages': messages,
149
+ 'max_tokens': params.max_tokens,
150
+ 'temperature': params.temperature,
151
+ 'top_p': params.top_p,
152
+ 'n': params.n,
153
+ 'frequency_penalty': params.frequency_penalty,
154
+ 'presence_penalty': params.presence_penalty,
155
+ }
156
+ return kwargs
157
+
158
+ def compute_result(self, **kwargs):
159
+ response = openai_chat_completion(self.PREFIX, **kwargs)
160
+ contents = []
161
+ for choice in response.choices:
162
+ if choice.message.content:
163
+ contents.append(choice.message.content)
164
+ if contents:
165
+ result = "\n".join(contents)
166
+ return result
167
+
168
+ def convert(self, source: UploadFile, parameters: ConverterParameters) \
169
+ -> List[Document]:
170
+
171
+ params: OpenAIVisionBaseParameters = cast(
172
+ OpenAIVisionBaseParameters, parameters
173
+ )
174
+ OPENAI_MODEL = os.getenv(self.PREFIX + "OPENAI_MODEL", None)
175
+ if OPENAI_MODEL:
176
+ params.model_str = OPENAI_MODEL
177
+ doc = None
178
+ try:
179
+ kind = filetype.guess(source.file)
180
+ source.file.seek(0)
181
+ if kind.mime.startswith("image"):
182
+ result = None
183
+ kwargs = self.compute_args(params, source)
184
+ if kwargs['model'] != NO_DEPLOYED_MODELS:
185
+ result = self.compute_result(**kwargs)
186
+ if result:
187
+ doc = Document(identifier=source.filename, text=result)
188
+ doc.properties = {"fileName": source.filename}
189
+ except BaseException as err:
190
+ raise err
191
+ if doc is None:
192
+ raise TypeError(f"Conversion of audio file {source.filename} failed")
193
+ return [doc]
194
+
195
+ @classmethod
196
+ def get_model(cls) -> Type[BaseModel]:
197
+ return OpenAIVisionBaseParameters
198
+
199
+
200
+ class OpenAIVisionConverter(OpenAIVisionConverterBase):
201
+ __doc__ = """Convert audio using [OpenAI Audio](https://platform.openai.com/docs/guides/speech-to-text) API"""
202
+
203
+ def convert(self, source: UploadFile, parameters: ConverterParameters) \
204
+ -> List[Document]:
205
+ params: OpenAIVisionParameters = cast(
206
+ OpenAIVisionParameters, parameters
207
+ )
208
+ params.model_str = params.model.value
209
+ return super().convert(source, params)
210
+
211
+ @classmethod
212
+ def get_model(cls) -> Type[BaseModel]:
213
+ return OpenAIVisionParameters
214
+
215
+
216
+ class DeepInfraOpenAIVisionConverter(OpenAIVisionConverterBase):
217
+ __doc__ = """Convert images using [DeepInfra Vision](https://deepinfra.com/docs/tutorials/whisper) API"""
218
+ PREFIX = DEEPINFRA_PREFIX
219
+
220
+ def convert(self, source: UploadFile, parameters: ConverterParameters) \
221
+ -> List[Document]:
222
+ params: DeepInfraOpenAIVisionParameters = cast(
223
+ DeepInfraOpenAIVisionParameters, parameters
224
+ )
225
+ params.model_str = params.model.value
226
+ return super().convert(source, params)
227
+
228
+ @classmethod
229
+ def get_model(cls) -> Type[BaseModel]:
230
+ return DeepInfraOpenAIVisionParameters
@@ -0,0 +1,103 @@
1
+ [build-system]
2
+ requires = ["flit_core >=2,<4"]
3
+ build-backend = "flit_core.buildapi"
4
+
5
+ [tool.flit.metadata]
6
+ module = "pyconverters_openai_vision"
7
+ author = "Olivier Terrier"
8
+ author-email = "olivier.terrier@kairntech.com"
9
+ home-page = "https://kairntech.com/"
10
+ classifiers = [
11
+ "Intended Audience :: Information Technology",
12
+ "Intended Audience :: System Administrators",
13
+ "Operating System :: OS Independent",
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python",
16
+ "Topic :: Internet",
17
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
18
+ "Topic :: Software Development :: Libraries :: Python Modules",
19
+ "Topic :: Software Development :: Libraries",
20
+ "Topic :: Software Development",
21
+ "Typing :: Typed",
22
+ "Development Status :: 4 - Beta",
23
+ "Environment :: Web Environment",
24
+ "Framework :: AsyncIO",
25
+ "Intended Audience :: Developers",
26
+ "Programming Language :: Python :: 3 :: Only",
27
+ "Programming Language :: Python :: 3.8",
28
+ "Topic :: Internet :: WWW/HTTP :: HTTP Servers",
29
+ "Topic :: Internet :: WWW/HTTP",
30
+ ]
31
+ requires = [
32
+ "pymultirole-plugins>=0.5.0,<0.6.0",
33
+ "openai==1.9.0",
34
+ "Jinja2",
35
+ "tenacity",
36
+ "log-with-context",
37
+ "StrEnum",
38
+ "filetype==1.0.13",
39
+ "requests",
40
+ ]
41
+ dist-name = "pyconverters-openai_vision"
42
+ description-file = "README.md"
43
+ requires-python = ">=3.8"
44
+
45
+ [tool.flit.entrypoints."pyconverters.plugins"]
46
+ openai_vision = "pyconverters_openai_vision.openai_vision:OpenAIVisionConverter"
47
+ deepinfra_openai_vision = "pyconverters_openai_vision.openai_vision:DeepInfraOpenAIVisionConverter"
48
+ azure_openai_vision = "pyconverters_openai_vision.openai_vision:AzureOpenAIVisionConverter"
49
+
50
+
51
+ [tool.flit.metadata.requires-extra]
52
+ test = [
53
+ "pytest>=7.0",
54
+ "pytest-cov",
55
+ "pytest-flake8",
56
+ "pytest-black",
57
+ # "hypothesis",
58
+ "flake8==3.9.2",
59
+ "tox",
60
+ "dirty-equals",
61
+ "werkzeug==2.0.0",
62
+ "flask==2.1.3"
63
+ ]
64
+ docs = [
65
+ "sphinx",
66
+ "sphinx-rtd-theme",
67
+ "m2r2", # markdown support
68
+ "sphinxcontrib.apidoc", # run sphinx-apidoc when building docs
69
+ "jupyter_sphinx", # for execution of code snippets in the documentation
70
+ ]
71
+ dev = [
72
+ "flit",
73
+ "pre-commit",
74
+ "bump2version",
75
+ ]
76
+
77
+ [tool.flakehell]
78
+ exclude = ["README.md"]
79
+ format = "colored"
80
+ #format = "junit-xml"
81
+ max_line_length = 120
82
+ show_source = true
83
+ #whitelist = "../../allowlist.txt"
84
+ extended_default_ignore = []
85
+
86
+ [tool.flakehell.plugins]
87
+ flake8-bandit = ["+*", "-S322"]
88
+ flake8-bugbear = ["+*"]
89
+ flake8-builtins = ["+*", "-A003"]
90
+ flake8-comprehensions = ["+*"]
91
+ #flake8-darglint = ["+*"]
92
+ flake8-docstrings = ["+*"]
93
+ flake8-eradicate = ["+*"]
94
+ flake8-isort = ["+*"]
95
+ flake8-mutable = ["+*"]
96
+ flake8-pytest-style = ["+*"]
97
+ flake8-spellcheck = ["+*"]
98
+ mccabe = ["+*"]
99
+ pep8-naming = ["+*"]
100
+ pycodestyle = ["+*"]
101
+ pyflakes = ["+*"]
102
+ pylint = ["+*"]
103
+
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env python
2
+ # setup.py generated by flit for tools that don't yet use PEP 517
3
+
4
+ from distutils.core import setup
5
+
6
+ packages = \
7
+ ['pyconverters_openai_vision']
8
+
9
+ package_data = \
10
+ {'': ['*']}
11
+
12
+ install_requires = \
13
+ ['pymultirole-plugins>=0.5.0,<0.6.0',
14
+ 'openai==1.9.0',
15
+ 'Jinja2',
16
+ 'tenacity',
17
+ 'log-with-context',
18
+ 'StrEnum',
19
+ 'filetype==1.0.13',
20
+ 'requests']
21
+
22
+ extras_require = \
23
+ {'dev': ['flit', 'pre-commit', 'bump2version'],
24
+ 'docs': ['sphinx',
25
+ 'sphinx-rtd-theme',
26
+ 'm2r2',
27
+ 'sphinxcontrib.apidoc',
28
+ 'jupyter_sphinx'],
29
+ 'test': ['pytest>=7.0',
30
+ 'pytest-cov',
31
+ 'pytest-flake8',
32
+ 'pytest-black',
33
+ 'flake8==3.9.2',
34
+ 'tox',
35
+ 'dirty-equals',
36
+ 'werkzeug==2.0.0',
37
+ 'flask==2.1.3']}
38
+
39
+ entry_points = \
40
+ {'pyconverters.plugins': ['azure_openai_vision = '
41
+ 'pyconverters_openai_vision.openai_vision:AzureOpenAIVisionConverter',
42
+ 'deepinfra_openai_vision = '
43
+ 'pyconverters_openai_vision.openai_vision:DeepInfraOpenAIVisionConverter',
44
+ 'openai_vision = '
45
+ 'pyconverters_openai_vision.openai_vision:OpenAIVisionConverter']}
46
+
47
+ setup(name='pyconverters-openai_vision',
48
+ version='0.5.2',
49
+ description='OpenAIVision converter',
50
+ author='Olivier Terrier',
51
+ author_email='olivier.terrier@kairntech.com',
52
+ url='https://kairntech.com/',
53
+ packages=packages,
54
+ package_data=package_data,
55
+ install_requires=install_requires,
56
+ extras_require=extras_require,
57
+ entry_points=entry_points,
58
+ python_requires='>=3.8',
59
+ )
File without changes
@@ -0,0 +1,57 @@
1
+ from pathlib import Path
2
+ from typing import List
3
+
4
+ import pytest
5
+ from pymultirole_plugins.v1.schema import Document
6
+ from starlette.datastructures import UploadFile
7
+
8
+ from pyconverters_openai_vision.openai_vision import (
9
+ OpenAIVisionConverter,
10
+ OpenAIVisionParameters, DeepInfraOpenAIVisionParameters, DeepInfraOpenAIVisionConverter
11
+ )
12
+
13
+
14
+ def test_openai_vision_basic():
15
+ model = OpenAIVisionConverter.get_model()
16
+ model_class = model.construct().__class__
17
+ assert model_class == OpenAIVisionParameters
18
+
19
+
20
+ @pytest.mark.skip(reason="Not a test")
21
+ def test_openai():
22
+ converter = OpenAIVisionConverter()
23
+ parameters = OpenAIVisionParameters()
24
+ testdir = Path(__file__).parent
25
+ source = Path(testdir, 'data/colducoq.jpg')
26
+ with source.open("rb") as fin:
27
+ docs: List[Document] = converter.convert(UploadFile(source.name, fin, 'image/jpeg'), parameters)
28
+ assert len(docs) == 1
29
+ doc0 = docs[0]
30
+ assert 'dent de crolles' in doc0.text.lower()
31
+
32
+ source = Path(testdir, 'data/webinar.png')
33
+ with source.open("rb") as fin:
34
+ docs: List[Document] = converter.convert(UploadFile(source.name, fin, 'image/png'), parameters)
35
+ assert len(docs) == 1
36
+ doc0 = docs[0]
37
+ assert 'kairntech' in doc0.text.lower()
38
+
39
+
40
+ @pytest.mark.skip(reason="Not a test")
41
+ def test_deepinfra():
42
+ converter = DeepInfraOpenAIVisionConverter()
43
+ parameters = DeepInfraOpenAIVisionParameters(model="meta-llama/Llama-3.2-11B-Vision-Instruct")
44
+ testdir = Path(__file__).parent
45
+ source = Path(testdir, 'data/colducoq.jpg')
46
+ with source.open("rb") as fin:
47
+ docs: List[Document] = converter.convert(UploadFile(source.name, fin, 'image/jpeg'), parameters)
48
+ assert len(docs) == 1
49
+ doc0 = docs[0]
50
+ assert 'dent de crolles' in doc0.text.lower()
51
+
52
+ source = Path(testdir, 'data/webinar.png')
53
+ with source.open("rb") as fin:
54
+ docs: List[Document] = converter.convert(UploadFile(source.name, fin, 'image/png'), parameters)
55
+ assert len(docs) == 1
56
+ doc0 = docs[0]
57
+ assert 'generative ai' in doc0.text.lower()
@@ -0,0 +1,52 @@
1
+ [tox]
2
+ isolated_build = True
3
+ envlist = clean,py38,report
4
+
5
+ [gh-actions]
6
+ python =
7
+ 3.8: clean,py38,report
8
+
9
+ [pytest]
10
+ addopts =
11
+ --durations=5
12
+ --flake8
13
+ norecursedirs =
14
+ docs
15
+
16
+ filterwarnings =
17
+ ignore:<class '.*'> is not using a cooperative constructor:pytest.PytestDeprecationWarning
18
+ ignore:The \(fspath. py.path.local\) argument to .* is deprecated.:pytest.PytestDeprecationWarning
19
+ ignore:.* is an Item subclass and should not be a collector.*:pytest.PytestWarning
20
+
21
+ [testenv]
22
+ deps = .[test]
23
+ commands = pytest --junit-xml={env:TEST_REPORT_DIR:.}/{env:TEST_REPORT_FILE:results.xml}
24
+ passenv = *
25
+ depends =
26
+ report: py38
27
+
28
+ [testenv:docs]
29
+ changedir=docs
30
+ deps = .[docs]
31
+ commands = sphinx-build . _build
32
+
33
+ [flake8]
34
+ max-line-length = 140
35
+ ignore =
36
+ E131,
37
+ E126,
38
+ E203,
39
+ E266,
40
+ E501,
41
+ W503,
42
+ W291,
43
+ C901
44
+ per-file-ignores =
45
+ __init__.py: F401
46
+ max-complexity = 18
47
+ select = B, C, E, F, W, T4, B9
48
+ exclude =
49
+ .git,
50
+ .tox,
51
+ __pycache__,
52
+ dist,