How To Set Project Links in PyPI - setuptools

How do I add links under the Project Links section of my PyPI project?

This question has been answered before but was a bit difficult to find, so hopefully this one is a bit easier.
PyPI pulls these links from the project_urls metadata.
In your setup.py file, call setuptools.setup() with the project_urls argument as a dictionary of title-url pairs.
import setuptools
project_urls = {
'Link 1': 'https://mygreatsite.com',
'Link 2': 'https://anothersite.com'
}
setuptools.setup( other_arguments, project_urls = project_urls )

If you want to know for which URL identifier which icon is used on PyPI,e.g. if you write
project_urls = {
'Twitter': 'https://twitter.com/PyScaffold',
}
you will see the Twitter bird on PyPI, e.g. https://pypi.org/project/PyScaffold/.
In order to see the available icons and when there are set, just check out this file(/warehouse/templates/packaging/detail.html) from the PyPI source code.

Poetry does this a bit differently, but it's the same idea.
You can put some basic info in the main part of your pyproject.toml file, under [tool.poetry]. Something like:
[tool.poetry]
homepage = "https://free.law/projects/x-ray"
repository = "https://github.com/freelawproject/x-ray"
documentation = "https://github.com/freelawproject/x-ray/blob/main/README.md"
And if you want even more URLs, you can do something like:
[tool.poetry.urls]
"Organisation Homepage" = "https://free.law/"
"Change Log" = "https://github.com/freelawproject/x-ray/blob/main/CHANGES.md"
Issues = "https://github.com/freelawproject/x-ray/issues"
Funding = "https://www.courtlistener.com/donate/?referrer=pypi-xray"
There's a list of special values that'll get you icons, but it seems like you can put whatever you want here and Poetry will be happy. If Pypi doesn't know what the link is, it'll just put a generic icon next to it.
Here's the list of things that get icons:
https://github.com/pypi/warehouse/blob/main/warehouse/templates/packaging/detail.html

Related

How to exclude UnityEditor reference from asmdef?

