pycall_thread 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 04ac373bef738633cd5d2746983182a216f6a5174a9b927709c5d133234d0a6c
4
+ data.tar.gz: 11e50545784a6d10755c489e1f95b2b23bcd9965356c3881a2dce7109d3bd6f2
5
+ SHA512:
6
+ metadata.gz: b67602e6888e458a64cd2874a540d9e7bba78cd8ff38e7642c2c1b1ef74da2e854890f6ce0cb139b0ad4b84c0c28d84b6209dcd4ad8a15a8027dbd6d1651084b
7
+ data.tar.gz: d8d8955f9b99a51cc8f3a80c81c39604945a3beafc4930dfa38f715144d7ca207aab778fc95ffcfa000bf6447224bce143463cbfd033481567cb86e477ad8a4a
data/.pdm-python ADDED
@@ -0,0 +1 @@
1
+ /Users/seth/src/tester/justpuma/.venv/bin/python
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.0.6
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in pycall_thread.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+ gem "minitest", "~> 5.0"
10
+ gem "rubocop", "~> 1.21"
11
+
12
+
13
+ # Include Puma for testing examples:
14
+ gem 'puma', '~> 6.4.1'
15
+
16
+ # To use the local path, you must also run things with:
17
+ # bundle exec ruby -I/Users/seth/src/pycall.rb/ext/pycall
18
+ #
19
+ # gem 'pycall', path: '/Users/seth/src/pycall.rb', require: false
data/Gemfile.lock ADDED
@@ -0,0 +1,56 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ pycall_thread (0.1.0)
5
+ pycall
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ json (2.7.2)
12
+ language_server-protocol (3.17.0.3)
13
+ minitest (5.24.1)
14
+ nio4r (2.7.3)
15
+ parallel (1.26.0)
16
+ parser (3.3.4.2)
17
+ ast (~> 2.4.1)
18
+ racc
19
+ puma (6.4.2)
20
+ nio4r (~> 2.0)
21
+ pycall (1.5.2)
22
+ racc (1.8.1)
23
+ rainbow (3.1.1)
24
+ rake (13.2.1)
25
+ regexp_parser (2.9.2)
26
+ rexml (3.3.4)
27
+ strscan
28
+ rubocop (1.65.1)
29
+ json (~> 2.3)
30
+ language_server-protocol (>= 3.17.0)
31
+ parallel (~> 1.10)
32
+ parser (>= 3.3.0.2)
33
+ rainbow (>= 2.2.2, < 4.0)
34
+ regexp_parser (>= 2.4, < 3.0)
35
+ rexml (>= 3.2.5, < 4.0)
36
+ rubocop-ast (>= 1.31.1, < 2.0)
37
+ ruby-progressbar (~> 1.7)
38
+ unicode-display_width (>= 2.4.0, < 3.0)
39
+ rubocop-ast (1.32.0)
40
+ parser (>= 3.3.1.0)
41
+ ruby-progressbar (1.13.0)
42
+ strscan (3.1.0)
43
+ unicode-display_width (2.5.0)
44
+
45
+ PLATFORMS
46
+ arm64-darwin-23
47
+
48
+ DEPENDENCIES
49
+ minitest (~> 5.0)
50
+ puma (~> 6.4.1)
51
+ pycall_thread!
52
+ rake (~> 13.0)
53
+ rubocop (~> 1.21)
54
+
55
+ BUNDLED WITH
56
+ 2.2.33
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # PyCallThread
2
+
3
+ PyCallThread provides `PyCallThread.run(&block)`, which lets you safely run a block of
4
+ [pycall.rb](https://github.com/mrkn/pycall.rb) code: even if you're in a thread.
5
+
6
+ This makes PyCall easier to use from Ruby on Rails, Puma and other threaded web servers.
7
+
8
+ ## Usage
9
+
10
+ ```
11
+ require 'pycall_thread'
12
+
13
+ # Initialization is optional but gives you a few config settings
14
+ PyCallThread.init do
15
+ # If you need to do anything to setup you venv, you can do it here
16
+ require 'pycall'
17
+ end
18
+
19
+ # We can safely call PyCall, even from a thread (or web request) using `PyCallThread.run`:
20
+ Thread.new do
21
+ data_table = PyCallThread.run do
22
+ pandas = PyCall.import('pandas')
23
+ data = pandas.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv', sep: ';')
24
+ data.head().to_string()
25
+ end
26
+ puts "Data is:"
27
+ puts data_table
28
+ end
29
+ ```
30
+
31
+ Examples of using PyCall with webservers:
32
+
33
+ - [Puma](./examples/puma)
34
+ - Ruby on Rails: todo
35
+
36
+ ## Installation
37
+
38
+ Gemfile:
39
+
40
+ ```ruby
41
+ gem 'pycall_thread'
42
+ ```
43
+
44
+ ## Development
45
+
46
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
47
+
48
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
49
+
50
+ TODO: make run `rake test` run the tests
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "pycall_thread"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,25 @@
1
+ require_relative '../../lib/pycall_thread/pycall_thread'
2
+
3
+ PyCallThread.init do
4
+ # Setup our local venv (using pdm, in .venv)
5
+ ENV['PYTHON'] = `pdm run which python`.strip
6
+ site_dir = `pdm run python -c 'import site; print(site.getsitepackages()[0])'`.strip
7
+
8
+ require 'pycall'
9
+
10
+ # This is to setup our local venv
11
+ site = PyCall.import_module('site')
12
+ site.addsitedir(site_dir)
13
+ end
14
+
15
+ class App
16
+ def call(env)
17
+ winequality = PyCallThread.run do
18
+ pandas = PyCall.import_module('pandas')
19
+ data = pandas.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv', sep: ';')
20
+ data.to_html()
21
+ end
22
+
23
+ [200, { 'Content-Type' => 'text/html' }, [winequality]]
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ require './app'
2
+
3
+ run App.new
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+
3
+ # Start Puma with 3 threads on port 9292
4
+ puma -t 3:3 -p 9292
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PycallThread
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,99 @@
1
+ module PyCallThread
2
+ @queue = Queue.new
3
+
4
+ VALID_UNSAFE_RETURN_VALUES = [:allow, :error, :warn]
5
+
6
+ def self.init(unsafe_return: :error, &require_pycall_block)
7
+ # Only safe to use PyCallThread if PyCall hasn't already been loaded
8
+ raise "PyCall::LibPython already exists: PyCall can't have been initialized already" if defined?(PyCall::LibPython)
9
+
10
+ @initialized = true
11
+
12
+ if VALID_UNSAFE_RETURN_VALUES.include?(unsafe_return)
13
+ @unsafe_return = unsafe_return
14
+ else
15
+ raise ArgumentError, "Invalid value for unsafe_return: #{unsafe_return}. Must be one of: #{VALID_UNSAFE_RETURN_VALUES.join(', ')}"
16
+ end
17
+
18
+ # Start the thread we will use to run code invoked with PyCallThread.run
19
+ # If we've been passed a require_pycall_block, use that to require 'pycall'
20
+ # instead of doing it directly.
21
+ @py_thread = Thread.new { pycall_thread_loop(&require_pycall_block) }
22
+
23
+ at_exit do
24
+ stop_pycall_thread
25
+ end
26
+
27
+ nil
28
+ end
29
+
30
+ # Runs &block on the PyCall thread, and returns the result
31
+ def self.run(&block)
32
+ init unless @initialized
33
+
34
+ result_queue = Queue.new
35
+ @queue << -> do
36
+ begin
37
+ result_queue << { retval: block.call }
38
+ rescue => e
39
+ result_queue << { exception: e }
40
+ end
41
+ end
42
+
43
+ result = result_queue.pop
44
+
45
+ if result[:exception]
46
+ raise result[:exception]
47
+ elsif python_object?(result[:retval])
48
+ msg = "Trying to return a python object from a PyCallThread.run block is potentially not thread-safe. Please convert #{result.inspect} to a basic Ruby type (like string, array, number, boolean etc) before returning."
49
+ case @unsafe_return
50
+ when :error
51
+ raise msg
52
+ when :warn
53
+ warn "Warning: #{msg}"
54
+ end
55
+ end
56
+
57
+ result[:retval]
58
+ end
59
+
60
+ def self.stop_pycall_thread
61
+ @queue << :stop
62
+ @py_thread.join
63
+ end
64
+
65
+ def self.pycall_thread_loop(&require_pycall_block)
66
+ Thread.current.name = "pycall"
67
+
68
+ # require 'pycall' or run a user-defined block that should do the same
69
+ if require_pycall_block
70
+ require_pycall_block.call
71
+ else
72
+ require 'pycall'
73
+ end
74
+
75
+ loop do
76
+ block = @queue.pop
77
+ break if block == :stop
78
+ block.call
79
+ rescue => e
80
+ puts "pycall_thread_loop(): exception in pycall_thread_loop #{e}"
81
+ puts e.backtrace.join("\n")
82
+ end
83
+
84
+ # If PyCall.finalize is not present, the main proces will hang at exit
85
+ # See: https://github.com/mrkn/pycall.rb/pull/187
86
+ PyCall.finalize if PyCall.respond_to?(:finalize)
87
+ end
88
+
89
+ def self.python_object?(obj)
90
+ [
91
+ PyCall::IterableWrapper,
92
+ PyCall::PyObjectWrapper,
93
+ PyCall::PyModuleWrapper,
94
+ PyCall::PyObjectWrapper,
95
+ PyCall::PyTypeObjectWrapper,
96
+ PyCall::PyPtr,
97
+ ].any? { |kind| obj.is_a?(kind) }
98
+ end
99
+ end
data/pdm.lock ADDED
@@ -0,0 +1,327 @@
1
+ # This file is @generated by PDM.
2
+ # It is not intended for manual editing.
3
+
4
+ [metadata]
5
+ groups = ["default", "dev"]
6
+ strategy = ["inherit_metadata"]
7
+ lock_version = "4.5.0"
8
+ content_hash = "sha256:1a7142df64b7d1c563d85942f53f3f3e7b5b37423c14d97a925cc35b3cdeb731"
9
+
10
+ [[metadata.targets]]
11
+ requires_python = "==3.12.*"
12
+
13
+ [[package]]
14
+ name = "asttokens"
15
+ version = "2.4.1"
16
+ summary = "Annotate AST trees with source code positions"
17
+ groups = ["dev"]
18
+ dependencies = [
19
+ "six>=1.12.0",
20
+ "typing; python_version < \"3.5\"",
21
+ ]
22
+ files = [
23
+ {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
24
+ {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
25
+ ]
26
+
27
+ [[package]]
28
+ name = "colorama"
29
+ version = "0.4.6"
30
+ requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
31
+ summary = "Cross-platform colored terminal text."
32
+ groups = ["dev"]
33
+ marker = "sys_platform == \"win32\""
34
+ files = [
35
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
36
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
37
+ ]
38
+
39
+ [[package]]
40
+ name = "decorator"
41
+ version = "5.1.1"
42
+ requires_python = ">=3.5"
43
+ summary = "Decorators for Humans"
44
+ groups = ["dev"]
45
+ files = [
46
+ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
47
+ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
48
+ ]
49
+
50
+ [[package]]
51
+ name = "executing"
52
+ version = "2.0.1"
53
+ requires_python = ">=3.5"
54
+ summary = "Get the currently executing AST node of a frame, and other information"
55
+ groups = ["dev"]
56
+ files = [
57
+ {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"},
58
+ {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"},
59
+ ]
60
+
61
+ [[package]]
62
+ name = "ipython"
63
+ version = "8.26.0"
64
+ requires_python = ">=3.10"
65
+ summary = "IPython: Productive Interactive Computing"
66
+ groups = ["dev"]
67
+ dependencies = [
68
+ "colorama; sys_platform == \"win32\"",
69
+ "decorator",
70
+ "exceptiongroup; python_version < \"3.11\"",
71
+ "jedi>=0.16",
72
+ "matplotlib-inline",
73
+ "pexpect>4.3; sys_platform != \"win32\" and sys_platform != \"emscripten\"",
74
+ "prompt-toolkit<3.1.0,>=3.0.41",
75
+ "pygments>=2.4.0",
76
+ "stack-data",
77
+ "traitlets>=5.13.0",
78
+ "typing-extensions>=4.6; python_version < \"3.12\"",
79
+ ]
80
+ files = [
81
+ {file = "ipython-8.26.0-py3-none-any.whl", hash = "sha256:e6b347c27bdf9c32ee9d31ae85defc525755a1869f14057e900675b9e8d6e6ff"},
82
+ {file = "ipython-8.26.0.tar.gz", hash = "sha256:1cec0fbba8404af13facebe83d04436a7434c7400e59f47acf467c64abd0956c"},
83
+ ]
84
+
85
+ [[package]]
86
+ name = "jedi"
87
+ version = "0.19.1"
88
+ requires_python = ">=3.6"
89
+ summary = "An autocompletion tool for Python that can be used for text editors."
90
+ groups = ["dev"]
91
+ dependencies = [
92
+ "parso<0.9.0,>=0.8.3",
93
+ ]
94
+ files = [
95
+ {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"},
96
+ {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"},
97
+ ]
98
+
99
+ [[package]]
100
+ name = "matplotlib-inline"
101
+ version = "0.1.7"
102
+ requires_python = ">=3.8"
103
+ summary = "Inline Matplotlib backend for Jupyter"
104
+ groups = ["dev"]
105
+ dependencies = [
106
+ "traitlets",
107
+ ]
108
+ files = [
109
+ {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"},
110
+ {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"},
111
+ ]
112
+
113
+ [[package]]
114
+ name = "numpy"
115
+ version = "1.26.4"
116
+ requires_python = ">=3.9"
117
+ summary = "Fundamental package for array computing in Python"
118
+ groups = ["default"]
119
+ files = [
120
+ {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"},
121
+ {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"},
122
+ {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"},
123
+ {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"},
124
+ {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"},
125
+ {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"},
126
+ {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"},
127
+ {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"},
128
+ {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"},
129
+ ]
130
+
131
+ [[package]]
132
+ name = "pandas"
133
+ version = "2.2.2"
134
+ requires_python = ">=3.9"
135
+ summary = "Powerful data structures for data analysis, time series, and statistics"
136
+ groups = ["default"]
137
+ dependencies = [
138
+ "numpy>=1.22.4; python_version < \"3.11\"",
139
+ "numpy>=1.23.2; python_version == \"3.11\"",
140
+ "numpy>=1.26.0; python_version >= \"3.12\"",
141
+ "python-dateutil>=2.8.2",
142
+ "pytz>=2020.1",
143
+ "tzdata>=2022.7",
144
+ ]
145
+ files = [
146
+ {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"},
147
+ {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"},
148
+ {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"},
149
+ {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"},
150
+ {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"},
151
+ {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"},
152
+ {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"},
153
+ {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"},
154
+ ]
155
+
156
+ [[package]]
157
+ name = "parso"
158
+ version = "0.8.4"
159
+ requires_python = ">=3.6"
160
+ summary = "A Python Parser"
161
+ groups = ["dev"]
162
+ files = [
163
+ {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"},
164
+ {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"},
165
+ ]
166
+
167
+ [[package]]
168
+ name = "pexpect"
169
+ version = "4.9.0"
170
+ summary = "Pexpect allows easy control of interactive console applications."
171
+ groups = ["dev"]
172
+ marker = "sys_platform != \"win32\" and sys_platform != \"emscripten\""
173
+ dependencies = [
174
+ "ptyprocess>=0.5",
175
+ ]
176
+ files = [
177
+ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
178
+ {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
179
+ ]
180
+
181
+ [[package]]
182
+ name = "prompt-toolkit"
183
+ version = "3.0.47"
184
+ requires_python = ">=3.7.0"
185
+ summary = "Library for building powerful interactive command lines in Python"
186
+ groups = ["dev"]
187
+ dependencies = [
188
+ "wcwidth",
189
+ ]
190
+ files = [
191
+ {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"},
192
+ {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"},
193
+ ]
194
+
195
+ [[package]]
196
+ name = "psutil"
197
+ version = "6.0.0"
198
+ requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
199
+ summary = "Cross-platform lib for process and system monitoring in Python."
200
+ groups = ["default"]
201
+ files = [
202
+ {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"},
203
+ {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"},
204
+ {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"},
205
+ {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"},
206
+ {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"},
207
+ {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"},
208
+ {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"},
209
+ {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"},
210
+ ]
211
+
212
+ [[package]]
213
+ name = "ptyprocess"
214
+ version = "0.7.0"
215
+ summary = "Run a subprocess in a pseudo terminal"
216
+ groups = ["dev"]
217
+ marker = "sys_platform != \"win32\" and sys_platform != \"emscripten\""
218
+ files = [
219
+ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
220
+ {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
221
+ ]
222
+
223
+ [[package]]
224
+ name = "pure-eval"
225
+ version = "0.2.3"
226
+ summary = "Safely evaluate AST nodes without side effects"
227
+ groups = ["dev"]
228
+ files = [
229
+ {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"},
230
+ {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"},
231
+ ]
232
+
233
+ [[package]]
234
+ name = "pygments"
235
+ version = "2.18.0"
236
+ requires_python = ">=3.8"
237
+ summary = "Pygments is a syntax highlighting package written in Python."
238
+ groups = ["dev"]
239
+ files = [
240
+ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
241
+ {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
242
+ ]
243
+
244
+ [[package]]
245
+ name = "python-dateutil"
246
+ version = "2.9.0.post0"
247
+ requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
248
+ summary = "Extensions to the standard Python datetime module"
249
+ groups = ["default"]
250
+ dependencies = [
251
+ "six>=1.5",
252
+ ]
253
+ files = [
254
+ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
255
+ {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
256
+ ]
257
+
258
+ [[package]]
259
+ name = "pytz"
260
+ version = "2024.1"
261
+ summary = "World timezone definitions, modern and historical"
262
+ groups = ["default"]
263
+ files = [
264
+ {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"},
265
+ {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"},
266
+ ]
267
+
268
+ [[package]]
269
+ name = "six"
270
+ version = "1.16.0"
271
+ requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
272
+ summary = "Python 2 and 3 compatibility utilities"
273
+ groups = ["default", "dev"]
274
+ files = [
275
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
276
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
277
+ ]
278
+
279
+ [[package]]
280
+ name = "stack-data"
281
+ version = "0.6.3"
282
+ summary = "Extract data from python stack frames and tracebacks for informative displays"
283
+ groups = ["dev"]
284
+ dependencies = [
285
+ "asttokens>=2.1.0",
286
+ "executing>=1.2.0",
287
+ "pure-eval",
288
+ ]
289
+ files = [
290
+ {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"},
291
+ {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"},
292
+ ]
293
+
294
+ [[package]]
295
+ name = "traitlets"
296
+ version = "5.14.3"
297
+ requires_python = ">=3.8"
298
+ summary = "Traitlets Python configuration system"
299
+ groups = ["dev"]
300
+ files = [
301
+ {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"},
302
+ {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"},
303
+ ]
304
+
305
+ [[package]]
306
+ name = "tzdata"
307
+ version = "2024.1"
308
+ requires_python = ">=2"
309
+ summary = "Provider of IANA time zone data"
310
+ groups = ["default"]
311
+ files = [
312
+ {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"},
313
+ {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"},
314
+ ]
315
+
316
+ [[package]]
317
+ name = "wcwidth"
318
+ version = "0.2.13"
319
+ summary = "Measures the displayed width of unicode strings in a terminal"
320
+ groups = ["dev"]
321
+ dependencies = [
322
+ "backports-functools-lru-cache>=1.2.1; python_version < \"3.2\"",
323
+ ]
324
+ files = [
325
+ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
326
+ {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
327
+ ]
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/pycall_thread/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "pycall_thread"
7
+ spec.version = PycallThread::VERSION
8
+ spec.authors = ["Seth Nickell"]
9
+ spec.email = ["snickell@gmail.com"]
10
+
11
+ spec.summary = "Use PyCall in a thread-safe way from Rails or Puma"
12
+ spec.description = "PyCall is not thread-safe, but this gem provides a way to run all PyCall code in the same thread."
13
+ spec.homepage = "https://github.com/snickell/pycall_thread"
14
+ spec.required_ruby_version = ">= 2.6.0"
15
+
16
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = spec.homepage
20
+
21
+ spec.license = "MIT"
22
+
23
+ spec.add_dependency "pycall"
24
+
25
+ # Specify which files should be added to the gem when it is released.
26
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
27
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
28
+ `git ls-files -z`.split("\x0").reject do |f|
29
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
30
+ end
31
+ end
32
+ spec.bindir = "exe"
33
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
34
+ spec.require_paths = ["lib"]
35
+
36
+ # Uncomment to register a new dependency of your gem
37
+ # spec.add_dependency "example-gem", "~> 1.0"
38
+
39
+ # For more information and examples about making a new gem, checkout our
40
+ # guide at: https://bundler.io/guides/creating_gem.html
41
+ end
data/pyproject.toml ADDED
@@ -0,0 +1,19 @@
1
+ [project]
2
+ requires-python = "==3.12.*"
3
+
4
+ dependencies = [
5
+ "pandas",
6
+ "psutil>=6.0.0",
7
+ ]
8
+
9
+ [tool.pdm]
10
+ distribution = false
11
+
12
+ [tool.pdm.dev-dependencies]
13
+ dev = [
14
+ "ipython>=8.26.0",
15
+ ]
16
+ [build-system]
17
+ requires = ["pdm-backend"]
18
+ build-backend = "pdm.backend"
19
+
@@ -0,0 +1,4 @@
1
+ module PycallThread
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pycall_thread
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Seth Nickell
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-08-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pycall
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: PyCall is not thread-safe, but this gem provides a way to run all PyCall
28
+ code in the same thread.
29
+ email:
30
+ - snickell@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".pdm-python"
36
+ - ".rubocop.yml"
37
+ - ".ruby-version"
38
+ - Gemfile
39
+ - Gemfile.lock
40
+ - README.md
41
+ - Rakefile
42
+ - bin/console
43
+ - bin/setup
44
+ - examples/puma/app.rb
45
+ - examples/puma/config.ru
46
+ - examples/puma/start_puma.sh
47
+ - lib/pycall_thread.rb
48
+ - lib/pycall_thread/version.rb
49
+ - pdm.lock
50
+ - pycall_thread.gemspec
51
+ - pyproject.toml
52
+ - sig/pycall_thread.rbs
53
+ homepage: https://github.com/snickell/pycall_thread
54
+ licenses:
55
+ - MIT
56
+ metadata:
57
+ allowed_push_host: https://rubygems.org
58
+ homepage_uri: https://github.com/snickell/pycall_thread
59
+ source_code_uri: https://github.com/snickell/pycall_thread
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 2.6.0
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.2.33
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: Use PyCall in a thread-safe way from Rails or Puma
79
+ test_files: []