Skip to content

Commit faa6630

Browse files
committed
pypi_packages: add tests and documentation
Signed-off-by: botantony <[email protected]>
1 parent 22c41d3 commit faa6630

File tree

3 files changed

+163
-2
lines changed

3 files changed

+163
-2
lines changed

Library/Homebrew/formula.rb

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ class Formula
220220
sig { returns(T.any(BuildOptions, Tab)) }
221221
attr_reader :build
222222

223+
# Information about PyPI mappings for this {Formula} is stored
224+
# as {PypiPackages} object.
223225
sig { returns(PypiPackages) }
224226
attr_reader :pypi_packages_info
225227

@@ -595,7 +597,24 @@ def bottle_for_tag(tag = nil)
595597
# @see .pypi_packages
596598
delegate pypi_packages: :"self.class"
597599

598-
# TODO: add docs
600+
# Adds information about PyPI formula mapping as {PypiPackages} object.
601+
# It provides a way to specify package name in PyPI repository,
602+
# define extra packages, or remove them (f.e. if formula installs them as a dependency).
603+
#
604+
# Examples of usage:
605+
# ```rb
606+
# # It will use information about the PyPI package `foo` to update resources
607+
# pypi_packages package_name: "foo"
608+
#
609+
# Add "extra" packages and remove unneeded ones
610+
# depends_on "numpy"
611+
#
612+
# pypi_packages extra_packages: "setuptools", exclude_packages: "numpy"
613+
#
614+
# # Special case: empty `package_name` allows to skip resource updates for non-extra packages
615+
# pypi_packages package_name: "", extra_packages: "setuptools"
616+
# ```
617+
#
599618
# @api public
600619
sig {
601620
params(

Library/Homebrew/pypi_packages.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class PypiPackages
2020
def self.from_json_file(tap, formula_name)
2121
list_entry = tap&.pypi_formula_mappings&.fetch(formula_name, nil)
2222

23-
return new(defined_pypi_mapping: false) unless list_entry.present?
23+
return new(defined_pypi_mapping: false) if list_entry.nil?
2424

2525
case T.cast(list_entry, T.any(FalseClass, String, T::Hash[String, T.any(String, T::Array[String])]))
2626
when false
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# frozen_string_literal: true
2+
3+
require "pypi_packages"
4+
5+
RSpec.describe PypiPackages do
6+
describe ".from_json_file" do
7+
let(:tap) { Tap.fetch("homebrew", "foo") }
8+
let(:formula_name) { "test-formula" }
9+
let(:mappings) { nil }
10+
11+
before do
12+
allow(tap).to receive(:pypi_formula_mappings).and_return(mappings)
13+
end
14+
15+
context "when JSON is `nil`" do
16+
it "returns an instance with defined_pypi_mapping: false" do
17+
pkgs = described_class.from_json_file(tap, formula_name)
18+
expect(pkgs).to be_a(described_class)
19+
expect(pkgs.defined_pypi_mapping?).to be(false)
20+
expect(pkgs.needs_manual_update?).to be(false)
21+
expect(pkgs.package_name).to be_nil
22+
end
23+
end
24+
25+
context "when JSON is an empty hash" do
26+
let(:mappings) { {} }
27+
28+
it "returns an instance with defined_pypi_mapping: false" do
29+
pkgs = described_class.from_json_file(tap, formula_name)
30+
expect(pkgs).to be_a(described_class)
31+
expect(pkgs.defined_pypi_mapping?).to be(false)
32+
expect(pkgs.needs_manual_update?).to be(false)
33+
expect(pkgs.package_name).to be_nil
34+
end
35+
end
36+
37+
context "when mapping entry is `false`" do
38+
let(:mappings) { { formula_name => false } }
39+
40+
it "returns an instance with needs_manual_update: true" do
41+
pkgs = described_class.from_json_file(tap, formula_name)
42+
expect(pkgs).to be_a(described_class)
43+
expect(pkgs.defined_pypi_mapping?).to be(true)
44+
expect(pkgs.needs_manual_update?).to be(true)
45+
expect(pkgs.package_name).to be_nil
46+
end
47+
end
48+
49+
context "when mapping entry is a String" do
50+
let(:mappings) { { formula_name => "bar" } }
51+
52+
it "returns an instance with package_name set" do
53+
pkgs = described_class.from_json_file(tap, formula_name)
54+
expect(pkgs.package_name).to eq("bar")
55+
expect(pkgs.extra_packages).to eq([])
56+
expect(pkgs.exclude_packages).to eq([])
57+
expect(pkgs.dependencies).to eq([])
58+
expect(pkgs.defined_pypi_mapping?).to be(true)
59+
expect(pkgs.needs_manual_update?).to be(false)
60+
end
61+
end
62+
63+
context "when mapping entry is `true`" do
64+
let(:mappings) { { formula_name => true } }
65+
66+
it "raises a Sorbet type error" do
67+
expect do
68+
described_class.from_json_file(tap, formula_name)
69+
end.to raise_error(TypeError, /got type TrueClass/)
70+
end
71+
end
72+
73+
context "when mapping entry is a Hash" do
74+
let(:mappings) do
75+
{
76+
formula_name => {
77+
"package_name" => "bar",
78+
"extra_packages" => ["baz"],
79+
"exclude_packages" => ["qux"],
80+
"dependencies" => ["quux"],
81+
},
82+
}
83+
end
84+
85+
it "returns an instance with all fields populated" do
86+
pkgs = described_class.from_json_file(tap, formula_name)
87+
expect(pkgs.package_name).to eq("bar")
88+
expect(pkgs.extra_packages).to eq(["baz"])
89+
expect(pkgs.exclude_packages).to eq(["qux"])
90+
expect(pkgs.dependencies).to eq(["quux"])
91+
expect(pkgs.defined_pypi_mapping?).to be(true)
92+
expect(pkgs.needs_manual_update?).to be(false)
93+
end
94+
end
95+
96+
context "when mapping entry hash omits optional keys" do
97+
let(:mappings) do
98+
{ formula_name => { "package_name" => "bar" } }
99+
end
100+
101+
it "fills missing keys with empty arrays" do
102+
pkgs = described_class.from_json_file(tap, formula_name)
103+
expect(pkgs.package_name).to eq("bar")
104+
expect(pkgs.extra_packages).to eq([])
105+
expect(pkgs.exclude_packages).to eq([])
106+
expect(pkgs.dependencies).to eq([])
107+
end
108+
end
109+
110+
context "when mapping entry hash uses Array for `package_name`" do
111+
let(:mappings) do
112+
{ formula_name => { "package_name" => ["bar"] } }
113+
end
114+
115+
it "raises a Sorbet type error" do
116+
expect do
117+
described_class.from_json_file(tap, formula_name)
118+
end.to raise_error(TypeError, /got type Array/)
119+
end
120+
end
121+
122+
context "when mapping entry hash uses String for s key" do
123+
let(:mappings) do
124+
{ formula_name => { "extra_packages" => "bar" } }
125+
end
126+
127+
it "raises a Sorbet type error" do
128+
expect do
129+
described_class.from_json_file(tap, formula_name)
130+
end.to raise_error(TypeError, /got type String/)
131+
end
132+
end
133+
134+
context "when tap is `nil`" do
135+
it "fills missing keys with empty arrays" do
136+
pkgs = described_class.from_json_file(nil, formula_name)
137+
expect(pkgs.defined_pypi_mapping?).to be(false)
138+
expect(pkgs.package_name).to be_nil
139+
end
140+
end
141+
end
142+
end

0 commit comments

Comments
 (0)