How to exclude UnityEditor reference from asmdef?
Why I need it:
I have an asmdef file. For example, it is MyAssembly/MyAssembly.asmdef. The MyAssembly contains a lot of features and each feature staff is placed in its own folder. And some of these features has a code that is needed only in editor, and it refers to UnityEditor namespace. Such editor code is placed into an Editor folder.
But as you know, Editor folder name means nothing in terms of asmdef usage. So I add AssemblyDefenitionReference in each folder and refer it to the MyAssemblyEditor.asmdef assembly definition. So the paths looks like this:
MyAssembly/MyAssembly.asmdef
MyAssembly/Editor/MyAssemblyEditor.asmdef - this folder contains no code. It's needed just to place asmdef, because it's not allowed to place two asmdefs in a single folder.
MyAssembly/SomeFeature/Editor/*feature editor staff*
MyAssembly/SomeFeature/Editor/Editor.asmref - refers to MyAssemblyEditor.asmdef
MyAssembly/SomeFeature/*feature staff*
All this works good. But the problem is that, when some developer adds a new feature, he can forget to add a reference to the MyAssemblyEditor.asmdef in the editor folder. And there are no any errors will be shown in this case. This mistake will be revealed only when the build will be cooked. But I'd like that using of UnityEditor in MyAssembly will be instantly marked as an error.
Feel free to suggest other solution for this problem.
This thread got me thinking I can use CsprojPostprocessor to remove all references to UnityEditor from my csproj file. I wrote such class:
using System.Text.RegularExpressions;
using UnityEditor;
// ReSharper disable once CheckNamespace
public class CsprojPostprocessor : AssetPostprocessor
{
public static string OnGeneratedCSProject(string path, string content)
{
if (!path.EndsWith("Editor.csproj") && !path.EndsWith("Tests.csproj"))
{
var newContent =
Regex.Replace(content, "<Reference Include=\"UnityEditor(.|\n)*?</Reference>", "");
return newContent;
}
return content;
}
}
It also can be done with an xml parser or something.
The only thing, that confuse me is that this mechanism is badly documented and doesn't look like something simple users should use. So I use it at my own risk, but looks like there is no guarantee it will be strongly supported in future.

Backend Folder-Styles are gone after copy

In the backend in the sitetree we gave some folders a special style (edit folder -> Behaviour -> Contain Plugin -> News). So that this folders get the class .t3-icon-pages-contains-news and are blue.
Now I copied different typo3 files into that installation, but use the same database. Everything works fine, but this folders dont have the class .t3-icon-pages-contains-news anymore. The Behaviour -> Contain Plugin is still set to news.
Any ideas where this is coming from?
You did not provide any code example so it is hard to figure out what is going wrong in your situation.
However, here a example how it should look like:
typo3conf/ext/myext/ext_tables.php or typo3conf/extTables.php
if (!$TCA['pages']['columns']['module']['config']['items']) {
$TCA['pages']['columns']['module']['config']['items'] = array();
}
$TCA['pages']['columns']['module']['config']['items'][] = array('your label', 'myext', 'EXT:myext/Resources/Public/Icons/FolderIcon.gif');
\TYPO3\CMS\Backend\Sprite\SpriteManager::addTcaTypeIcon('pages', 'contains-myext', '../typo3conf/myext/Resources/Public/Icons/FolderIcon.gif');

SugarCRM support links are broken within modules

I recently downloaded and installed sugarcrm community edition: 6.5.17.
How can I fix the help links within the various modules? Or, how can I figure out how the help calls work, since I can't find any code reference within code to the links being generated.
When I click on a help link within any module, such as within opportunities:
Click here to learn more about the Opportunities module. In order to access more information, use the user menu drop down located on the main navigation bar to access Help.
Where the "click here" link looks like this when viewing the html page source:
href='?module=Administration&action=SupportPortal&view=documentation&version=6.5.17&edition=CE&lang=&help_module=Project&help_action=&key='
I get a "403 Forbidden" error with this link:
[link]http://support.sugarcrm.com/02_Documentation/01_Sugar_Editions/05_Sugar_Community_Edition/Sugar_Community_Edition_6.5/Application_Guide/13_Opportunities/
I manually found the correct link to be:
[link]http://support.sugarcrm.com/02_Documentation/01_Sugar_Editions/05_Sugar_Community_Edition/Sugar_Community_Edition_6.5/Sugar_Community_Edition_Application_Guide_6.5.0/13_Opportunities/
Open config_override.php and add a param for custom_help_url that points to a top-level page at the site that you found.
Here's how I found it:
In Sugar, nearly anything you read on the screen is kept in some language file for localization and translation purposes. So we can start by using grep to search the system for this bit of translated text:
[MKP01] [~/Sites/sugar] > grep -r 'to learn more about the' include/
include//language/en_us.lang.php: 'MSG_EMPTY_LIST_VIEW_NO_RESULTS_SUBMSG' => "<item4> to learn more about the <item1> module. In order to access more information, use the user menu drop down located on the main navigation bar to access Help.",
Now that we know the name of the label, we can search for instances of when it is used:
[MKP01] [~/Sites/nemrace] > grep -r 'MSG_EMPTY_LIST_VIEW_NO_RESULTS_SUBMSG' include/
include//language/en_us.lang.php: 'MSG_EMPTY_LIST_VIEW_NO_RESULTS_SUBMSG' => "<item4> to learn more about the <item1> module. In order to access more information, use the user menu drop down located on the main navigation bar to access Help.",
include//ListView/ListViewGeneric.tpl: {$APP.MSG_EMPTY_LIST_VIEW_NO_RESULTS_SUBMSG|replace:"<item1>":$moduleName|replace:"<item4>":$helpLink}
The first result there we already know about, but the second is what you're after: it's the default Smarty template for all list views. If we open that file in a text editor we can trace the definition for variable $helpLink is only a few lines above where it's used:
{capture assign="helpLink"}<a target="_blank" href='?module=Administration&action=SupportPortal&view=documentation&version={$sugar_info.sugar_version}&edition={$sugar_info.sugar_flavor}&lang=&help_module={$currentModule}&help_action=&key='>{$APP.LBL_CLICK_HERE}</a>{/capture}
Then you know to dig into modules/Administration/SupportPortal.php and reading that logic, know to look for the definition of function get_help_url. We find it in include/utils.php
function get_help_url($send_edition = '', $send_version = '', $send_lang = '', $send_module = '', $send_action = '', $dev_status = '', $send_key = '', $send_anchor = '') {
global $sugar_config;
if (!empty($sugar_config['custom_help_url'])) {
$sendUrl = $sugar_config['custom_help_url'];
} else {
if (!empty($sugar_config['custom_help_base_url'])) {
$baseUrl= $sugar_config['custom_help_base_url'];
} else {
$baseUrl = "http://www.sugarcrm.com/crm/product_doc.php";
}
$sendUrl = $baseUrl . "?edition={$send_edition}&version={$send_version}&lang={$send_lang}&module={$send_module}&help_action={$send_action}&status={$dev_status}&key={$ send_key}";
if(!empty($send_anchor)) {
$sendUrl .= "&anchor=".$send_anchor;
}
}
return $sendUrl;
}
This is great news - Sugar understands that some system administrators would wish to provide their own help URls or base URLs, so my recommendation to you would be open config_override.php and add a param for custom_help_url that points to a top-level page at the site that you found.

How do I get a list of members in a Python module, or how should I create plugins?

I'm writing a sort of dashboard-esque program in Python to make tasks that I do frequently here at work a little better/easier.
What I currently have set up is an actions module. actions contains an actionlist that is a list of classes. Each of my classes has a .title, .icon (base-64 encoded string for use with tkinter.PhotoImage), and a .action function.
Using tkinter I create a new button for each of these actions, with the appropriate icon, text, and the command of the button set to the .action. So far it works quite nicely, but if I want to create a new action, I have to go into my actions module, create a new class, and then put that class in my actionlist.
What I would like to do is be able to dynamically generate this actionlist based on what files are present in my module. So I would like to have something like this:
actions/
__init__.py
someaction.py
do_another_thing.py
do_all_the_things.py
chop_down_the_tree_with_a_herring.py
And whenever I would like to create a new action, I just put a .py script in my actions folder and then it would automagically generate the list of actions for me, however I can't seem to find any good way of creating this list. I've thought of using something like os.listdir to get the list of files, but neither that or the other methods of introspection (dir, and inspect.getmembers) seemed to have quite what I wanted. Of course, it's possible that I just missed the features that I need.
In any case, pointers in the right direction would be great. Thanks!
SOLUTION:
Lennart's solution, with some slight adaptations worked for me.
Inside my __init__.py I have the following code:
plugins = []
__all__ = [
'do_all_the_things',
'do_another_thing',
'someaction',
'chop_down_the_tree_with_a_herring',
'seek_the_grail',
]
Then inside each of my plugins I have something like this:
from actions import plugins
class SeekTheGrail:
def __init__(self):
self.text = 'We Seek The Grail'
self.icon = b'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'
def action(self):
print('I am Arthur, King of the Britons!')
plugins.append(SeekTheGrail())
and now I just have to modify the __all__ collection when I want to add or remove the actions from loading.
The way to create plugins is to have a plugin registry, and register plugins there. This can be done from the amazingly simple, like just defining a global list in a module:
plugins = []
And then registering the plugins with:
class APlugin(object):
blahblah
from themodule import plugins
plugin.add(APlugin())
This can be made more flexible by having types of plugins and registering them with a name etc. But then you are on the path to complexity, and then you might want to take a look at the Zope Component Architecture, which simply speaking is a very powerful system for making your application pluggable.
It should be noted that in any case it's best to have a list of what plugins should be activated instead of auto-discovering them. This can be as simple as a list in a text file or a setting in a config file. This enables you to easily activate and disactivate the plugins.
You can use something like this:
actions = {}
for filename in os.listdir('somepath/actions'):
actions[filename] = __import__('actions.'+filename)
for name,class in actions:
createButton(name,class)
Where in the createButton you create your tk_inter button and assign it the functions from the class. The important bit i suppose is that you can use __import__ to import a class using its string name and save it as a variable.

Trac - Get date of last commit of a specific repository file in wiki page

I'm using Trac to manage my projects. Some guys have to take a regular look to my wiki pages to watch changes in a special file. Therefore I have a link on my wikipage. Something link this:
[source:MyRepository/trunk/subfolder/file.pdf#head The file to read]
What I would like to have is the following output:
The file to read (last commited: 11/01/15 at 08:52am)
Any ideas how to manage that in trac wiki?
You will probably have to write a small plugin for that, something like the following:
from genshi.builder import tag
from trac.util.datefmt import format_datetime
from trac.util.translation import _
from trac.versioncontrol.api import RepositoryManager
from trac.wiki.api import parse_args
from trac.wiki.macros import WikiMacroBase
class SourceMacro(WikiMacroBase):
def expand_macro(self, formatter, name, content):
args, kwargs = parse_args(content)
path = args[0]
label = kwargs.get('label', path)
rm = RepositoryManager(self.env)
reponame, repos, path = rm.get_repository_by_path(path)
node = repos.get_node(path)
href = formatter.href.browser(reponame or None, path)
return tag(tag.a(label, href=href),
_(' (last committed: %(date)s)',
date=format_datetime(node.last_modified)))
(For Trac 0.12 or later, error handling left as the dreaded exercise to the reader.)
Place this in a file file named source_link.py in the plugins directory of your Trac environment. Then you can reference your files with the following macro:
[[Source(MyRepository/trunk/subfolder/file.pdf)]]
or if you want a specific label:
[[Source(MyRepository/trunk/subfolder/file.pdf, label=The file to read)]]