├── .github
├── __init__.py
├── actions.py
├── tests.py
└── workflows
│ ├── delete.yml
│ ├── register.yml
│ ├── static.yml
│ ├── tests.yml
│ └── update.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── index.html
├── mydependency
└── index.html
├── pkg_template.html
├── private-hello
└── index.html
├── public-hello
└── index.html
├── static
├── index_styles.css
├── package_page.js
├── package_styles.css
└── pypi_checker.js
├── transformers
└── index.html
└── update_pkgs.py
/.github/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/astariul/github-hosted-pypi/e29e6f7ea7bcc35209362f90b37619abd6b49fe0/.github/__init__.py
--------------------------------------------------------------------------------
/.github/actions.py:
--------------------------------------------------------------------------------
1 | import os
2 | import copy
3 | import re
4 | import shutil
5 |
6 | from bs4 import BeautifulSoup
7 |
8 |
9 | INDEX_FILE = "index.html"
10 | TEMPLATE_FILE = "pkg_template.html"
11 | YAML_ACTION_FILES = [".github/workflows/delete.yml", ".github/workflows/update.yml"]
12 |
13 | INDEX_CARD_HTML = '''
14 |
15 | placeholder_name
16 |
17 |
18 |
19 | placehholder_version
20 |
21 |
22 |
23 | placeholder_description
24 |
25 | '''
26 |
27 |
28 | def normalize(name):
29 | """ From PEP503 : https://www.python.org/dev/peps/pep-0503/ """
30 | return re.sub(r"[-_.]+", "-", name).lower()
31 |
32 |
33 | def normalize_version(version):
34 | version = version.lower()
35 | return version[1:] if version.startswith("v") else version
36 |
37 |
38 | def is_stable(version):
39 | return not ("dev" in version or "a" in version or "b" in version or "rc" in version)
40 |
41 |
42 | def package_exists(soup, package_name):
43 | package_ref = package_name + "/"
44 | for anchor in soup.find_all('a'):
45 | if anchor['href'] == package_ref:
46 | return True
47 | return False
48 |
49 |
50 | def transform_github_url(input_url):
51 | # Split the input URL to extract relevant information
52 | parts = input_url.rstrip('/').split('/')
53 | username, repo = parts[-2], parts[-1]
54 |
55 | # Create the raw GitHub content URL
56 | raw_url = f'https://raw.githubusercontent.com/{username}/{repo}/main/README.md'
57 | return raw_url
58 |
59 |
60 | def register(pkg_name, version, author, short_desc, homepage):
61 | link = f'git+{homepage}@{version}'
62 | long_desc = transform_github_url(homepage)
63 | # Read our index first
64 | with open(INDEX_FILE) as html_file:
65 | soup = BeautifulSoup(html_file, "html.parser")
66 | norm_pkg_name = normalize(pkg_name)
67 | norm_version = normalize_version(version)
68 |
69 | if package_exists(soup, norm_pkg_name):
70 | raise ValueError(f"Package {norm_pkg_name} seems to already exists")
71 |
72 | # Create a new anchor element for our new package
73 | placeholder_card = BeautifulSoup(INDEX_CARD_HTML, 'html.parser')
74 | placeholder_card = placeholder_card.find('a')
75 | new_package = copy.copy(placeholder_card)
76 | new_package['href'] = f"{norm_pkg_name}/"
77 | new_package.contents[0].replace_with(pkg_name)
78 | spans = new_package.find_all('span')
79 | spans[1].string = norm_version # First span contain the version
80 | spans[2].string = short_desc # Second span contain the short description
81 |
82 | # Add it to our index and save it
83 | soup.find('h6', class_='text-header').insert_after(new_package)
84 | with open(INDEX_FILE, 'wb') as index:
85 | index.write(soup.prettify("utf-8"))
86 |
87 | # Then get the template, replace the content and write to the right place
88 | with open(TEMPLATE_FILE) as temp_file:
89 | template = temp_file.read()
90 |
91 | template = template.replace("_package_name", pkg_name)
92 | template = template.replace("_norm_version", norm_version)
93 | template = template.replace("_version", version)
94 | template = template.replace("_link", f"{link}#egg={norm_pkg_name}-{norm_version}")
95 | template = template.replace("_homepage", homepage)
96 | template = template.replace("_author", author)
97 | template = template.replace("_long_description", long_desc)
98 | template = template.replace("_latest_main", version)
99 |
100 | os.mkdir(norm_pkg_name)
101 | package_index = os.path.join(norm_pkg_name, INDEX_FILE)
102 | with open(package_index, "w") as f:
103 | f.write(template)
104 |
105 |
106 | def update(pkg_name, version):
107 | # Read our index first
108 | with open(INDEX_FILE) as html_file:
109 | soup = BeautifulSoup(html_file, "html.parser")
110 | norm_pkg_name = normalize(pkg_name)
111 | norm_version = normalize_version(version)
112 |
113 | if not package_exists(soup, norm_pkg_name):
114 | raise ValueError(f"Package {norm_pkg_name} seems to not exists")
115 |
116 | # Change the version in the main page (only if stable)
117 | if is_stable(version):
118 | anchor = soup.find('a', attrs={"href": f"{norm_pkg_name}/"})
119 | spans = anchor.find_all('span')
120 | spans[1].string = norm_version
121 | with open(INDEX_FILE, 'wb') as index:
122 | index.write(soup.prettify("utf-8"))
123 |
124 | # Change the package page
125 | index_file = os.path.join(norm_pkg_name, INDEX_FILE)
126 | with open(index_file) as html_file:
127 | soup = BeautifulSoup(html_file, "html.parser")
128 |
129 | # Extract the URL from the onclick attribute
130 | button = soup.find('button', id='repoHomepage')
131 | if button:
132 | link = button.get("onclick")[len("openLinkInNewTab('"):-2]
133 | else:
134 | raise Exception("Homepage URL not found")
135 |
136 | # Create a new anchor element for our new version
137 | original_div = soup.find('section', class_='versions').findAll('div')[-1]
138 | new_div = copy.copy(original_div)
139 | anchor = new_div.find('a')
140 | new_div['onclick'] = f"load_readme('{version}', scroll_to_div=true);"
141 | new_div['id'] = version
142 | new_div['class'] = ""
143 | if not is_stable(version):
144 | new_div['class'] += "prerelease"
145 | else:
146 | # replace the latest main version
147 | main_version_span = soup.find('span', id='latest-main-version')
148 | main_version_span.string = version
149 | anchor.string = norm_version
150 | anchor['href'] = f"git+{link}@{version}#egg={norm_pkg_name}-{norm_version}"
151 |
152 | # Add it to our index
153 | original_div.insert_after(new_div)
154 |
155 | # Change the latest version (if stable)
156 | if is_stable(version):
157 | soup.html.body.div.section.find_all('span')[1].contents[0].replace_with(version)
158 |
159 | # Save it
160 | with open(index_file, 'wb') as index:
161 | index.write(soup.prettify("utf-8"))
162 |
163 |
164 | def delete(pkg_name):
165 | # Read our index first
166 | with open(INDEX_FILE) as html_file:
167 | soup = BeautifulSoup(html_file, "html.parser")
168 | norm_pkg_name = normalize(pkg_name)
169 |
170 | if not package_exists(soup, norm_pkg_name):
171 | raise ValueError(f"Package {norm_pkg_name} seems to not exists")
172 |
173 | # Remove the package directory
174 | shutil.rmtree(norm_pkg_name)
175 |
176 | # Find and remove the anchor corresponding to our package
177 | anchor = soup.find('a', attrs={"href": f"{norm_pkg_name}/"})
178 | anchor.extract()
179 | with open(INDEX_FILE, 'wb') as index:
180 | index.write(soup.prettify("utf-8"))
181 |
182 |
183 | def main():
184 | # Call the right method, with the right arguments
185 | action = os.environ["PKG_ACTION"]
186 |
187 | if action == "REGISTER":
188 | register(
189 | pkg_name=os.environ["PKG_NAME"],
190 | version=os.environ["PKG_VERSION"],
191 | author=os.environ["PKG_AUTHOR"],
192 | short_desc=os.environ["PKG_SHORT_DESC"],
193 | homepage=os.environ["PKG_HOMEPAGE"],
194 | )
195 | elif action == "DELETE":
196 | delete(
197 | pkg_name=os.environ["PKG_NAME"]
198 | )
199 | elif action == "UPDATE":
200 | update(
201 | pkg_name=os.environ["PKG_NAME"],
202 | version=os.environ["PKG_VERSION"]
203 | )
204 |
205 |
206 | if __name__ == "__main__":
207 | main()
208 |
--------------------------------------------------------------------------------
/.github/tests.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import os
3 | from importlib.metadata import PackageNotFoundError, version
4 | from contextlib import contextmanager
5 |
6 |
7 | @contextmanager
8 | def run_local_pypi_index():
9 | # WARNING : This requires the script to be run from the root of the repo
10 | p = subprocess.Popen(["python", "-m", "http.server"])
11 | try:
12 | yield
13 | finally:
14 | p.terminate()
15 |
16 |
17 | def exists(pkg_name: str) -> bool:
18 | try:
19 | version(pkg_name)
20 | except PackageNotFoundError:
21 | return False
22 | else:
23 | return True
24 |
25 |
26 | def pip_install(pkg_name: str, upgrade: bool = False, version: str = None):
27 | package_to_install = pkg_name if version is None else f"{pkg_name}=={version}"
28 | cmd = ["python", "-m", "pip", "install", package_to_install, "--upgrade" if upgrade else "", "--extra-index-url", "http://localhost:8000"]
29 | subprocess.run([c for c in cmd if c])
30 |
31 |
32 | def pip_uninstall(pkg_name: str):
33 | subprocess.run(["python", "-m", "pip", "uninstall", pkg_name, "-y"])
34 |
35 |
36 | def register(pkg_name: str, pkg_version: str, pkg_link: str):
37 | env = os.environ.copy()
38 | env["PKG_ACTION"] = "REGISTER"
39 | env["PKG_NAME"] = pkg_name
40 | env["PKG_VERSION"] = pkg_version
41 | env["PKG_AUTHOR"] = "Dummy author"
42 | env["PKG_SHORT_DESC"] = "Dummy Description"
43 | env["PKG_HOMEPAGE"] = pkg_link
44 | subprocess.run(["python", ".github/actions.py"], env=env)
45 |
46 |
47 | def update(pkg_name: str, pkg_version: str):
48 | env = os.environ.copy()
49 | env["PKG_ACTION"] = "UPDATE"
50 | env["PKG_NAME"] = pkg_name
51 | env["PKG_VERSION"] = pkg_version
52 | subprocess.run(["python", ".github/actions.py"], env=env)
53 |
54 |
55 | def delete(pkg_name: str):
56 | env = os.environ.copy()
57 | env["PKG_ACTION"] = "DELETE"
58 | env["PKG_NAME"] = pkg_name
59 | subprocess.run(["python", ".github/actions.py"], env=env)
60 |
61 |
62 | def run_tests():
63 | # This test is checking that the Github actions for registering, updating,
64 | # and deleting are working and the PyPi index is updated accordingly.
65 | # What we do is :
66 | # * Serve the HTML locally so we have a local PyPi index
67 | # * Run the actions for registering, updating, and deleting packages in
68 | # this local PyPi
69 | # * In-between, run some basic checks to see if the installation is
70 | # working properly
71 | with run_local_pypi_index():
72 | # First, make sure the package is not installed
73 | assert not exists("public-hello")
74 |
75 | # The package `public-hello` is already registered in our local PyPi
76 | # ACTION : Let's remove it from our local PyPi
77 | delete("public-hello")
78 |
79 | # Trying to install the package, only the version uploaded to PyPi (0.0.0)
80 | # will be detected and installed (the version in our local PyPi was
81 | # successfully deleted)
82 | pip_install("public-hello")
83 | assert exists("public-hello") and version("public-hello") == "0.0.0"
84 |
85 | # ACTION : Register the package, to make it available again
86 | register("public-hello", "0.1", "https://github.com/astariul/public-hello")
87 |
88 | # Now we can install it from the local PyPi
89 | pip_install("public-hello", upgrade=True)
90 | assert exists("public-hello") and version("public-hello") == "0.1"
91 |
92 | # ACTION : Update the package with a new version
93 | update("public-hello", "0.2")
94 |
95 | # We can update the package to the newest version
96 | pip_install("public-hello", upgrade=True)
97 | assert exists("public-hello") and version("public-hello") == "0.2"
98 |
99 | # We should still be able to install the old version
100 | pip_install("public-hello", version="0.1")
101 | assert exists("public-hello") and version("public-hello") == "0.1"
102 |
103 | # Uninstall the package (for consistency with the initial state)
104 | pip_uninstall("public-hello")
105 | assert not exists("public-hello")
106 |
107 |
108 | if __name__ == "__main__":
109 | run_tests()
110 |
--------------------------------------------------------------------------------
/.github/workflows/delete.yml:
--------------------------------------------------------------------------------
1 | name: delete
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | package_name:
7 | description: Package name
8 | required: true
9 | type: string
10 |
11 | jobs:
12 | delete:
13 | runs-on: ubuntu-latest
14 | strategy:
15 | matrix:
16 | python-version: [3.8]
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 | - name: Set up Python ${{ matrix.python-version }}
21 | uses: actions/setup-python@v1
22 | with:
23 | python-version: ${{ matrix.python-version }}
24 | - name: Run Action
25 | env:
26 | PKG_ACTION: DELETE
27 | PKG_NAME: ${{ inputs.package_name }}
28 | run: |
29 | pip install beautifulsoup4
30 | python .github/actions.py
31 | - name: Create Pull Request
32 | uses: peter-evans/create-pull-request@v3
33 | with:
34 | commit-message: ':package: [:robot:] Delete package from PyPi index'
35 | title: '[🤖] Delete `${{ inputs.package_name }}` from PyPi index'
36 | body: Automatically generated PR, deleting `${{ inputs.package_name }}` from
37 | PyPi index.
38 | branch-suffix: random
39 | delete-branch: true
40 |
--------------------------------------------------------------------------------
/.github/workflows/register.yml:
--------------------------------------------------------------------------------
1 | name: register
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | package_name:
7 | description: 'Package name'
8 | required: true
9 | type: string
10 | version:
11 | description: 'Version of the package (tag name)'
12 | required: true
13 | type: string
14 | author:
15 | description: 'Author(s) of the package'
16 | required: true
17 | type: string
18 | short_desc:
19 | description: 'A short description of the package to show on the index'
20 | required: true
21 | type: string
22 | homepage:
23 | description: 'Homepage of the package (link to the github repository)'
24 | required: true
25 | type: string
26 |
27 | jobs:
28 | update:
29 | runs-on: ubuntu-latest
30 | strategy:
31 | matrix:
32 | python-version: [3.8]
33 |
34 | steps:
35 | - uses: actions/checkout@v2
36 | - name: Set up Python ${{ matrix.python-version }}
37 | uses: actions/setup-python@v1
38 | with:
39 | python-version: ${{ matrix.python-version }}
40 | - name: Run Action
41 | env:
42 | PKG_ACTION: REGISTER
43 | PKG_NAME: ${{ inputs.package_name }}
44 | PKG_VERSION: ${{ inputs.version }}
45 | PKG_AUTHOR: ${{ inputs.author }}
46 | PKG_SHORT_DESC: ${{ inputs.short_desc }}
47 | PKG_HOMEPAGE: ${{ inputs.homepage }}
48 | run: |
49 | pip install beautifulsoup4
50 | python .github/actions.py
51 | - name: Create Pull Request
52 | uses: peter-evans/create-pull-request@v3
53 | with:
54 | commit-message: ':package: [:robot:] Register package in PyPi index'
55 | title: '[🤖] Register `${{ inputs.package_name }}` in PyPi index'
56 | body: Automatically generated PR, registering `${{ inputs.package_name }}` in PyPi
57 | index.
58 | branch-suffix: random
59 | delete-branch: true
60 |
--------------------------------------------------------------------------------
/.github/workflows/static.yml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Deploy static content to Pages
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: ["main"]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20 | concurrency:
21 | group: "pages"
22 | cancel-in-progress: false
23 |
24 | jobs:
25 | # Single deploy job since we're just deploying
26 | deploy:
27 | environment:
28 | name: github-pages
29 | url: ${{ steps.deployment.outputs.page_url }}
30 | runs-on: ubuntu-latest
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@v4
34 | - name: Setup Pages
35 | uses: actions/configure-pages@v5
36 | - name: Upload artifact
37 | uses: actions/upload-pages-artifact@v3
38 | with:
39 | # Upload entire repository
40 | path: '.'
41 | - name: Deploy to GitHub Pages
42 | id: deployment
43 | uses: actions/deploy-pages@v4
44 |
45 | prod_checks:
46 | runs-on: ubuntu-latest
47 | needs: deploy
48 | steps:
49 | - uses: actions/setup-python@v5
50 | - name: Install public-hello
51 | run: |
52 | python -m pip install --upgrade pip
53 | pip install public-hello --extra-index-url https://astariul.github.io/github-hosted-pypi/
54 |
55 | # Check if the package and its dependency was installed
56 | pip show public-hello
57 | pip show mydependency
58 |
59 | # The code from the package should be accessible
60 | python -c "from public_hello import hi; assert hi() == 'Hello world from public repository (with a dependency)'"
61 | - name: Install public-hello older version
62 | run: |
63 | python -m pip install --upgrade pip
64 | pip install public-hello==0.1 --extra-index-url https://astariul.github.io/github-hosted-pypi/
65 |
66 | # Check if the package was installed
67 | pip show public-hello
68 |
69 | # The code from the package should be accessible
70 | python -c "from public_hello import hi; assert hi() == 'Hello world from public repository'"
71 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: tests
2 |
3 | on: pull_request
4 |
5 | jobs:
6 | tests:
7 | runs-on: ubuntu-latest
8 | permissions:
9 | contents: read
10 | pull-requests: write
11 | steps:
12 | - uses: actions/checkout@v4
13 | with: # Use deploy key to ensure pushed change trigger checks as well : https://github.com/peter-evans/create-pull-request/blob/master/docs/concepts-guidelines.md#workarounds-to-trigger-further-workflow-runs
14 | ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
15 | - name: Set up Python
16 | uses: actions/setup-python@v5
17 | with:
18 | python-version: "3.8"
19 | - name: Install dependencies
20 | run: |
21 | python -m pip install --upgrade pip
22 | pip install beautifulsoup4
23 | - name: Run unit-tests
24 | timeout-minutes: 5
25 | run: |
26 | # Enable pipefail option, so if the tests fail, the job will fail as well
27 | set -o pipefail
28 | python .github/tests.py
29 |
--------------------------------------------------------------------------------
/.github/workflows/update.yml:
--------------------------------------------------------------------------------
1 | name: update
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | package_name:
7 | description: Package name
8 | required: true
9 | type: string
10 | version:
11 | description: New version of the package (tag name)
12 | required: true
13 | type: string
14 |
15 | jobs:
16 | update:
17 | runs-on: ubuntu-latest
18 | strategy:
19 | matrix:
20 | python-version: [3.8]
21 |
22 | steps:
23 | - uses: actions/checkout@v2
24 | - name: Set up Python ${{ matrix.python-version }}
25 | uses: actions/setup-python@v1
26 | with:
27 | python-version: ${{ matrix.python-version }}
28 | - name: Run Action
29 | env:
30 | PKG_ACTION: UPDATE
31 | PKG_NAME: ${{ inputs.package_name }}
32 | PKG_VERSION: ${{ inputs.version }}
33 | run: |
34 | pip install beautifulsoup4
35 | python .github/actions.py
36 | - name: Create Pull Request
37 | uses: peter-evans/create-pull-request@v3
38 | with:
39 | commit-message: ':package: [:robot:] Update package in PyPi index'
40 | title: '[🤖] Update `${{ inputs.package_name }}` in PyPi index'
41 | body: Automatically generated PR, updating `${{ inputs.package_name }}` in PyPi
42 | index.
43 | branch-suffix: random
44 | delete-branch: true
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Nicolas Remond
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
4 | Make all your private packages accessible in one place
with this github-hosted PyPi index
5 |
10 | Description • 11 | Try it ! • 12 | Get Started • 13 | Modify indexed packages • 14 | FAQ • 15 | A word about supply chain attacks • 16 | Contribute • 17 | References 18 |
19 | 20 | --- 21 | 22 | ## Features 23 | 24 | * **:octocat: Github-hosted** 25 | * **🚀 Template ready to deploy** 26 | * **🔆 Easy to use** through Github Actions 27 | * **🚨 Secure** : Warns you if your package is vulnerable to supply chain attacks 28 | 29 | ## Description 30 | 31 | This repository is a Github page used as a PyPi index, conform to [PEP503](https://www.python.org/dev/peps/pep-0503/). 32 | 33 | You can use it to group all your packages in one place, and access it easily through `pip`, almost like any other package publicly available ! 34 | 35 | --- 36 | 37 | _While the PyPi index is public, private packages indexed here are kept private : you will need Github authentication to be able to retrieve it._ 38 | 39 | ## Try it ! 40 | 41 | Visit [astariul.github.io/github-hosted-pypi/](http://astariul.github.io/github-hosted-pypi/) and try to install packages indexed there ! 42 | 43 | --- 44 | 45 | Try to install the package `public-hello` : 46 | ```console 47 | pip install public-hello --extra-index-url https://astariul.github.io/github-hosted-pypi/ 48 | ``` 49 | 50 | It will also install the package `mydependency`, automatically ! 51 | 52 | Try it with : 53 | 54 | ```python 55 | from public_hello import hi 56 | print(hi()) 57 | ``` 58 | 59 | You can also install a specific version : 60 | 61 | ```console 62 | pip install public-hello==0.1 --extra-index-url https://astariul.github.io/github-hosted-pypi/ 63 | ``` 64 | 65 | --- 66 | 67 | Now try to install the package `private-hello` : 68 | ```console 69 | pip install private-hello --extra-index-url https://astariul.github.io/github-hosted-pypi/ 70 | ``` 71 | 72 | _It will not work, because it's private and only me can access it !_ 73 | 74 | ## Get started 75 | 76 | * Use this template and create your own repository : 77 | 78 | 81 | 82 | * Go to `Settings` of your repository, and enable Github Page 83 | * Customize `index.html` and `pkg_template.html` to your liking 84 | * You're ready to go ! Visit `
37 | Welcome to the private Python package index of YourCompany !
38 |
39 | You can install packages with :
40 |
pip install <package_name> --extra-index-url https://astariul.github.io/github-hosted-pypi/
41 |
42 | 56 | 59 |60 | 61 |
62 | pip install mydependency --extra-index-url https://astariul.github.io/github-hosted-pypi/
63 |
64 |
65 | 76 | 77 | Author : 78 | 79 | Nicolas Remond 80 |
81 |95 |
96 |56 | 59 |60 | 61 |
62 | pip install _package_name --extra-index-url https://astariul.github.io/github-hosted-pypi/
63 |
64 |
65 | 76 | 77 | Author : 78 | 79 | _author 80 |
81 |95 |
96 |56 | 59 |60 | 61 |
62 | pip install private-hello --extra-index-url https://astariul.github.io/github-hosted-pypi/
63 |
64 |
65 | 76 | 77 | Author : 78 | 79 | Nicolas Remond 80 |
81 |95 |
96 |52 | 55 |56 |
57 | pip install public-hello --extra-index-url https://astariul.github.io/github-hosted-pypi/
58 |
59 | 69 | 70 | Author : 71 | 72 | Nicolas Remond 73 |
74 |97 |
98 |56 | 59 |60 | 61 |
62 | pip install transformers --extra-index-url https://astariul.github.io/github-hosted-pypi/
63 |
64 |
65 | 76 | 77 | Author : 78 | 79 | Nicolas Remond 80 |
81 |95 |
96 |