UNSOLVED Pragma directives in Python?

Are there any pragma directives in Python to be able to, for example, stop some lines of code to be included when the "Source Protector..." command is executed on a .pyp file?

Hello @rui_mac,

thank you for reaching out to us. I am a bit confused by your question. #pragma is a pre-processor directive and Python has no pre-processor in that sense. But you can of course put conditional statements into the the module scope, or code in general, which then can sort of work like pre-processor directives.

import c4d
import sys

# In C/C++ we would solve something like this with the pre-processor
if sys.version_info.major == 3:
    pass

if c4d.GetC4DVersion() >= 26000:
    pass

class MyFancyPlugin(...):
    ...

A '*.pyp' file is nothing special and does not differ from a common python module. The different file extension primarily does exist so that Cinema knows where to enter a (set of) plugin(s). If you wanted to, you could find out if code is being executed from a '*.pyp' file by looking at the __file__ attribute of an executed module. E.g.:

import c4d

print (__file__)

Last but not least, you cannot detect the source protector being executed, because the source protector does not run any code. I am not sure, but it might be that you are confusing here byte-code compilation and source protection. This has been discussed before here. Source protection is just something that happens before Python code is being interpreted and compiled. It does not care about the content of the file, you could protect your favorite cooky recipes with it, and simply encrypts the content of a file with AES. Before the file is being compiled to byte code, to be loaded by the Python VM, Cinema 4D will decrypt it.

If you want some kind of self modifying code on encryption, I would reccomend using a Python script which wraps the g_encryptPypFile of Cinema 4D apps, e.g., c4dpy as shown in the thread linked above. So, you have some script do_encrypt.py which takes another file encrypt_me.py as an argument. It will then run c4dpy with encrypt_me.py as the encryption argument. If you wanted encrypt_me.py to be modified to some extent, you could:

  1. Just put that into do_encrypt.py, or
  2. Have an __encrpyt__ execution context in encrypt_me.py which modifies the file, so that do_encrypt.py can just execute that context for all modules it is encrypting.

Cheers,
Ferdinand

Thank you for the answer.
My purpose was quite simple, I guess. Let me try to explain.
My plugins have a protection scheme that check something online and do some time period calculations. But, during development, I don't want to keep checking for all these. I just want that all those things get executed when the final version of the code is executed, when the person who bought the plugin has it running on their machine.
So, what I was hopping what to have a way to code something like (and this is completely made up..):

#ifnotprotected
return True

Yes, I know the # character is a comment in Python, but let us imagine the Source Protector code checked for keywords directly following the #.
So, if there was a way to include code in the Python code that WAS NOT INCLUDED in the final encrypted file that is produced by the Source Protector but is executed when the code is still being developed (or vice-versa, optionally), that would be great.
There is no such thing, is it?

Hey @rui_mac,

thank you for the clarification. The major obstacle seems to be here faciality with Python and that I consider things obvious that are not that obvious. So let me be verbose here (go to point 4 for TLDR):

  1. Python does not have any pre-processor like logic in the more concrete sense because to Python the whole C-thing of "if this applies, compile this into something entirely different" does not apply, because Python simply does not work like this, there is no strict distinction of platforms or build versions in Python.
  2. CPython, i.e., our interpreter, has a debug attribute, exposed as __debug__ in all modules. By default, this is always true, so all code is by default released in debug mode in Python. You can hook in that attribute to distinguish between a debug and release version. But you should be aware that setting __debug__ to False will also impact other things, e.g., setting it to False will skip all assertions. In the course of writing this answer I found out that there is a bug in our interpreter as it ignores the -O argument. Since __debug__ is read-only without getting very hacky, __debug__ = True, you effectively cannot use this feature in c4dpy. There are also other problems with this I will not go into here. We will fix this in an upcoming release.
# Will be #False when the -0 argument is being passed and #True when not.
print (f"{__debug__ = }")

# Will do nothing when the -0 argument is being passed and raise an AssertionError when not.
assert(True is False)
  1. The other mechanism wich Python has that is somewhat similar to the idea of build versions, is the idea of execution contexts of a module, expressed through its __ name __ attribute. E.g.;
# The standard module execution context to distinguish a file from being loaded
# from being executed.
if __name__ == "__main__":
    pass

# Something like this could also be conceivable, it is just that we, our
# Python interpreter, would have to call Python plugin (*.pyp) modules in such
# manner for this to work. You cannot do this yourself.

# Could be conceived to be called when Cinema 4D is being started with a 
# fictitious debug argument.
if __name__ == "__debug__":
    pass
  1. The only thing that remains for you is to simply define a debug module attribute of your own.
import os

IS_DEBUG: bool = True # Defines the debug state of the plugin.

# A pattern which is sometimes used in Python, is to make a debug state 
# dependent on the existence of a file, usually a log file. The reasoning
# is here that it is harder to overlook a file in a release package than
# some relatively hidden module attribute.
HAS_DEBUG_LOG: bool = os.path.exists(
    os.path.join(os.path.dirname(__file__), "debug.log")

Cheers,
Ferdinand

Once again, thenk you very much for all the information, Ferdinand.
What I had done already was the last "solution".
I do have a file inside the plugin folder that, when the code detects that it is present, skips some verifications.
So, I guess I will go on doing that. That even allows me to check for specific filenames and perform different actions for each one.
I was just checking to see if there was any type of build-in mechanism to emulate that pragma directives did.
And I do know that python is interpreted and that real pragma directives are evaluated in the prepass stage.
But I didn't knew if the Source Protector performed some type of evaluation, when protecting the code.